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 =
    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