Home | History | Annotate | Download | only in hal
      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