1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.hal; 17 18 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_EXT_ROUTING_HINT; 19 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_FOCUS; 20 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_HW_VARIANT; 21 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_PARAMETERS; 22 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_ROUTING_POLICY; 23 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_STREAM_STATE; 24 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_VOLUME; 25 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_VOLUME_LIMIT; 26 import static com.android.car.CarServiceUtils.toIntArray; 27 28 import android.annotation.Nullable; 29 import android.car.VehicleZoneUtil; 30 import android.car.media.CarAudioManager; 31 import android.car.media.CarAudioManager.OnParameterChangeListener; 32 import android.hardware.automotive.vehicle.V2_0.SubscribeFlags; 33 import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag; 34 import android.hardware.automotive.vehicle.V2_0.VehicleAudioExtFocusFlag; 35 import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusIndex; 36 import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusRequest; 37 import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusState; 38 import android.hardware.automotive.vehicle.V2_0.VehicleAudioHwVariantConfigFlag; 39 import android.hardware.automotive.vehicle.V2_0.VehicleAudioRoutingPolicyIndex; 40 import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeCapabilityFlag; 41 import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeIndex; 42 import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeLimitIndex; 43 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; 44 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 45 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 46 import android.text.TextUtils; 47 import android.util.Log; 48 49 import com.android.car.AudioRoutingPolicy; 50 import com.android.car.CarAudioAttributesUtil; 51 import com.android.car.CarLog; 52 53 import java.io.PrintWriter; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.Collection; 57 import java.util.HashMap; 58 import java.util.List; 59 import java.util.Map; 60 61 public class AudioHalService extends HalServiceBase { 62 public static final int VEHICLE_AUDIO_FOCUS_REQUEST_INVALID = -1; 63 public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN = 64 VehicleAudioFocusRequest.REQUEST_GAIN; 65 public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT = 66 VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT; 67 public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK = 68 VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK; 69 public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK = 70 VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_NO_DUCK; 71 public static final int VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE = 72 VehicleAudioFocusRequest.REQUEST_RELEASE; 73 74 public static final int VEHICLE_AUDIO_FOCUS_STATE_INVALID = -1; 75 public static final int VEHICLE_AUDIO_FOCUS_STATE_GAIN = 76 VehicleAudioFocusState.STATE_GAIN; 77 public static final int VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT = 78 VehicleAudioFocusState.STATE_GAIN_TRANSIENT; 79 public static final int VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK = 80 VehicleAudioFocusState.STATE_LOSS_TRANSIENT_CAN_DUCK; 81 public static final int VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT = 82 VehicleAudioFocusState.STATE_LOSS_TRANSIENT; 83 public static final int VEHICLE_AUDIO_FOCUS_STATE_LOSS = 84 VehicleAudioFocusState.STATE_LOSS; 85 public static final int VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE = 86 VehicleAudioFocusState.STATE_LOSS_TRANSIENT_EXLCUSIVE; 87 88 public static final int VEHICLE_AUDIO_STREAM_STATE_STOPPED = 0; 89 public static final int VEHICLE_AUDIO_STREAM_STATE_STARTED = 1; 90 91 public static final int VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG = 92 VehicleAudioExtFocusFlag.NONE_FLAG; 93 public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG = 94 VehicleAudioExtFocusFlag.PERMANENT_FLAG; 95 public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG = 96 VehicleAudioExtFocusFlag.TRANSIENT_FLAG; 97 public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG = 98 VehicleAudioExtFocusFlag.PLAY_ONLY_FLAG; 99 public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG = 100 VehicleAudioExtFocusFlag.MUTE_MEDIA_FLAG; 101 102 public static final int STREAM_NUM_DEFAULT = 0; 103 104 public static final int FOCUS_STATE_ARRAY_INDEX_STATE = 105 VehicleAudioFocusIndex.FOCUS; 106 public static final int FOCUS_STATE_ARRAY_INDEX_STREAMS = 107 VehicleAudioFocusIndex.STREAMS; 108 public static final int FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS = 109 VehicleAudioFocusIndex.EXTERNAL_FOCUS_STATE; 110 111 public static final int AUDIO_CONTEXT_MUSIC_FLAG = 112 VehicleAudioContextFlag.MUSIC_FLAG; 113 public static final int AUDIO_CONTEXT_NAVIGATION_FLAG = 114 VehicleAudioContextFlag.NAVIGATION_FLAG; 115 public static final int AUDIO_CONTEXT_VOICE_COMMAND_FLAG = 116 VehicleAudioContextFlag.VOICE_COMMAND_FLAG; 117 public static final int AUDIO_CONTEXT_CALL_FLAG = 118 VehicleAudioContextFlag.CALL_FLAG; 119 public static final int AUDIO_CONTEXT_ALARM_FLAG = 120 VehicleAudioContextFlag.ALARM_FLAG; 121 public static final int AUDIO_CONTEXT_NOTIFICATION_FLAG = 122 VehicleAudioContextFlag.NOTIFICATION_FLAG; 123 public static final int AUDIO_CONTEXT_UNKNOWN_FLAG = 124 VehicleAudioContextFlag.UNKNOWN_FLAG; 125 public static final int AUDIO_CONTEXT_SAFETY_ALERT_FLAG = 126 VehicleAudioContextFlag.SAFETY_ALERT_FLAG; 127 public static final int AUDIO_CONTEXT_RADIO_FLAG = 128 VehicleAudioContextFlag.RADIO_FLAG; 129 public static final int AUDIO_CONTEXT_CD_ROM_FLAG = 130 VehicleAudioContextFlag.CD_ROM_FLAG; 131 public static final int AUDIO_CONTEXT_AUX_AUDIO_FLAG = 132 VehicleAudioContextFlag.AUX_AUDIO_FLAG; 133 public static final int AUDIO_CONTEXT_SYSTEM_SOUND_FLAG = 134 VehicleAudioContextFlag.SYSTEM_SOUND_FLAG; 135 public static final int AUDIO_CONTEXT_EXT_SOURCE_FLAG = 136 VehicleAudioContextFlag.EXT_SOURCE_FLAG; 137 public static final int AUDIO_CONTEXT_RINGTONE_FLAG = 138 VehicleAudioContextFlag.RINGTONE_FLAG; 139 140 public interface AudioHalFocusListener { 141 /** 142 * Audio focus change from car. 143 * @param focusState 144 * @param streams 145 * @param externalFocus Flags of active external audio focus. 146 * 0 means no external audio focus. 147 */ 148 void onFocusChange(int focusState, int streams, int externalFocus); 149 /** 150 * Stream state change (start / stop) from android 151 * @param streamNumber stream number like 0, 1, ... 152 * @param streamActive Whether the stream is active or not. 153 */ 154 void onStreamStatusChange(int streamNumber, boolean streamActive); 155 } 156 157 public interface AudioHalVolumeListener { 158 /** 159 * Audio volume change from car. 160 * @param streamNumber 161 * @param volume 162 * @param volumeState 163 */ 164 void onVolumeChange(int streamNumber, int volume, int volumeState); 165 /** 166 * Volume limit change from car. 167 * @param streamNumber 168 * @param volume 169 */ 170 void onVolumeLimitChange(int streamNumber, int volume); 171 } 172 173 private static final boolean DBG = false; 174 175 private final VehicleHal mVehicleHal; 176 private AudioHalFocusListener mFocusListener; 177 private AudioHalVolumeListener mVolumeListener; 178 private int mVariant; 179 180 private final HashMap<Integer, VehiclePropConfig> mProperties = new HashMap<>(); 181 182 private OnParameterChangeListener mOnParameterChangeListener; 183 184 public AudioHalService(VehicleHal vehicleHal) { 185 mVehicleHal = vehicleHal; 186 } 187 188 public synchronized void setFocusListener(AudioHalFocusListener focusListener) { 189 mFocusListener = focusListener; 190 } 191 192 public synchronized void setVolumeListener(AudioHalVolumeListener volumeListener) { 193 mVolumeListener = volumeListener; 194 } 195 196 public void setAudioRoutingPolicy(AudioRoutingPolicy policy) { 197 if (!mVehicleHal.isPropertySupported(VehicleProperty.AUDIO_ROUTING_POLICY)) { 198 Log.w(CarLog.TAG_AUDIO, 199 "Vehicle HAL did not implement VehicleProperty.AUDIO_ROUTING_POLICY"); 200 return; 201 } 202 int[] policyToSet = new int[2]; 203 for (int i = 0; i < policy.getPhysicalStreamsCount(); i++) { 204 policyToSet[VehicleAudioRoutingPolicyIndex.STREAM] = i; 205 int contexts = 0; 206 for (int logicalStream : policy.getLogicalStreamsForPhysicalStream(i)) { 207 contexts |= logicalStreamToHalContextType(logicalStream); 208 } 209 policyToSet[VehicleAudioRoutingPolicyIndex.CONTEXTS] = contexts; 210 try { 211 mVehicleHal.set(AUDIO_ROUTING_POLICY).to(policyToSet); 212 } catch (PropertyTimeoutException e) { 213 Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_ROUTING_POLICY", e); 214 } 215 } 216 } 217 218 /** 219 * Returns the volume limits of a stream. Returns null if max value wasn't defined for 220 * AUDIO_VOLUME property. 221 */ 222 @Nullable 223 public synchronized Integer getStreamMaxVolume(int stream) { 224 VehiclePropConfig config = mProperties.get(VehicleProperty.AUDIO_VOLUME); 225 if (config == null) { 226 throw new IllegalStateException("VehicleProperty.AUDIO_VOLUME not supported"); 227 } 228 int supportedContext = getSupportedAudioVolumeContexts(); 229 230 int MAX_VALUES_FIRST_ELEMENT_INDEX = 4; 231 ArrayList<Integer> maxValues = new ArrayList<>(); 232 for (int i = MAX_VALUES_FIRST_ELEMENT_INDEX; i < config.configArray.size(); i++) { 233 maxValues.add(config.configArray.get(i)); 234 } 235 236 Integer result = null; 237 if (supportedContext != 0) { 238 int index = VehicleZoneUtil.zoneToIndex(supportedContext, stream); 239 if (index < maxValues.size()) { 240 result = maxValues.get(index); 241 } 242 } else { 243 if (stream < maxValues.size()) { 244 result = maxValues.get(stream); 245 } 246 } 247 248 if (result == null) { 249 Log.e(CarLog.TAG_AUDIO, "No min/max volume found in vehicle" + 250 " prop config for stream: " + stream); 251 } 252 253 return result; 254 } 255 256 /** 257 * Convert car audio manager stream type (usage) into audio context type. 258 */ 259 public static int logicalStreamToHalContextType(int logicalStream) { 260 return logicalStreamWithExtTypeToHalContextType(logicalStream, null); 261 } 262 263 public static int logicalStreamWithExtTypeToHalContextType(int logicalStream, String extType) { 264 switch (logicalStream) { 265 case CarAudioManager.CAR_AUDIO_USAGE_RADIO: 266 return VehicleAudioContextFlag.RADIO_FLAG; 267 case CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL: 268 return VehicleAudioContextFlag.CALL_FLAG; 269 case CarAudioManager.CAR_AUDIO_USAGE_RINGTONE: 270 return VehicleAudioContextFlag.RINGTONE_FLAG; 271 case CarAudioManager.CAR_AUDIO_USAGE_MUSIC: 272 return VehicleAudioContextFlag.MUSIC_FLAG; 273 case CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE: 274 return VehicleAudioContextFlag.NAVIGATION_FLAG; 275 case CarAudioManager.CAR_AUDIO_USAGE_VOICE_COMMAND: 276 return VehicleAudioContextFlag.VOICE_COMMAND_FLAG; 277 case CarAudioManager.CAR_AUDIO_USAGE_ALARM: 278 return VehicleAudioContextFlag.ALARM_FLAG; 279 case CarAudioManager.CAR_AUDIO_USAGE_NOTIFICATION: 280 return VehicleAudioContextFlag.NOTIFICATION_FLAG; 281 case CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT: 282 return VehicleAudioContextFlag.SAFETY_ALERT_FLAG; 283 case CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND: 284 return VehicleAudioContextFlag.SYSTEM_SOUND_FLAG; 285 case CarAudioManager.CAR_AUDIO_USAGE_DEFAULT: 286 return VehicleAudioContextFlag.UNKNOWN_FLAG; 287 case CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE: 288 if (extType != null) { 289 switch (extType) { 290 case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_CD_DVD: 291 return VehicleAudioContextFlag.CD_ROM_FLAG; 292 case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0: 293 case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN1: 294 return VehicleAudioContextFlag.AUX_AUDIO_FLAG; 295 default: 296 if (extType.startsWith("RADIO_")) { 297 return VehicleAudioContextFlag.RADIO_FLAG; 298 } else { 299 return VehicleAudioContextFlag.EXT_SOURCE_FLAG; 300 } 301 } 302 } else { // no external source specified. fall back to radio 303 return VehicleAudioContextFlag.RADIO_FLAG; 304 } 305 case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM: 306 case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY: 307 case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE: 308 // internal tag not associated with any stream 309 return 0; 310 default: 311 Log.w(CarLog.TAG_AUDIO, "Unknown logical stream:" + logicalStream); 312 return 0; 313 } 314 } 315 316 /** 317 * Converts car audio context type to car stream usage. 318 */ 319 public static int carContextToCarUsage(int carContext) { 320 switch (carContext) { 321 case VehicleAudioContextFlag.MUSIC_FLAG: 322 return CarAudioManager.CAR_AUDIO_USAGE_MUSIC; 323 case VehicleAudioContextFlag.NAVIGATION_FLAG: 324 return CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE; 325 case VehicleAudioContextFlag.ALARM_FLAG: 326 return CarAudioManager.CAR_AUDIO_USAGE_ALARM; 327 case VehicleAudioContextFlag.VOICE_COMMAND_FLAG: 328 return CarAudioManager.CAR_AUDIO_USAGE_VOICE_COMMAND; 329 case VehicleAudioContextFlag.AUX_AUDIO_FLAG: 330 return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE; 331 case VehicleAudioContextFlag.CALL_FLAG: 332 return CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL; 333 case VehicleAudioContextFlag.RINGTONE_FLAG: 334 return CarAudioManager.CAR_AUDIO_USAGE_RINGTONE; 335 case VehicleAudioContextFlag.CD_ROM_FLAG: 336 return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE; 337 case VehicleAudioContextFlag.NOTIFICATION_FLAG: 338 return CarAudioManager.CAR_AUDIO_USAGE_NOTIFICATION; 339 case VehicleAudioContextFlag.RADIO_FLAG: 340 return CarAudioManager.CAR_AUDIO_USAGE_RADIO; 341 case VehicleAudioContextFlag.SAFETY_ALERT_FLAG: 342 return CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT; 343 case VehicleAudioContextFlag.SYSTEM_SOUND_FLAG: 344 return CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND; 345 case VehicleAudioContextFlag.UNKNOWN_FLAG: 346 return CarAudioManager.CAR_AUDIO_USAGE_DEFAULT; 347 case VehicleAudioContextFlag.EXT_SOURCE_FLAG: 348 return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE; 349 default: 350 Log.w(CarLog.TAG_AUDIO, "Unknown car context:" + carContext); 351 return 0; 352 } 353 } 354 355 public void requestAudioFocusChange(int request, int streams, int audioContexts) { 356 requestAudioFocusChange(request, streams, VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG, audioContexts); 357 } 358 359 public void requestAudioFocusChange(int request, int streams, int extFocus, int audioContexts) { 360 int[] payload = { request, streams, extFocus, audioContexts }; 361 try { 362 mVehicleHal.set(AUDIO_FOCUS).to(payload); 363 } catch (PropertyTimeoutException e) { 364 Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_FOCUS", e); 365 // focus timeout will reset it anyway 366 } 367 } 368 369 public void setStreamVolume(int streamType, int index) { 370 int[] payload = {streamType, index, 0}; 371 try { 372 mVehicleHal.set(VehicleProperty.AUDIO_VOLUME).to(payload); 373 } catch (PropertyTimeoutException e) { 374 Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_VOLUME", e); 375 //TODO should reset volume, bug: 32096870 376 } 377 } 378 379 public int getStreamVolume(int stream) { 380 int[] volume = {stream, 0, 0}; 381 VehiclePropValue requestedStreamVolume = new VehiclePropValue(); 382 requestedStreamVolume.prop = VehicleProperty.AUDIO_VOLUME; 383 requestedStreamVolume.value.int32Values.addAll(Arrays.asList(stream, 0 , 0)); 384 VehiclePropValue propValue; 385 try { 386 propValue = mVehicleHal.get(requestedStreamVolume); 387 } catch (PropertyTimeoutException e) { 388 Log.e(CarLog.TAG_AUDIO, "VehicleProperty.AUDIO_VOLUME not ready", e); 389 return 0; 390 } 391 392 if (propValue.value.int32Values.size() != 3) { 393 Log.e(CarLog.TAG_AUDIO, "returned value not valid"); 394 throw new IllegalStateException("Invalid preset returned from service: " 395 + Arrays.toString(propValue.value.int32Values.toArray())); 396 } 397 398 int retStreamNum = propValue.value.int32Values.get(0); 399 int retVolume = propValue.value.int32Values.get(1); 400 int retVolumeState = propValue.value.int32Values.get(2); 401 402 if (retStreamNum != stream) { 403 Log.e(CarLog.TAG_AUDIO, "Stream number is not the same: " 404 + stream + " vs " + retStreamNum); 405 throw new IllegalStateException("Stream number is not the same"); 406 } 407 return retVolume; 408 } 409 410 public synchronized int getHwVariant() { 411 return mVariant; 412 } 413 414 public synchronized boolean isRadioExternal() { 415 VehiclePropConfig config = mProperties.get(VehicleProperty.AUDIO_HW_VARIANT); 416 if (config == null) { 417 return true; 418 } 419 return (config.configArray.get(0) 420 & VehicleAudioHwVariantConfigFlag.INTERNAL_RADIO_FLAG) == 0; 421 } 422 423 public synchronized boolean isFocusSupported() { 424 return isPropertySupportedLocked(AUDIO_FOCUS); 425 } 426 427 public synchronized boolean isAudioVolumeSupported() { 428 return isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME); 429 } 430 431 public synchronized int getSupportedAudioVolumeContexts() { 432 if (!isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME)) { 433 throw new IllegalStateException("VehicleProperty.AUDIO_VOLUME not supported"); 434 } 435 VehiclePropConfig config = mProperties.get(VehicleProperty.AUDIO_VOLUME); 436 return config.configArray.get(0); 437 } 438 439 /** 440 * Whether external audio module can memorize logical audio volumes or not. 441 * @return 442 */ 443 public synchronized boolean isExternalAudioVolumePersistent() { 444 if (!isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME)) { 445 throw new IllegalStateException("VehicleProperty.AUDIO_VOLUME not supported"); 446 } 447 VehiclePropConfig config = mProperties.get(VehicleProperty.AUDIO_VOLUME); 448 if (config.configArray.get(0) == 0) { // physical streams only 449 return false; 450 } 451 if ((config.configArray.get(1) 452 & VehicleAudioVolumeCapabilityFlag.PERSISTENT_STORAGE) != 0) { 453 return true; 454 } 455 return false; 456 } 457 458 public synchronized boolean isAudioVolumeLimitSupported() { 459 return isPropertySupportedLocked(AUDIO_VOLUME_LIMIT); 460 } 461 462 public synchronized boolean isAudioVolumeMasterOnly() { 463 if (!isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME)) { 464 throw new IllegalStateException("VehicleProperty.AUDIO_VOLUME not supported"); 465 } 466 VehiclePropConfig config = mProperties.get( 467 AUDIO_VOLUME); 468 if ((config.configArray.get(1) & 469 VehicleAudioVolumeCapabilityFlag.MASTER_VOLUME_ONLY) 470 != 0) { 471 return true; 472 } 473 return false; 474 } 475 476 /** 477 * Get the current audio focus state. 478 * @return 0: focusState, 1: streams, 2: externalFocus 479 */ 480 public int[] getCurrentFocusState() { 481 if (!isFocusSupported()) { 482 return new int[] { VEHICLE_AUDIO_FOCUS_STATE_GAIN, 0xffffffff, 0}; 483 } 484 try { 485 VehiclePropValue propValue = mVehicleHal.get(VehicleProperty.AUDIO_FOCUS); 486 return toIntArray(propValue.value.int32Values); 487 } catch (PropertyTimeoutException e) { 488 Log.e(CarLog.TAG_AUDIO, "VehicleProperty.AUDIO_FOCUS not ready", e); 489 return new int[] { VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0x0, 0}; 490 } 491 } 492 493 public static class ExtRoutingSourceInfo { 494 /** Represents an external route which will not disable any physical stream in android side. 495 */ 496 public static final int NO_DISABLED_PHYSICAL_STREAM = -1; 497 498 /** Bit position of this source in vhal */ 499 public final int bitPosition; 500 /** 501 * Physical stream replaced by this routing. will be {@link #NO_DISABLED_PHYSICAL_STREAM} 502 * if no physical stream for android is replaced by this routing. 503 */ 504 public final int physicalStreamNumber; 505 506 public ExtRoutingSourceInfo(int bitPosition, int physycalStreamNumber) { 507 this.bitPosition = bitPosition; 508 this.physicalStreamNumber = physycalStreamNumber; 509 } 510 511 @Override 512 public String toString() { 513 return "[bitPosition=" + bitPosition + ", physicalStreamNumber=" 514 + physicalStreamNumber + "]"; 515 } 516 } 517 518 /** 519 * Get external audio routing types from AUDIO_EXT_ROUTING_HINT property. 520 * 521 * @return null if AUDIO_EXT_ROUTING_HINT is not supported. 522 */ 523 public Map<String, ExtRoutingSourceInfo> getExternalAudioRoutingTypes() { 524 VehiclePropConfig config; 525 synchronized (this) { 526 if (!isPropertySupportedLocked(AUDIO_EXT_ROUTING_HINT)) { 527 if (DBG) { 528 Log.i(CarLog.TAG_AUDIO, "AUDIO_EXT_ROUTING_HINT is not supported"); 529 } 530 return null; 531 } 532 config = mProperties.get(AUDIO_EXT_ROUTING_HINT); 533 } 534 if (TextUtils.isEmpty(config.configString)) { 535 Log.w(CarLog.TAG_AUDIO, "AUDIO_EXT_ROUTING_HINT with empty config string"); 536 return null; 537 } 538 Map<String, ExtRoutingSourceInfo> routingTypes = new HashMap<>(); 539 String configString = config.configString; 540 if (DBG) { 541 Log.i(CarLog.TAG_AUDIO, "AUDIO_EXT_ROUTING_HINT config string:" + configString); 542 } 543 String[] routes = configString.split(","); 544 for (String routeString : routes) { 545 String[] tokens = routeString.split(":"); 546 int bitPosition = 0; 547 String name = null; 548 int physicalStreamNumber = ExtRoutingSourceInfo.NO_DISABLED_PHYSICAL_STREAM; 549 if (tokens.length == 2) { 550 bitPosition = Integer.parseInt(tokens[0]); 551 name = tokens[1]; 552 } else if (tokens.length == 3) { 553 bitPosition = Integer.parseInt(tokens[0]); 554 name = tokens[1]; 555 physicalStreamNumber = Integer.parseInt(tokens[2]); 556 } else { 557 Log.w(CarLog.TAG_AUDIO, "AUDIO_EXT_ROUTING_HINT has wrong entry:" + 558 routeString); 559 continue; 560 } 561 routingTypes.put(name, new ExtRoutingSourceInfo(bitPosition, physicalStreamNumber)); 562 } 563 return routingTypes; 564 } 565 566 public void setExternalRoutingSource(int[] externalRoutings) { 567 try { 568 mVehicleHal.set(AUDIO_EXT_ROUTING_HINT).to(externalRoutings); 569 } catch (PropertyTimeoutException e) { 570 Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_EXT_ROUTING_HINT", e); 571 } 572 } 573 574 private boolean isPropertySupportedLocked(int property) { 575 VehiclePropConfig config = mProperties.get(property); 576 return config != null; 577 } 578 579 @Override 580 public synchronized void init() { 581 for (VehiclePropConfig config : mProperties.values()) { 582 if (VehicleHal.isPropertySubscribable(config)) { 583 int subsribeFlag = SubscribeFlags.HAL_EVENT; 584 if (AUDIO_STREAM_STATE == config.prop) { 585 subsribeFlag |= SubscribeFlags.SET_CALL; 586 } 587 mVehicleHal.subscribeProperty(this, config.prop, 0, subsribeFlag); 588 } 589 } 590 try { 591 mVariant = mVehicleHal.get(int.class, AUDIO_HW_VARIANT); 592 } catch (IllegalArgumentException e) { 593 // no variant. Set to default, 0. 594 mVariant = 0; 595 } catch (PropertyTimeoutException e) { 596 Log.e(CarLog.TAG_AUDIO, "VehicleProperty.AUDIO_HW_VARIANT not ready", e); 597 mVariant = 0; 598 } 599 } 600 601 @Override 602 public synchronized void release() { 603 for (VehiclePropConfig config : mProperties.values()) { 604 if (VehicleHal.isPropertySubscribable(config)) { 605 mVehicleHal.unsubscribeProperty(this, config.prop); 606 } 607 } 608 mProperties.clear(); 609 } 610 611 @Override 612 public synchronized Collection<VehiclePropConfig> takeSupportedProperties( 613 Collection<VehiclePropConfig> allProperties) { 614 for (VehiclePropConfig p : allProperties) { 615 switch (p.prop) { 616 case VehicleProperty.AUDIO_FOCUS: 617 case VehicleProperty.AUDIO_VOLUME: 618 case VehicleProperty.AUDIO_VOLUME_LIMIT: 619 case VehicleProperty.AUDIO_HW_VARIANT: 620 case VehicleProperty.AUDIO_EXT_ROUTING_HINT: 621 case VehicleProperty.AUDIO_PARAMETERS: 622 case VehicleProperty.AUDIO_STREAM_STATE: 623 mProperties.put(p.prop, p); 624 break; 625 } 626 } 627 return new ArrayList<>(mProperties.values()); 628 } 629 630 @Override 631 public void handleHalEvents(List<VehiclePropValue> values) { 632 AudioHalFocusListener focusListener; 633 AudioHalVolumeListener volumeListener; 634 OnParameterChangeListener parameterListener; 635 synchronized (this) { 636 focusListener = mFocusListener; 637 volumeListener = mVolumeListener; 638 parameterListener = mOnParameterChangeListener; 639 } 640 dispatchEventToListener(focusListener, volumeListener, parameterListener, values); 641 } 642 643 public String[] getAudioParameterKeys() { 644 VehiclePropConfig config; 645 synchronized (this) { 646 if (!isPropertySupportedLocked(AUDIO_PARAMETERS)) { 647 if (DBG) { 648 Log.i(CarLog.TAG_AUDIO, "AUDIO_PARAMETERS is not supported"); 649 } 650 return null; 651 } 652 config = mProperties.get(AUDIO_PARAMETERS); 653 } 654 return config.configString.split(";"); 655 } 656 657 public void setAudioParameters(String parameters) { 658 synchronized (this) { 659 if (!isPropertySupportedLocked(AUDIO_PARAMETERS)) { 660 throw new IllegalStateException("VehicleProperty.AUDIO_PARAMETERS not supported"); 661 } 662 } 663 VehiclePropValue value = new VehiclePropValue(); 664 value.prop = AUDIO_PARAMETERS; 665 value.value.stringValue = parameters; 666 try { 667 mVehicleHal.set(value); 668 } catch (PropertyTimeoutException e) { 669 Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_EXT_ROUTING_HINT", e); 670 } 671 } 672 673 public String getAudioParameters(String keys) { 674 synchronized (this) { 675 if (!isPropertySupportedLocked(AUDIO_PARAMETERS)) { 676 throw new IllegalStateException("VehicleProperty.AUDIO_PARAMETERS not supported"); 677 } 678 } 679 try { 680 VehiclePropValue requested = new VehiclePropValue(); 681 requested.prop = AUDIO_PARAMETERS; 682 requested.value.stringValue = keys; 683 VehiclePropValue propValue = mVehicleHal.get(requested); 684 return propValue.value.stringValue; 685 } catch (PropertyTimeoutException e) { 686 Log.e(CarLog.TAG_AUDIO, "VehicleProperty.AUDIO_PARAMETERS not ready", e); 687 return new String(""); 688 } 689 } 690 691 public synchronized void setOnParameterChangeListener(OnParameterChangeListener listener) { 692 mOnParameterChangeListener = listener; 693 } 694 695 private void dispatchEventToListener(AudioHalFocusListener focusListener, 696 AudioHalVolumeListener volumeListener, 697 OnParameterChangeListener parameterListener, 698 List<VehiclePropValue> values) { 699 for (VehiclePropValue v : values) { 700 switch (v.prop) { 701 case VehicleProperty.AUDIO_FOCUS: { 702 ArrayList<Integer> vec = v.value.int32Values; 703 int focusState = vec.get(VehicleAudioFocusIndex.FOCUS); 704 int streams = vec.get(VehicleAudioFocusIndex.STREAMS); 705 int externalFocus = vec.get(VehicleAudioFocusIndex.EXTERNAL_FOCUS_STATE); 706 if (focusListener != null) { 707 focusListener.onFocusChange(focusState, streams, externalFocus); 708 } 709 } break; 710 case VehicleProperty.AUDIO_STREAM_STATE: { 711 ArrayList<Integer> vec = v.value.int32Values; 712 boolean streamStarted = vec.get(0) == VEHICLE_AUDIO_STREAM_STATE_STARTED; 713 int streamNum = vec.get(1); 714 if (focusListener != null) { 715 focusListener.onStreamStatusChange(streamNum, streamStarted); 716 } 717 } break; 718 case AUDIO_VOLUME: { 719 ArrayList<Integer> vec = v.value.int32Values; 720 int streamNum = vec.get(VehicleAudioVolumeIndex.STREAM); 721 int volume = vec.get(VehicleAudioVolumeIndex.VOLUME); 722 int volumeState = vec.get(VehicleAudioVolumeIndex.STATE); 723 if (volumeListener != null) { 724 volumeListener.onVolumeChange(streamNum, volume, volumeState); 725 } 726 } break; 727 case AUDIO_VOLUME_LIMIT: { 728 ArrayList<Integer> vec = v.value.int32Values; 729 int stream = vec.get(VehicleAudioVolumeLimitIndex.STREAM); 730 int maxVolume = vec.get(VehicleAudioVolumeLimitIndex.MAX_VOLUME); 731 if (volumeListener != null) { 732 volumeListener.onVolumeLimitChange(stream, maxVolume); 733 } 734 } break; 735 case AUDIO_PARAMETERS: { 736 String params = v.value.stringValue; 737 if (parameterListener != null) { 738 parameterListener.onParameterChange(params); 739 } 740 } 741 } 742 } 743 values.clear(); 744 } 745 746 @Override 747 public void dump(PrintWriter writer) { 748 writer.println("*Audio HAL*"); 749 writer.println(" audio H/W variant:" + mVariant); 750 writer.println(" Supported properties"); 751 VehicleHal.dumpProperties(writer, mProperties.values()); 752 } 753 754 } 755