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