diff options
author | Keun-young Park <keunyoung@google.com> | 2016-06-28 12:58:23 -0700 |
---|---|---|
committer | Keun-young Park <keunyoung@google.com> | 2016-07-08 10:17:55 -0700 |
commit | 4c6834a27ccbcf42cbeef43059751752baf80ac3 (patch) | |
tree | cd0af929cb33f6725f2f1b06327e6e684229331f | |
parent | 8739715585b9c3c7aa685bc732dec6a484825c22 (diff) | |
download | Car-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
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> |