aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeun-young Park <keunyoung@google.com>2016-06-28 12:58:23 -0700
committerKeun-young Park <keunyoung@google.com>2016-07-08 10:17:55 -0700
commit4c6834a27ccbcf42cbeef43059751752baf80ac3 (patch)
treecd0af929cb33f6725f2f1b06327e6e684229331f
parent8739715585b9c3c7aa685bc732dec6a484825c22 (diff)
downloadCar-4c6834a27ccbcf42cbeef43059751752baf80ac3.tar.gz
add external audio routing support
- external audio routing is done via audio focus request - add hidden CarAudioManager API for apps to request audio focus for external audio source: will be left hidden until it is good to go as public or system api - add test to confirm external routing bug: 29601910 Change-Id: I33600a6f53b0854cd3b55663a8be6987ac20413a
-rw-r--r--car-lib/src/android/car/media/CarAudioManager.java116
-rw-r--r--car-lib/src/android/car/media/ICarAudio.aidl4
-rw-r--r--car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java1
-rw-r--r--libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java6
-rw-r--r--libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java24
-rwxr-xr-xlibvehiclenetwork/tool/vehiclehal_code_gen.py23
-rw-r--r--service/src/com/android/car/AudioRoutingPolicy.java5
-rw-r--r--service/src/com/android/car/CarAudioAttributesUtil.java50
-rw-r--r--service/src/com/android/car/CarAudioService.java446
-rw-r--r--service/src/com/android/car/hal/AudioHalService.java118
-rw-r--r--service/src/com/android/car/hal/VehicleHal.java6
-rw-r--r--tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java1047
-rw-r--r--tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java28
-rw-r--r--tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java15
-rw-r--r--vns_policy/vns_policy.xml4
15 files changed, 1762 insertions, 131 deletions
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 4dd8be46b2..2d205620a6 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -77,18 +77,48 @@ public class CarAudioManager implements CarManagerBase {
* Audio usage for playing safety alert.
*/
public static final int CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT = 9;
+ /**
+ * Audio usage for external audio usage.
+ * @hide
+ */
+ public static final int CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE = 10;
/** @hide */
- public static final int CAR_AUDIO_USAGE_MAX = CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT;
+ public static final int CAR_AUDIO_USAGE_MAX = CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
/** @hide */
@IntDef({CAR_AUDIO_USAGE_DEFAULT, CAR_AUDIO_USAGE_MUSIC, CAR_AUDIO_USAGE_RADIO,
CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE, CAR_AUDIO_USAGE_VOICE_CALL,
CAR_AUDIO_USAGE_VOICE_COMMAND, CAR_AUDIO_USAGE_ALARM, CAR_AUDIO_USAGE_NOTIFICATION,
- CAR_AUDIO_USAGE_SYSTEM_SOUND, CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT})
+ CAR_AUDIO_USAGE_SYSTEM_SOUND, CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT,
+ CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE})
@Retention(RetentionPolicy.SOURCE)
public @interface CarAudioUsage {}
+ /** @hide */
+ public static final String CAR_RADIO_TYPE_AM_FM = "RADIO_AM_FM";
+ /** @hide */
+ public static final String CAR_RADIO_TYPE_AM_FM_HD = "RADIO_AM_FM_HD";
+ /** @hide */
+ public static final String CAR_RADIO_TYPE_DAB = "RADIO_DAB";
+ /** @hide */
+ public static final String CAR_RADIO_TYPE_SATELLITE = "RADIO_SATELLITE";
+
+ /** @hide */
+ public static final String CAR_EXTERNAL_SOURCE_TYPE_CD_DVD = "CD_DVD";
+ /** @hide */
+ public static final String CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0 = "AUX_IN0";
+ /** @hide */
+ public static final String CAR_EXTERNAL_SOURCE_TYPE_AUX_IN1 = "AUX_IN1";
+ /** @hide */
+ public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE = "EXT_NAV_GUIDANCE";
+ /** @hide */
+ public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_VOICE_CALL = "EXT_VOICE_CALL";
+ /** @hide */
+ public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_VOICE_COMMAND = "EXT_VOICE_COMMAND";
+ /** @hide */
+ public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_SAFETY_ALERT = "EXT_SAFETY_ALERT";
+
private final ICarAudio mService;
private final AudioManager mAudioManager;
@@ -101,9 +131,79 @@ public class CarAudioManager implements CarManagerBase {
try {
return mService.getAudioAttributesForCarUsage(carUsage);
} catch (RemoteException e) {
- AudioAttributes.Builder builder = new AudioAttributes.Builder();
- return builder.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).
- setUsage(AudioAttributes.USAGE_UNKNOWN).build();
+ return createAudioAttributes(AudioAttributes.CONTENT_TYPE_UNKNOWN,
+ AudioAttributes.USAGE_UNKNOWN);
+ }
+ }
+
+ /**
+ * Get AudioAttributes for radio. This is necessary when there are multiple types of radio
+ * in system.
+ *
+ * @param radioType String specifying the desired radio type. Should use only what is listed in
+ * {@link #getSupportedRadioTypes()}.
+ * @return
+ * @throws IllegalArgumentException If not supported type is passed.
+ *
+ * @hide
+ */
+ public AudioAttributes getAudioAttributesForRadio(String radioType)
+ throws IllegalArgumentException {
+ try {
+ return mService.getAudioAttributesForRadio(radioType);
+ } catch (RemoteException e) {
+ return createAudioAttributes(AudioAttributes.CONTENT_TYPE_UNKNOWN,
+ AudioAttributes.USAGE_UNKNOWN);
+ }
+ }
+
+ /**
+ * Get AudioAttributes for external audio source.
+ *
+ * @param externalSourceType String specifying the desired source type. Should use only what is
+ * listed in {@link #getSupportedExternalSourceTypes()}.
+ * @return
+ * @throws IllegalArgumentException If not supported type is passed.
+ *
+ * @hide
+ */
+ public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType)
+ throws IllegalArgumentException {
+ try {
+ return mService.getAudioAttributesForExternalSource(externalSourceType);
+ } catch (RemoteException e) {
+ return createAudioAttributes(AudioAttributes.CONTENT_TYPE_UNKNOWN,
+ AudioAttributes.USAGE_UNKNOWN);
+ }
+ }
+
+ /**
+ * List all supported external audio sources.
+ *
+ * @return
+ *
+ * @hide
+ */
+ public String[] getSupportedExternalSourceTypes() {
+ try {
+ return mService.getSupportedExternalSourceTypes();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * List all supported radio sources.
+ *
+ * @return
+ *
+ * @hide
+ */
+ public String[] getSupportedRadioTypes() {
+ try {
+ return mService.getSupportedRadioTypes();
+ } catch (RemoteException e) {
+ return null;
}
}
@@ -271,7 +371,6 @@ public class CarAudioManager implements CarManagerBase {
@Override
public void onCarDisconnected() {
- // TODO Auto-generated method stub
}
/** @hide */
@@ -279,4 +378,9 @@ public class CarAudioManager implements CarManagerBase {
mService = ICarAudio.Stub.asInterface(service);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
+
+ private AudioAttributes createAudioAttributes(int contentType, int usage) {
+ AudioAttributes.Builder builder = new AudioAttributes.Builder();
+ return builder.setContentType(contentType).setUsage(usage).build();
+ }
}
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index b4e1283572..1c07796db6 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -34,4 +34,8 @@ interface ICarAudio {
int getStreamVolume(int streamType) = 5;
boolean isMediaMuted() = 6;
boolean setMediaMute(boolean mute) = 7;
+ AudioAttributes getAudioAttributesForRadio(in String radioType) = 8;
+ AudioAttributes getAudioAttributesForExternalSource(in String externalSourceType) = 9;
+ String[] getSupportedExternalSourceTypes() = 10;
+ String[] getSupportedRadioTypes() = 11;
}
diff --git a/car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java b/car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java
index fdb81074c4..afe3f4c210 100644
--- a/car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java
+++ b/car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java
@@ -202,6 +202,7 @@ public class VehicleHalEmulator {
switch (property) {
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME:
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS:
+ case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT:
case VehicleNetworkConsts.VEHICLE_PROPERTY_AP_POWER_STATE:
case VehicleNetworkConsts.VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON:
continue;
diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java
index f7ef2c4449..a54282a1da 100644
--- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java
+++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java
@@ -216,6 +216,12 @@ public class VehicleNetwork {
setProperty(v);
}
+ public void setStringProperty(int property, String value)
+ throws IllegalArgumentException, ServiceSpecificException {
+ VehiclePropValue v = VehiclePropValueUtil.createStringValue(property, value, 0);
+ setProperty(v);
+ }
+
/**
* Set zoned boolean type property
*
diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
index 2e14b7521b..4cbbeeaf9d 100644
--- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
+++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
@@ -60,6 +60,7 @@ public static final int VEHICLE_PROPERTY_AUDIO_VOLUME = 0x00000901;
public static final int VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT = 0x00000902;
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_AUDIO_EXT_ROUTING_HINT = 0x00000905;
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_AP_POWER_BOOTUP_REASON = 0x00000A02;
@@ -153,6 +154,7 @@ case VEHICLE_PROPERTY_AUDIO_VOLUME: return VehicleValueType.VEHICLE_VALUE_TYPE_I
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_AUDIO_EXT_ROUTING_HINT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4;
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_AP_POWER_BOOTUP_REASON: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32;
@@ -246,6 +248,7 @@ case VEHICLE_PROPERTY_AUDIO_VOLUME: return "VEHICLE_PROPERTY_AUDIO_VOLUME";
case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return "VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT";
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_AUDIO_EXT_ROUTING_HINT: return "VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT";
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_AP_POWER_BOOTUP_REASON: return "VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON";
@@ -339,6 +342,7 @@ case VEHICLE_PROPERTY_AUDIO_VOLUME: return new int[] { VehiclePropChangeMode.VEH
case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
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_AUDIO_EXT_ROUTING_HINT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
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_AP_POWER_BOOTUP_REASON: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC };
@@ -432,6 +436,7 @@ case VEHICLE_PROPERTY_AUDIO_VOLUME: return new int[] { VehiclePropAccess.VEHICLE
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_AUDIO_EXT_ROUTING_HINT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
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_AP_POWER_BOOTUP_REASON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
@@ -486,6 +491,23 @@ default: return null;
}
}
+
+
+
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_RADIO_AM_FM = "RADIO_AM_FM";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_RADIO_AM_FM_HD = "RADIO_AM_FM_HD";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_RADIO_DAB = "RADIO_DAB";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_RADIO_SATELLITE = "RADIO_SATELLITE";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_CD_DVD = "CD_DVD";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_AUX_IN0 = "AUX_IN0";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_AUX_IN1 = "AUX_IN1";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_EXT_NAV_GUIDANCE = "EXT_NAV_GUIDANCE";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_EXT_VOICE_COMMAND = "EXT_VOICE_COMMAND";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_EXT_VOICE_CALL = "EXT_VOICE_CALL";
+public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_EXT_SAFETY_ALERT = "EXT_SAFETY_ALERT";
+
+
+
public static class VehicleHvacFanDirection {
public static final int VEHICLE_HVAC_FAN_DIRECTION_FACE = 0x1;
public static final int VEHICLE_HVAC_FAN_DIRECTION_FLOOR = 0x2;
@@ -625,6 +647,7 @@ public static final int VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG = 0x100;
public static final int VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG = 0x200;
public static final int VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG = 0x400;
public static final int VEHICLE_AUDIO_CONTEXT_RADIO_FLAG = 0x800;
+public static final int VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG = 0x1000;
public static String enumToString(int v) {
switch(v) {
case VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG: return "VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG";
@@ -639,6 +662,7 @@ case VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG: return "VEHICLE_AUDIO_CONTEXT_CD_ROM_FLA
case VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG: return "VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG";
case VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG: return "VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG";
case VEHICLE_AUDIO_CONTEXT_RADIO_FLAG: return "VEHICLE_AUDIO_CONTEXT_RADIO_FLAG";
+case VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG: return "VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG";
default: return "UNKNOWN";
}
}
diff --git a/libvehiclenetwork/tool/vehiclehal_code_gen.py b/libvehiclenetwork/tool/vehiclehal_code_gen.py
index e35654fe44..dfabd7dccd 100755
--- a/libvehiclenetwork/tool/vehiclehal_code_gen.py
+++ b/libvehiclenetwork/tool/vehiclehal_code_gen.py
@@ -50,9 +50,10 @@ JAVA_TRAIL = \
}
"""
-RE_PROPERTY_PATTERN = r'/\*\*(.*?)\*/\n\#define\s+VEHICLE_PROPERTY_(\S+)\s+(\S+)'
+RE_PROPERTY_PATTERN = r'/\*\*(.*?)\*/\n\#define\s+VEHICLE_PROPERTY_(\S+)\s+(\(0x\S+\))'
RE_ENUM_PATTERN = r'enum\s+(\S+)\s+\{\S*(.*?)\}'
RE_ENUM_ENTRY_PATTERN = r'(\S+)\s*=\s*(.*?)[,\n]'
+RE_AUDIO_EXT_ROUTING_PATTERN = r'\n\#define\s+VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_(\S+)\s+\"(\S+)\"'
class PropertyInfo(object):
def __init__(self, value, name):
@@ -188,6 +189,10 @@ def printEnums(enums):
for e in enums:
printEnum(e)
+def printExtAudioRoutingSources(audio_ext_routing):
+ for r in audio_ext_routing:
+ print "public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_" + r + ' = "' + r + '";'
+
def main(argv):
vehicle_h_path = os.path.dirname(os.path.abspath(__file__)) + "/../../../../../hardware/libhardware/include/hardware/vehicle.h"
#print vehicle_h_path
@@ -245,10 +250,26 @@ def main(argv):
enums.append(info)
#for e in enums:
# print e
+
+ audio_ext_routing = []
+ audio_ext_routing_re = re.compile(RE_AUDIO_EXT_ROUTING_PATTERN, re.MULTILINE | re.DOTALL)
+ for match in audio_ext_routing_re.finditer(text):
+ #print match
+ name = match.group(1)
+ value = match.group(2)
+ if name != value:
+ print "Warning, AUDIO_EXT_ROUTING_SOURCE_" + name + " does not match " + value
+ else:
+ audio_ext_routing.append(name)
+
print JAVA_HEADER
printProperties(props)
+ print "\n\n"
+ printExtAudioRoutingSources(audio_ext_routing)
+ print "\n\n"
printEnums(enums)
print JAVA_TRAIL
+
if __name__ == '__main__':
main(sys.argv)
diff --git a/service/src/com/android/car/AudioRoutingPolicy.java b/service/src/com/android/car/AudioRoutingPolicy.java
index fdad5aaeb9..0a5951976a 100644
--- a/service/src/com/android/car/AudioRoutingPolicy.java
+++ b/service/src/com/android/car/AudioRoutingPolicy.java
@@ -40,6 +40,11 @@ public class AudioRoutingPolicy {
public static AudioRoutingPolicy create(Context context, int policyNumber) {
final Resources res = context.getResources();
String[] policies = res.getStringArray(R.array.audioRoutingPolicy);
+ if (policyNumber > (policies.length - 1)) {
+ Log.e(CarLog.TAG_AUDIO, "AudioRoutingPolicy.create got wrong policy number:" +
+ policyNumber + ", num of avaiable policies:" + policies.length);
+ policyNumber = 0;
+ }
return new AudioRoutingPolicy(policies[policyNumber]);
}
diff --git a/service/src/com/android/car/CarAudioAttributesUtil.java b/service/src/com/android/car/CarAudioAttributesUtil.java
index 1d0cb00ebe..54ed2c638c 100644
--- a/service/src/com/android/car/CarAudioAttributesUtil.java
+++ b/service/src/com/android/car/CarAudioAttributesUtil.java
@@ -28,7 +28,7 @@ public class CarAudioAttributesUtil {
public static final int CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY = 101;
public static final int CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE = 102;
- /** Bundle key for storing media type */
+ /** Bundle key for storing media type. */
public static final String KEY_CAR_AUDIO_TYPE = "car_audio_type";
private static final int CAR_AUDIO_TYPE_DEFAULT = 0;
@@ -38,12 +38,17 @@ public class CarAudioAttributesUtil {
private static final int CAR_AUDIO_TYPE_CARSERVICE_BOTTOM = 4;
private static final int CAR_AUDIO_TYPE_CARSERVICE_CAR_PROXY = 5;
private static final int CAR_AUDIO_TYPE_CARSERVICE_MEDIA_MUTE = 6;
+ private static final int CAR_AUDIO_TYPE_EXTERNAL_SOURCE = 7;
+
+ /** Bundle key for storing routing type which is String. */
+ public static final String KEY_EXT_ROUTING_TYPE = "ext_routing_type";
public static AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
switch (carUsage) {
case CarAudioManager.CAR_AUDIO_USAGE_MUSIC:
return createAudioAttributes(AudioAttributes.CONTENT_TYPE_MUSIC,
AudioAttributes.USAGE_MEDIA);
+ case CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE: // default to radio
case CarAudioManager.CAR_AUDIO_USAGE_RADIO:
return createCustomAudioAttributes(CAR_AUDIO_TYPE_RADIO,
AudioAttributes.CONTENT_TYPE_MUSIC, AudioAttributes.USAGE_MEDIA);
@@ -98,10 +103,13 @@ public class CarAudioAttributesUtil {
}
switch (usage) {
case AudioAttributes.USAGE_MEDIA:
- if (type == CAR_AUDIO_TYPE_RADIO) {
- return CarAudioManager.CAR_AUDIO_USAGE_RADIO;
- } else {
- return CarAudioManager.CAR_AUDIO_USAGE_MUSIC;
+ switch (type) {
+ case CAR_AUDIO_TYPE_RADIO:
+ return CarAudioManager.CAR_AUDIO_USAGE_RADIO;
+ case CAR_AUDIO_TYPE_EXTERNAL_SOURCE:
+ return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
+ default:
+ return CarAudioManager.CAR_AUDIO_USAGE_MUSIC;
}
case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
if (type == CAR_AUDIO_TYPE_VOICE_COMMAND) {
@@ -149,4 +157,36 @@ public class CarAudioAttributesUtil {
bundle.putInt(KEY_CAR_AUDIO_TYPE, carAudioType);
return builder.setContentType(contentType).setUsage(usage).addBundle(bundle).build();
}
+
+ public static AudioAttributes getCarRadioAttributes(String radioType) {
+ AudioAttributes.Builder builder = new AudioAttributes.Builder();
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_CAR_AUDIO_TYPE, CAR_AUDIO_TYPE_RADIO);
+ bundle.putString(KEY_EXT_ROUTING_TYPE, radioType);
+ return builder.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).
+ setUsage(AudioAttributes.USAGE_MEDIA).addBundle(bundle).build();
+ }
+
+ public static AudioAttributes getCarExtSourceAttributes(String externalSourceType) {
+ AudioAttributes.Builder builder = new AudioAttributes.Builder();
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_CAR_AUDIO_TYPE, CAR_AUDIO_TYPE_EXTERNAL_SOURCE);
+ bundle.putString(KEY_EXT_ROUTING_TYPE, externalSourceType);
+ return builder.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).
+ setUsage(AudioAttributes.USAGE_MEDIA).addBundle(bundle).build();
+ }
+
+ /**
+ * Get ext routing type from given AudioAttributes.
+ * @param attr
+ * @return {@link CarAudioManager#CAR_RADIO_TYPE_AM_FM} if ext routing info does not exist.
+ */
+ public static String getExtRouting(AudioAttributes attr) {
+ Bundle bundle = attr.getBundle();
+ String extRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
+ if (bundle != null) {
+ extRouting = bundle.getString(KEY_EXT_ROUTING_TYPE);
+ }
+ return extRouting;
+ }
}
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index c6f09a990c..33fa20dfcf 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -40,6 +40,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
import com.android.car.hal.AudioHalService;
import com.android.car.hal.AudioHalService.AudioHalFocusListener;
@@ -47,7 +48,13 @@ import com.android.car.hal.VehicleHal;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
AudioHalFocusListener {
@@ -107,7 +114,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
@GuardedBy("mLock")
private int mBottomFocusState;
@GuardedBy("mLock")
- private boolean mRadioActive = false;
+ private boolean mRadioOrExtSourceActive = false;
@GuardedBy("mLock")
private boolean mCallActive = false;
@GuardedBy("mLock")
@@ -125,6 +132,24 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
@GuardedBy("mLock")
private int mNumConsecutiveHalFailures;
+ @GuardedBy("mLock")
+ private boolean mExternalRoutingHintSupported;
+ @GuardedBy("mLock")
+ private Map<String, AudioHalService.ExtRoutingSourceInfo> mExternalRoutingTypes;
+ @GuardedBy("mLock")
+ private Set<String> mExternalRadioRoutingTypes;
+ @GuardedBy("mLock")
+ private String mDefaultRadioRoutingType;
+ @GuardedBy("mLock")
+ private Set<String> mExternalNonRadioRoutingTypes;
+ @GuardedBy("mLock")
+ private int mRadioPhysicalStream;
+ @GuardedBy("mLock")
+ private int[] mExternalRoutings = {0, 0, 0, 0};
+ private int[] mExternalRoutingsScratch = {0, 0, 0, 0};
+ private final int[] mExternalRoutingsForFocusRelease = {0, 0, 0, 0};
+ private final ExtSourceInfo mExtSourceInfoScratch = new ExtSourceInfo();
+
private final boolean mUseDynamicRouting;
private final AudioAttributes mAttributeBottom =
@@ -191,12 +216,61 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
}
mAudioHal.setFocusListener(this);
mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
+ // get call outside lock as it can take time
+ HashSet<String> externalRadioRoutingTypes = new HashSet<>();
+ HashSet<String> externalNonRadioRoutingTypes = new HashSet<>();
+ Map<String, AudioHalService.ExtRoutingSourceInfo> externalRoutingTypes =
+ mAudioHal.getExternalAudioRoutingTypes();
+ if (externalRoutingTypes != null) {
+ for (String routingType : externalRoutingTypes.keySet()) {
+ if (routingType.startsWith("RADIO_")) {
+ externalRadioRoutingTypes.add(routingType);
+ } else {
+ externalNonRadioRoutingTypes.add(routingType);
+ }
+ }
+ }
+ // select default radio routing. AM_FM -> AM_FM_HD -> whatever with AM or FM -> first one
+ String defaultRadioRouting = null;
+ if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM)) {
+ defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
+ } else if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD)) {
+ defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD;
+ } else {
+ for (String radioType : externalRadioRoutingTypes) {
+ // set to 1st one
+ if (defaultRadioRouting == null) {
+ defaultRadioRouting = radioType;
+ }
+ if (radioType.contains("AM") || radioType.contains("FM")) {
+ defaultRadioRouting = radioType;
+ break;
+ }
+ }
+ }
+ if (defaultRadioRouting == null) { // no radio type defined. fall back to AM_FM
+ defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
+ }
+ int radioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
+ CarAudioManager.CAR_AUDIO_USAGE_RADIO);
synchronized (mLock) {
if (audioPolicy != null) {
mAudioPolicy = audioPolicy;
}
+ mRadioPhysicalStream = radioPhysicalStream;
mAudioRoutingPolicy = audioRoutingPolicy;
mIsRadioExternal = mAudioHal.isRadioExternal();
+ if (externalRoutingTypes != null) {
+ mExternalRoutingHintSupported = true;
+ mExternalRoutingTypes = externalRoutingTypes;
+ } else {
+ mExternalRoutingHintSupported = false;
+ mExternalRoutingTypes = new HashMap<>();
+ }
+ mExternalRadioRoutingTypes = externalRadioRoutingTypes;
+ mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes;
+ mDefaultRadioRoutingType = defaultRadioRouting;
+ Arrays.fill(mExternalRoutings, 0);
}
mVolumeService.init();
}
@@ -322,7 +396,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
mLastFocusRequestToCar = null;
mTopFocusInfo = null;
mPendingFocusChanges.clear();
- mRadioActive = false;
+ mRadioOrExtSourceActive = false;
if (mCarAudioContextChangeHandler != null) {
mCarAudioContextChangeHandler.cancelAll();
mCarAudioContextChangeHandler = null;
@@ -331,6 +405,9 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
mCurrentPrimaryAudioContext = 0;
audioPolicy = mAudioPolicy;
mAudioPolicy = null;
+ mExternalRoutingTypes.clear();
+ mExternalRadioRoutingTypes.clear();
+ mExternalNonRadioRoutingTypes.clear();
}
if (audioPolicy != null) {
mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
@@ -351,18 +428,31 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
@Override
public void dump(PrintWriter writer) {
- writer.println("*CarAudioService*");
- writer.println(" mCurrentFocusState:" + mCurrentFocusState +
- " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
- writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts));
- writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
- writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
- " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
- writer.println(" mIsRadioExternal:" + mIsRadioExternal);
- writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
- writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted());
- writer.println(" mAudioPolicy:" + mAudioPolicy);
- mAudioRoutingPolicy.dump(writer);
+ synchronized (mLock) {
+ writer.println("*CarAudioService*");
+ writer.println(" mCurrentFocusState:" + mCurrentFocusState +
+ " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
+ writer.println(" mCurrentAudioContexts:0x" +
+ Integer.toHexString(mCurrentAudioContexts));
+ writer.println(" mCallActive:" + mCallActive + " mRadioOrExtSourceActive:" +
+ mRadioOrExtSourceActive);
+ writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
+ " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
+ writer.println(" mIsRadioExternal:" + mIsRadioExternal);
+ writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
+ writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted());
+ writer.println(" mAudioPolicy:" + mAudioPolicy);
+ mAudioRoutingPolicy.dump(writer);
+ writer.println(" mExternalRoutingHintSupported:" + mExternalRoutingHintSupported);
+ if (mExternalRoutingHintSupported) {
+ writer.println(" mDefaultRadioRoutingType:" + mDefaultRadioRoutingType);
+ writer.println(" Routing Types:");
+ for (Entry<String, AudioHalService.ExtRoutingSourceInfo> entry :
+ mExternalRoutingTypes.entrySet()) {
+ writer.println(" type:" + entry.getKey() + " info:" + entry.getValue());
+ }
+ }
+ }
}
@Override
@@ -429,6 +519,44 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
}
}
+ @Override
+ public AudioAttributes getAudioAttributesForRadio(String radioType) {
+ synchronized (mLock) {
+ if (!mExternalRadioRoutingTypes.contains(radioType)) { // type not exist
+ throw new IllegalArgumentException("Specified radio type is not available:" +
+ radioType);
+ }
+ }
+ return CarAudioAttributesUtil.getCarRadioAttributes(radioType);
+ }
+
+ @Override
+ public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) {
+ synchronized (mLock) {
+ if (!mExternalNonRadioRoutingTypes.contains(externalSourceType)) { // type not exist
+ throw new IllegalArgumentException("Specified ext source type is not available:" +
+ externalSourceType);
+ }
+ }
+ return CarAudioAttributesUtil.getCarExtSourceAttributes(externalSourceType);
+ }
+
+ @Override
+ public String[] getSupportedExternalSourceTypes() {
+ synchronized (mLock) {
+ return mExternalNonRadioRoutingTypes.toArray(
+ new String[mExternalNonRadioRoutingTypes.size()]);
+ }
+ }
+
+ @Override
+ public String[] getSupportedRadioTypes() {
+ synchronized (mLock) {
+ return mExternalRadioRoutingTypes.toArray(
+ new String[mExternalRadioRoutingTypes.size()]);
+ }
+ }
+
/**
* API for system to control mute with lock.
* @param mute
@@ -495,12 +623,12 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
}
mLastFocusRequestToCar = null;
- if (mRadioActive &&
+ if (mRadioOrExtSourceActive &&
(mCurrentFocusState.externalFocus &
AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
// radio flag dropped
newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
- mRadioActive = false;
+ mRadioOrExtSourceActive = false;
}
if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT ||
@@ -627,18 +755,20 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
return false;
}
- private boolean isFocusFromExternalRadio(AudioFocusInfo info) {
- if (!mIsRadioExternal) {
- // if radio is not external, no special handling of radio is necessary.
- return false;
- }
+ private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) {
if (info == null) {
return false;
}
AudioAttributes attrib = info.getAttributes();
- if (attrib != null &&
- CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
- CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
+ if (attrib == null) {
+ return false;
+ }
+ // if radio is not external, no special handling of radio is necessary.
+ if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
+ CarAudioManager.CAR_AUDIO_USAGE_RADIO && mIsRadioExternal) {
+ return true;
+ } else if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
+ CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE) {
return true;
}
return false;
@@ -679,8 +809,8 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
break;
}
- if (mRadioActive) { // radio is no longer active.
- mRadioActive = false;
+ if (mRadioOrExtSourceActive) { // radio is no longer active.
+ mRadioOrExtSourceActive = false;
}
return false;
}
@@ -692,53 +822,53 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
boolean muteMedia = false;
+ String primaryExtSource = CarAudioAttributesUtil.getExtRouting(attrib);
// update primary context and notify if necessary
- int primaryContext = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop);
- switch (logicalStreamTypeForTop) {
- case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE:
+ int primaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
+ logicalStreamTypeForTop, primaryExtSource);
+ if (logicalStreamTypeForTop ==
+ CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
muteMedia = true;
- // remaining parts the same with other cases. fall through.
- case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM:
- case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY:
- primaryContext = 0;
- break;
- }
- // save the current context now but it is sent to context change listener after focus
- // response from car
- if (mCurrentPrimaryAudioContext != primaryContext) {
- mCurrentPrimaryAudioContext = primaryContext;
- mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
}
-
- int audioContexts = 0;
if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
- if (!mCallActive) {
- mCallActive = true;
- audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG;
- }
+ mCallActive = true;
} else {
- if (mCallActive) {
- mCallActive = false;
- }
- audioContexts = primaryContext;
+ mCallActive = false;
}
// 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;
+ boolean primaryIsExternal = false;
+ if (isFocusFromExternalRadioOrExternalSource(mTopFocusInfo)) {
+ streamsToRequest = 0;
+ mRadioOrExtSourceActive = true;
+ primaryIsExternal = true;
+ if (fixExtSourceAndContext(
+ mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) {
+ primaryExtSource = mExtSourceInfoScratch.source;
+ primaryContext = mExtSourceInfoScratch.context;
+ }
+ } else {
+ mRadioOrExtSourceActive = false;
+ primaryExtSource = null;
+ }
+ // save the current context now but it is sent to context change listener after focus
+ // response from car
+ if (mCurrentPrimaryAudioContext != primaryContext) {
+ mCurrentPrimaryAudioContext = primaryContext;
+ mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
+ }
+
+ boolean secondaryIsExternal = false;
+ int secondaryContext = 0;
+ String secondaryExtSource = null;
switch (mTopFocusInfo.getGainRequest()) {
case AudioManager.AUDIOFOCUS_GAIN:
- if (isFocusFromExternalRadio(mTopFocusInfo)) {
- mRadioActive = true;
- } else {
- mRadioActive = false;
- }
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:
@@ -758,9 +888,30 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
muteMedia = true;
break;
}
- int secondContext = AudioHalService.logicalStreamToHalContextType(
- logicalStreamTypeForSecond);
- audioContexts |= secondContext;
+ if (isFocusFromExternalRadioOrExternalSource(mSecondFocusInfo)) {
+ secondaryIsExternal = true;
+ secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib);
+ secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
+ logicalStreamTypeForSecond, secondaryExtSource);
+ if (fixExtSourceAndContext(
+ mExtSourceInfoScratch.set(secondaryExtSource, secondaryContext))) {
+ secondaryExtSource = mExtSourceInfoScratch.source;
+ secondaryContext = mExtSourceInfoScratch.context;
+ }
+ int secondaryExtPhysicalStreamFlag =
+ getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
+ if ((secondaryExtPhysicalStreamFlag & streamsToRequest) != 0) {
+ // secondary stream is the same as primary. cannot keep secondary
+ secondaryIsExternal = false;
+ secondaryContext = 0;
+ secondaryExtSource = null;
+ break;
+ }
+ mRadioOrExtSourceActive = true;
+ } else {
+ secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
+ logicalStreamTypeForSecond, null);
+ }
switch (mCurrentFocusState.focusState) {
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
streamsToRequest |= mCurrentFocusState.streams;
@@ -783,45 +934,155 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
streamsToRequest = 0;
break;
}
+ int audioContexts = 0;
if (muteMedia) {
- mRadioActive = false;
- audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
- AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG);
- extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
- int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
- CarAudioManager.CAR_AUDIO_USAGE_RADIO);
- streamsToRequest &= ~(0x1 << radioPhysicalStream);
- } else if (mRadioActive) {
- // 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(
- CarAudioManager.CAR_AUDIO_USAGE_RADIO);
- if (!isFocusFromExternalRadio(mTopFocusInfo) &&
- (physicalStreamTypeForTop == radioPhysicalStream) && mIsRadioExternal) {
- Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" +
- physicalStreamTypeForTop + " as radio, stopping radio");
- // stream conflict here. radio cannot be played
- extFocus = 0;
- mRadioActive = false;
- audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
+ boolean addMute = true;
+ if (primaryIsExternal) {
+ if ((getPhysicalStreamFlagForExtSourceLocked(primaryExtSource) &
+ (0x1 << mRadioPhysicalStream)) != 0) {
+ // cannot mute as primary is media
+ addMute = false;
+ }
+ } else if (secondaryIsExternal) {
+ if ((getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource) &
+ (0x1 << mRadioPhysicalStream)) != 0) {
+ mRadioOrExtSourceActive = false;
+ }
} else {
+ mRadioOrExtSourceActive = false;
+ }
+ audioContexts = primaryContext | secondaryContext;
+ if (addMute) {
+ audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
+ AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG |
+ AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG |
+ AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG);
+ extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
+ streamsToRequest &= ~(0x1 << mRadioPhysicalStream);
+ }
+ } else if (mRadioOrExtSourceActive) {
+ boolean addExtFocusFlag = true;
+ if (primaryIsExternal) {
+ int primaryExtPhysicalStreamFlag =
+ getPhysicalStreamFlagForExtSourceLocked(primaryExtSource);
+ if (secondaryIsExternal) {
+ int secondaryPhysicalStreamFlag =
+ getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
+ if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) {
+ // overlap, drop secondary
+ audioContexts &= ~secondaryContext;
+ secondaryContext = 0;
+ secondaryExtSource = null;
+ }
+ streamsToRequest = 0;
+ } else { // primary only
+ if (streamsToRequest == primaryExtPhysicalStreamFlag) {
+ // cannot keep secondary
+ secondaryContext = 0;
+ }
+ streamsToRequest &= ~primaryExtPhysicalStreamFlag;
+ }
+ }
+ if (addExtFocusFlag) {
extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
- streamsToRequest &= ~(0x1 << radioPhysicalStream);
}
+ audioContexts = primaryContext | secondaryContext;
} else if (streamsToRequest == 0) {
mCurrentAudioContexts = 0;
mFocusHandler.handleFocusReleaseRequest();
return false;
+ } else {
+ audioContexts = primaryContext | secondaryContext;
}
+ boolean routingHintChanged = sendExtRoutingHintToCarIfNecessaryLocked(primaryExtSource,
+ secondaryExtSource);
return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
- audioContexts);
+ audioContexts, routingHintChanged);
+ }
+
+ /**
+ * Fix external source info if it is not valid.
+ * @param extSourceInfo
+ * @return true if value is not valid and was updated.
+ */
+ private boolean fixExtSourceAndContext(ExtSourceInfo extSourceInfo) {
+ if (!mExternalRoutingTypes.containsKey(extSourceInfo.source)) {
+ Log.w(CarLog.TAG_AUDIO, "External source not available:" + extSourceInfo.source);
+ // fall back to radio
+ extSourceInfo.source = mDefaultRadioRoutingType;
+ extSourceInfo.context = AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
+ return true;
+ }
+ if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG &&
+ !extSourceInfo.source.startsWith("RADIO_")) {
+ Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source);
+ extSourceInfo.source = mDefaultRadioRoutingType;
+ return true;
+ }
+ return false;
+ }
+
+ private int getPhysicalStreamFlagForExtSourceLocked(String extSource) {
+ AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
+ extSource);
+ if (info != null) {
+ return 0x1 << info.physicalStreamNumber;
+ } else {
+ return 0x1 << mRadioPhysicalStream;
+ }
+ }
+
+ private boolean sendExtRoutingHintToCarIfNecessaryLocked(String primarySource,
+ String secondarySource) {
+ if (!mExternalRoutingHintSupported) {
+ return false;
+ }
+ if (DBG) {
+ Log.d(TAG_FOCUS, "Setting external routing hint, primary:" + primarySource +
+ " secondary:" + secondarySource);
+ }
+ Arrays.fill(mExternalRoutingsScratch, 0);
+ fillExtRoutingPositionLocked(mExternalRoutingsScratch, primarySource);
+ fillExtRoutingPositionLocked(mExternalRoutingsScratch, secondarySource);
+ if (Arrays.equals(mExternalRoutingsScratch, mExternalRoutings)) {
+ return false;
+ }
+ System.arraycopy(mExternalRoutingsScratch, 0, mExternalRoutings, 0,
+ mExternalRoutingsScratch.length);
+ if (DBG) {
+ Log.d(TAG_FOCUS, "Set values:" + Arrays.toString(mExternalRoutingsScratch));
+ }
+ try {
+ mAudioHal.setExternalRoutingSource(mExternalRoutings);
+ } catch (IllegalArgumentException e) {
+ //ignore. can happen with mocking.
+ return false;
+ }
+ return true;
+ }
+
+ private void fillExtRoutingPositionLocked(int[] array, String extSource) {
+ if (extSource == null) {
+ return;
+ }
+ AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
+ extSource);
+ if (info == null) {
+ return;
+ }
+ int pos = info.bitPosition;
+ if (pos < 0) {
+ return;
+ }
+ int index = pos / 32;
+ int bitPosInInt = pos % 32;
+ array[index] |= (0x1 << bitPosInInt);
}
private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
- int streamsToRequest, int extFocus, int audioContexts) {
+ int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) {
if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
- audioContexts)) {
+ audioContexts) || forceSend) {
mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
extFocus);
mCurrentAudioContexts = audioContexts;
@@ -963,6 +1224,9 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
sent = true;
try {
+ if (mExternalRoutingHintSupported) {
+ mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease);
+ }
mAudioHal.requestAudioFocusChange(
AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
} catch (IllegalArgumentException e) {
@@ -1355,4 +1619,16 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
public static FocusRequest STATE_RELEASE =
new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
}
+
+ private static class ExtSourceInfo {
+
+ public String source;
+ public int context;
+
+ public ExtSourceInfo set(String source, int context) {
+ this.source = source;
+ this.context = context;
+ return this;
+ }
+ }
}
diff --git a/service/src/com/android/car/hal/AudioHalService.java b/service/src/com/android/car/hal/AudioHalService.java
index 8d650c9f33..5470f9700a 100644
--- a/service/src/com/android/car/hal/AudioHalService.java
+++ b/service/src/com/android/car/hal/AudioHalService.java
@@ -47,6 +47,8 @@ import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class AudioHalService extends HalServiceBase {
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_INVALID = -1;
@@ -131,6 +133,8 @@ public class AudioHalService extends HalServiceBase {
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG;
public static final int AUDIO_CONTEXT_SYSTEM_SOUND_FLAG =
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
+ public static final int AUDIO_CONTEXT_EXT_SOURCE_FLAG =
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG;
public interface AudioHalFocusListener {
/**
@@ -165,6 +169,8 @@ public class AudioHalService extends HalServiceBase {
void onVolumeLimitChange(int streamNumber, int volume);
}
+ private static final boolean DBG = true;
+
private final VehicleHal mVehicleHal;
private AudioHalFocusListener mFocusListener;
private AudioHalVolumeListener mVolumeListener;
@@ -215,7 +221,7 @@ public class AudioHalService extends HalServiceBase {
/**
* Returns the volume limits of a stream in the form <min, max>.
*/
- public Pair<Integer, Integer> getStreamVolumeLimit(int stream) {
+ public synchronized Pair<Integer, Integer> getStreamVolumeLimit(int stream) {
if (!isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME)) {
throw new IllegalStateException("VEHICLE_PROPERTY_AUDIO_VOLUME not supported");
}
@@ -254,6 +260,10 @@ public class AudioHalService extends HalServiceBase {
* Convert car audio manager stream type (usage) into audio context type.
*/
public static int logicalStreamToHalContextType(int logicalStream) {
+ return logicalStreamWithExtTypeToHalContextType(logicalStream, null);
+ }
+
+ public static int logicalStreamWithExtTypeToHalContextType(int logicalStream, String extType) {
switch (logicalStream) {
case CarAudioManager.CAR_AUDIO_USAGE_RADIO:
return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG;
@@ -275,6 +285,24 @@ public class AudioHalService extends HalServiceBase {
return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_DEFAULT:
return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_UNKNOWN_FLAG;
+ case CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE:
+ if (extType != null) {
+ switch (extType) {
+ case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_CD_DVD:
+ return AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG;
+ case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0:
+ case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN1:
+ return AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG;
+ default:
+ if (extType.startsWith("RADIO_")) {
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG;
+ } else {
+ return AudioHalService.AUDIO_CONTEXT_EXT_SOURCE_FLAG;
+ }
+ }
+ } else { // no external source specified. fall back to radio
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG;
+ }
case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM:
case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY:
case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE:
@@ -300,11 +328,11 @@ public class AudioHalService extends HalServiceBase {
case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_VOICE_COMMAND_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_VOICE_COMMAND;
case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG:
- return CarAudioManager.CAR_AUDIO_USAGE_MUSIC;
+ return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CALL_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL;
case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG:
- return CarAudioManager.CAR_AUDIO_USAGE_MUSIC;
+ return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NOTIFICATION_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_NOTIFICATION;
case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG:
@@ -315,6 +343,8 @@ public class AudioHalService extends HalServiceBase {
return CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND;
case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_UNKNOWN_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_DEFAULT;
+ case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG:
+ return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
default:
Log.w(CarLog.TAG_AUDIO, "Unknown car context:" + carContext);
return 0;
@@ -466,6 +496,87 @@ public class AudioHalService extends HalServiceBase {
}
}
+ public static class ExtRoutingSourceInfo {
+ /** Represents an external route which will not disable any physical stream in android side.
+ */
+ public static final int NO_DISABLED_PHYSICAL_STREAM = -1;
+
+ /** Bit position of this source in vhal */
+ public final int bitPosition;
+ /**
+ * Physical stream replaced by this routing. will be {@link #NO_DISABLED_PHYSICAL_STREAM}
+ * if no physical stream for android is replaced by this routing.
+ */
+ public final int physicalStreamNumber;
+
+ public ExtRoutingSourceInfo(int bitPosition, int physycalStreamNumber) {
+ this.bitPosition = bitPosition;
+ this.physicalStreamNumber = physycalStreamNumber;
+ }
+
+ @Override
+ public String toString() {
+ return "[bitPosition=" + bitPosition + ", physycalStreamNumber="
+ + physicalStreamNumber + "]";
+ }
+ }
+
+ /**
+ * Get external audio routing types from AUDIO_EXT_ROUTING_HINT property.
+ *
+ * @return null if AUDIO_EXT_ROUTING_HINT is not supported.
+ */
+ public Map<String, ExtRoutingSourceInfo> getExternalAudioRoutingTypes() {
+ VehiclePropConfig config;
+ synchronized (this) {
+ if (!isPropertySupportedLocked(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT)) {
+ return null;
+ }
+ config = mProperties.get(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT);
+ }
+ if (!config.hasConfigString()) {
+ Log.w(CarLog.TAG_HAL, "AUDIO_EXT_ROUTING_HINT with empty config string");
+ return null;
+ }
+ Map<String, ExtRoutingSourceInfo> routingTypes = new HashMap<>();
+ String configString = config.getConfigString();
+ if (DBG) {
+ Log.i(CarLog.TAG_HAL, "AUDIO_EXT_ROUTING_HINT config string:" + configString);
+ }
+ String[] routes = configString.split(",");
+ for (String routeString : routes) {
+ String[] tokens = routeString.split(":");
+ int bitPosition = 0;
+ String name = null;
+ int physicalStreamNumber = ExtRoutingSourceInfo.NO_DISABLED_PHYSICAL_STREAM;
+ if (tokens.length == 2) {
+ bitPosition = Integer.parseInt(tokens[0]);
+ name = tokens[1];
+ } else if (tokens.length == 3) {
+ bitPosition = Integer.parseInt(tokens[0]);
+ name = tokens[1];
+ physicalStreamNumber = Integer.parseInt(tokens[2]);
+ } else {
+ Log.w(CarLog.TAG_AUDIO, "VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT has wrong entry:" +
+ routeString);
+ continue;
+ }
+ routingTypes.put(name, new ExtRoutingSourceInfo(bitPosition, physicalStreamNumber));
+ }
+ return routingTypes;
+ }
+
+ public void setExternalRoutingSource(int[] externalRoutings) {
+ try {
+ mVehicleHal.getVehicleNetwork().setIntVectorProperty(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT, externalRoutings);
+ } catch (ServiceSpecificException e) {
+ Log.e(CarLog.TAG_AUDIO, "Cannot write to VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT", e);
+ }
+ }
+
private boolean isPropertySupportedLocked(int property) {
VehiclePropConfig config = mProperties.get(property);
return config != null;
@@ -509,6 +620,7 @@ public class AudioHalService extends HalServiceBase {
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME:
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT:
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT:
+ case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT:
case VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE:
mProperties.put(p.getProp(), p);
break;
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 181252417f..305f9aedf3 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -247,9 +247,9 @@ public class VehicleHal implements VehicleNetworkListener {
}
public static boolean isPropertySubscribable(VehiclePropConfig config) {
- if (config.hasAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_READ == 0 ||
- config.getChangeMode() ==
- VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC) {
+ if ((config.getAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_READ) == 0 ||
+ (config.getChangeMode() ==
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC)) {
return false;
}
return true;
diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java
new file mode 100644
index 0000000000..0b6268b096
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java
@@ -0,0 +1,1047 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.test;
+
+import android.car.Car;
+import android.car.media.CarAudioManager;
+import android.car.test.VehicleHalEmulator.VehicleHalPropertyHandler;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import com.android.car.vehiclenetwork.VehicleNetworkConsts;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioContextFlag;
+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 java.util.Arrays;
+import java.util.LinkedList;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+public class CarAudioExtFocusTest extends MockedCarTestBase {
+ private static final String TAG = CarAudioExtFocusTest.class.getSimpleName();
+
+ 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, float sampleRate, int zones) {
+ fail("cannot subscribe");
+ }
+
+ @Override
+ public void onPropertyUnsubscribe(int property) {
+ fail("cannot unsubscribe");
+ }
+ };
+
+ private final FocusPropertyHandler mAudioFocusPropertyHandler =
+ new FocusPropertyHandler(this);
+
+ private final ExtRoutingHintPropertyHandler mExtRoutingHintPropertyHandler =
+ new ExtRoutingHintPropertyHandler();
+
+ private static final String EXT_ROUTING_CONFIG =
+ "0:RADIO_AM_FM:0,1:RADIO_SATELLITE:0,33:CD_DVD:0," +
+ "64:com.google.test.SOMETHING_SPECIAL," +
+ "4:EXT_NAV_GUIDANCE:1," +
+ "5:AUX_IN0:0";
+
+ private final Semaphore mWaitSemaphore = new Semaphore(0);
+ private final LinkedList<VehiclePropValue> mEvents = new LinkedList<VehiclePropValue>();
+ private AudioManager mAudioManager;
+ private CarAudioManager mCarAudioManager;
+
+ @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_VEC4,
+ VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+ 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+ mAudioFocusPropertyHandler);
+ getVehicleHalEmulator().addStaticProperty(
+ VehiclePropConfigUtil.createStaticStringProperty(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT),
+ VehiclePropValueUtil.createIntValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT, 1, 0));
+ getVehicleHalEmulator().addProperty(
+ VehiclePropConfigUtil.getBuilder(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT,
+ VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE,
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+ VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4,
+ VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+ 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).
+ setConfigString(EXT_ROUTING_CONFIG).build(),
+ mExtRoutingHintPropertyHandler);
+ getVehicleHalEmulator().start();
+ mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
+ assertNotNull(mCarAudioManager);
+ }
+
+ public void testExtRoutings() throws Exception {
+ String[] radioTypes = mCarAudioManager.getSupportedRadioTypes();
+ assertNotNull(radioTypes);
+ checkStringArrayContents(new String[] {"RADIO_AM_FM", "RADIO_SATELLITE"}, radioTypes);
+
+ String[] nonRadioTypes = mCarAudioManager.getSupportedExternalSourceTypes();
+ assertNotNull(nonRadioTypes);
+ checkStringArrayContents(new String[] {"CD_DVD", "com.google.test.SOMETHING_SPECIAL",
+ "EXT_NAV_GUIDANCE", "AUX_IN0"}, nonRadioTypes);
+ }
+
+ private void checkStringArrayContents(String[] expected, String[] actual) throws Exception {
+ Arrays.sort(expected);
+ Arrays.sort(actual);
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ public void testRadioAttributeCreation() throws Exception {
+ AudioAttributes attrb = mCarAudioManager.getAudioAttributesForRadio(
+ CarAudioManager.CAR_RADIO_TYPE_AM_FM);
+ assertNotNull(attrb);
+
+ attrb = mCarAudioManager.getAudioAttributesForRadio(
+ CarAudioManager.CAR_RADIO_TYPE_SATELLITE);
+ assertNotNull(attrb);
+
+ try {
+ attrb = mCarAudioManager.getAudioAttributesForRadio(
+ CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ public void testExtSourceAttributeCreation() throws Exception {
+ AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_CD_DVD);
+ assertNotNull(attrb);
+
+ attrb = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE);
+ assertNotNull(attrb);
+
+ attrb = mCarAudioManager.getAudioAttributesForExternalSource(
+ "com.google.test.SOMETHING_SPECIAL");
+ assertNotNull(attrb);
+
+ try {
+ attrb = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ public void testRadioAmFmGainFocus() throws Exception {
+ AudioAttributes attrb = mCarAudioManager.getAudioAttributesForRadio(
+ CarAudioManager.CAR_RADIO_TYPE_AM_FM);
+ assertNotNull(attrb);
+ checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {1, 0, 0, 0},
+ 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG);
+ }
+
+ public void testRadioSatelliteGainFocus() throws Exception {
+ AudioAttributes attrb = mCarAudioManager.getAudioAttributesForRadio(
+ CarAudioManager.CAR_RADIO_TYPE_SATELLITE);
+ assertNotNull(attrb);
+ checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {2, 0, 0, 0},
+ 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG);
+ }
+
+ public void testCdGainFocus() throws Exception {
+ AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_CD_DVD);
+ assertNotNull(attrb);
+ checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {0, 2, 0, 0},
+ 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG);
+ }
+
+ public void testAuxInFocus() throws Exception {
+ AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0);
+ assertNotNull(attrb);
+ checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {0x1<<5, 0, 0, 0},
+ 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG);
+ }
+
+ public void testExtNavInFocus() throws Exception {
+ AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE);
+ assertNotNull(attrb);
+ checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {0x1<<4, 0, 0, 0},
+ 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG);
+ }
+
+ public void testCustomInFocus() throws Exception {
+ AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource(
+ "com.google.test.SOMETHING_SPECIAL");
+ assertNotNull(attrb);
+ checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {0, 0, 1, 0},
+ 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG);
+ }
+
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG |
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(0, request[2]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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);
+ mAudioManager.abandonAudioFocus(listenerMusic);
+ //TODO how to check this?
+ }
+
+ public void testExternalRadioExternalNav() throws Exception {
+ // android radio
+ AudioFocusListener listenerRadio = new AudioFocusListener();
+ CarAudioManager carAudioManager = (CarAudioManager) getCar().getCarManager(
+ Car.AUDIO_SERVICE);
+ assertNotNull(carAudioManager);
+ AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(
+ CarAudioManager.CAR_AUDIO_USAGE_RADIO);
+ int res = mAudioManager.requestAudioFocus(listenerRadio,
+ radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ int[] 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]);
+ assertArrayEquals(new int[] {1, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+ //external nav
+ AudioFocusListener listenerNav = new AudioFocusListener();
+ AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE);
+ res = mAudioManager.requestAudioFocus(listenerNav,
+ extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG |
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG, request[3]);
+ assertArrayEquals(new int[] {1 | 1<<4, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]);
+ assertArrayEquals(new int[] {1, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+ mAudioManager.abandonAudioFocus(listenerRadio);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(0, request[2]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ 0,
+ 0);
+ }
+
+ public void testMediaExternalNav() 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ //external nav
+ AudioFocusListener listenerNav = new AudioFocusListener();
+ AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE);
+ res = mAudioManager.requestAudioFocus(listenerNav,
+ extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN,
+ request[0]);
+ assertEquals(0x1, request[1]);
+ assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ request[2]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG |
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG, request[3]);
+ assertArrayEquals(new int[] {1<<4, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0x1,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ 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]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ 0,
+ 0);
+ }
+
+ /**
+ * Test internal nav - external nav case.
+ * External nav takes the same physical stream as internal nav. So internal nav
+ * will be lost while external nav is played. This should not happen in real case when
+ * AppFocus is used, but this test is to make sure that audio focus works as expected.
+ */
+ public void testNavExternalNav() throws Exception {
+ // android nav
+ AudioFocusListener listenerIntNav = new AudioFocusListener();
+ AudioAttributes intNavAttributes = mCarAudioManager.getAudioAttributesForCarUsage(
+ CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE);
+ int res = mAudioManager.requestAudioFocus(listenerIntNav, intNavAttributes,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK,
+ request[0]);
+ assertEquals(0x2, request[1]);
+ assertEquals(0, request[2]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ //external nav
+ AudioFocusListener listenerExtNav = new AudioFocusListener();
+ AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource(
+ CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE);
+ res = mAudioManager.requestAudioFocus(listenerExtNav,
+ extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG, request[3]);
+ assertArrayEquals(new int[] {1<<4, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0x1,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+ mAudioManager.abandonAudioFocus(listenerExtNav);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK,
+ request[0]);
+ assertEquals(0x2, request[1]);
+ assertEquals(0, request[2]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ mAudioManager.abandonAudioFocus(listenerIntNav);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(0, request[2]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ // android radio
+ AudioFocusListener listenerRadio = new AudioFocusListener();
+ CarAudioManager carAudioManager = (CarAudioManager) getCar().getCarManager(
+ Car.AUDIO_SERVICE);
+ assertNotNull(carAudioManager);
+ AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(
+ CarAudioManager.CAR_AUDIO_USAGE_RADIO);
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]);
+ assertArrayEquals(new int[] {1, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG |
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]);
+ assertArrayEquals(new int[] {1, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]);
+ assertArrayEquals(new int[] {1, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+ }
+
+ private void checkSingleRequestRelease(AudioAttributes attrb, int androidFocusToRequest,
+ int[] expectedExtRouting, int expectedStreams,
+ int expectedExtState, int expectedContexts) throws Exception {
+ AudioFocusListener lister = new AudioFocusListener();
+ int res = mCarAudioManager.requestAudioFocus(lister, attrb, androidFocusToRequest, 0);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ int expectedFocusRequest = VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
+ int response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+ switch (androidFocusToRequest) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ expectedFocusRequest = VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
+ response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+ expectedFocusRequest =
+ VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
+ response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+ expectedFocusRequest =
+ VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
+ response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT;
+ break;
+ }
+ assertEquals(expectedFocusRequest, request[0]);
+ assertEquals(expectedStreams, request[1]);
+ assertEquals(expectedExtState, request[2]);
+ assertEquals(expectedContexts, request[3]);
+ assertArrayEquals(expectedExtRouting, mExtRoutingHintPropertyHandler.getLastHint());
+ 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]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+ }
+
+ public void testRadioMute() throws Exception {
+ testMediaMute(CarAudioManager.CAR_AUDIO_USAGE_RADIO,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG);
+ }
+
+ public void testMusicMute() throws Exception {
+ testMediaMute(CarAudioManager.CAR_AUDIO_USAGE_MUSIC,
+ 0x1,
+ 0,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG);
+ }
+
+ private void testMediaMute(int mediaUsage, int primaryStream, int extFocusFlag,
+ int mediaContext) throws Exception {
+ // android radio
+ AudioFocusListener listenerMedia = new AudioFocusListener();
+ CarAudioManager carAudioManager = (CarAudioManager) getCar().getCarManager(
+ Car.AUDIO_SERVICE);
+ assertNotNull(carAudioManager);
+ AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(mediaUsage);
+ Log.i(TAG, "request media Focus");
+ int res = mAudioManager.requestAudioFocus(listenerMedia,
+ radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(primaryStream, request[1]);
+ assertEquals(extFocusFlag, request[2]);
+ assertEquals(mediaContext, request[3]);
+ if (mediaUsage == CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
+ assertArrayEquals(new int[] {1, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ } else {
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ }
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ primaryStream,
+ extFocusFlag);
+ // now mute it.
+ assertFalse(carAudioManager.isMediaMuted());
+ Log.i(TAG, "mute media");
+ assertTrue(carAudioManager.setMediaMute(true));
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT,
+ request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG,
+ request[2]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG);
+ assertTrue(carAudioManager.isMediaMuted());
+ // nav guidance on top of it
+ AudioFocusListener listenerNav = new AudioFocusListener();
+ AudioAttributes navAttrib = (new AudioAttributes.Builder()).
+ setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
+ setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
+ build();
+ Log.i(TAG, "request nav Focus");
+ 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]);
+ assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG,
+ request[2]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ assertTrue(carAudioManager.isMediaMuted());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG);
+ assertTrue(carAudioManager.isMediaMuted());
+ // nav guidance ends
+ mAudioManager.abandonAudioFocus(listenerNav);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT,
+ request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG,
+ request[2]);
+ assertEquals(0, request[3]);
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ assertTrue(carAudioManager.isMediaMuted());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG);
+ // now unmute it. media should resume.
+ assertTrue(carAudioManager.isMediaMuted());
+ assertFalse(carAudioManager.setMediaMute(false));
+ assertFalse(carAudioManager.isMediaMuted());
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(primaryStream, request[1]);
+ assertEquals(extFocusFlag,
+ request[2]);
+ assertEquals(mediaContext, request[3]);
+ if (mediaUsage == CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
+ assertArrayEquals(new int[] {1, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ } else {
+ assertArrayEquals(new int[] {0, 0, 0, 0},
+ mExtRoutingHintPropertyHandler.getLastHint());
+ }
+ assertFalse(carAudioManager.isMediaMuted());
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ primaryStream,
+ extFocusFlag);
+ assertFalse(carAudioManager.isMediaMuted());
+ // release focus
+ mAudioManager.abandonAudioFocus(listenerMedia);
+ }
+
+ protected static 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();
+ }
+ }
+
+ protected static 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 int mRequestedAudioContexts;
+ private final MockedCarTestBase mCarTest;
+
+ private final Semaphore mSetWaitSemaphore = new Semaphore(0);
+
+ public FocusPropertyHandler(MockedCarTestBase carTest) {
+ mCarTest = carTest;
+ }
+
+ public void sendAudioFocusState(int state, int streams, int extFocus) {
+ synchronized (this) {
+ mState = state;
+ mStreams = streams;
+ mExtFocus = extFocus;
+ }
+ int[] values = { state, streams, extFocus, 0 };
+ mCarTest.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,
+ mRequestedAudioContexts };
+ }
+ }
+
+ @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);
+ mRequestedAudioContexts = value.getInt32Values(
+ VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_AUDIO_CONTEXTS);
+ }
+ 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, 0 };
+ return VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+ SystemClock.elapsedRealtimeNanos());
+ }
+
+ @Override
+ public void onPropertySubscribe(int property, float sampleRate, int zones) {
+ assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, property);
+ }
+
+ @Override
+ public void onPropertyUnsubscribe(int property) {
+ assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, property);
+ }
+ }
+
+ private static class ExtRoutingHintPropertyHandler implements VehicleHalPropertyHandler {
+ private int[] mLastHint = {0, 0, 0, 0};
+
+ public int[] getLastHint() {
+ int[] lastHint = new int[mLastHint.length];
+ synchronized (this) {
+ System.arraycopy(mLastHint, 0, lastHint, 0, mLastHint.length);
+ }
+ return lastHint;
+ }
+
+ @Override
+ public void onPropertySet(VehiclePropValue value) {
+ assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT,
+ value.getProp());
+ assertEquals(mLastHint.length, value.getInt32ValuesCount());
+ synchronized (this) {
+ for (int i = 0; i < mLastHint.length; i++) {
+ mLastHint[i] = value.getInt32Values(i);
+ }
+ }
+ }
+
+ @Override
+ public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ fail("write only");
+ return null;
+ }
+
+ @Override
+ public void onPropertySubscribe(int property, float sampleRate, int zones) {
+ fail("cannot subsctibe");
+ }
+
+ @Override
+ public void onPropertyUnsubscribe(int property) {
+ fail("cannot subsctibe");
+ }
+ }
+}
diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java
index 5a978fd489..df1db9a128 100644
--- a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java
@@ -77,34 +77,6 @@ public class CarAudioFocusTest extends MockedCarTestBase {
private final FocusPropertyHandler mAudioFocusPropertyHandler =
new FocusPropertyHandler(this);
- 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, float sampleRate, int zones) {
- // 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;
diff --git a/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java
index 9c8ef3d7fb..adfc3f355a 100644
--- a/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java
@@ -26,6 +26,7 @@ import android.support.car.ServiceConnectionListener;
import android.test.AndroidTestCase;
import android.util.Log;
+import java.util.Arrays;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -68,6 +69,20 @@ public class MockedCarTestBase extends AndroidTestCase {
}
};
+ public static <T> void assertArrayEquals(T[] expected, T[] actual) {
+ if (!Arrays.equals(expected, actual)) {
+ fail("expected:<" + Arrays.toString(expected) +
+ "> but was:<" + Arrays.toString(actual) + ">");
+ }
+ }
+
+ public static void assertArrayEquals(int[] expected, int[] actual) {
+ if (!Arrays.equals(expected, actual)) {
+ fail("expected:<" + Arrays.toString(expected) +
+ "> but was:<" + Arrays.toString(actual) + ">");
+ }
+ }
+
@Override
protected synchronized void setUp() throws Exception {
super.setUp();
diff --git a/vns_policy/vns_policy.xml b/vns_policy/vns_policy.xml
index 244695651f..c20ac664fe 100644
--- a/vns_policy/vns_policy.xml
+++ b/vns_policy/vns_policy.xml
@@ -146,6 +146,10 @@
<UID name="AID_SYSTEM" access="r" value="1000"/>
</PROPERTY>
+ <PROPERTY name="VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT" value = "0x00000905">
+ <UID name="AID_SYSTEM" access="rw" value="1000"/>
+ </PROPERTY>
+
<PROPERTY name="VEHICLE_PROPERTY_AP_POWER_STATE" value = "0x00000A00">
<UID name="AID_SYSTEM" access="rw" value="1000"/>
</PROPERTY>