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          */
onFocusChange(int focusState, int streams, int externalFocus)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          */
onStreamStatusChange(int streamNumber, boolean streamActive)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          */
onVolumeChange(int streamNumber, int volume, int volumeState)159         void onVolumeChange(int streamNumber, int volume, int volumeState);
160         /**
161          * Volume limit change from car.
162          * @param streamNumber
163          * @param volume
164          */
onVolumeLimitChange(int streamNumber, int volume)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 
AudioHalService(VehicleHal vehicleHal)179     public AudioHalService(VehicleHal vehicleHal) {
180         mVehicleHal = vehicleHal;
181     }
182 
setFocusListener(AudioHalFocusListener focusListener)183     public synchronized void setFocusListener(AudioHalFocusListener focusListener) {
184         mFocusListener = focusListener;
185     }
186 
setVolumeListener(AudioHalVolumeListener volumeListener)187     public synchronized void setVolumeListener(AudioHalVolumeListener volumeListener) {
188         mVolumeListener = volumeListener;
189     }
190 
setAudioRoutingPolicy(AudioRoutingPolicy policy)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
getStreamMaxVolume(int stream)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      */
logicalStreamToHalContextType(int logicalStream)254     public static int logicalStreamToHalContextType(int logicalStream) {
255         return logicalStreamWithExtTypeToHalContextType(logicalStream, null);
256     }
257 
logicalStreamWithExtTypeToHalContextType(int logicalStream, String extType)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      */
carContextToCarUsage(int carContext)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 
requestAudioFocusChange(int request, int streams, int audioContexts)346     public void requestAudioFocusChange(int request, int streams, int audioContexts) {
347         requestAudioFocusChange(request, streams, VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG, audioContexts);
348     }
349 
requestAudioFocusChange(int request, int streams, int extFocus, int audioContexts)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 
setStreamVolume(int streamType, int index)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 
getStreamVolume(int stream)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 
getHwVariant()401     public synchronized int getHwVariant() {
402         return mVariant;
403     }
404 
isRadioExternal()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 
isFocusSupported()414     public synchronized boolean isFocusSupported() {
415         return isPropertySupportedLocked(AUDIO_FOCUS);
416     }
417 
isAudioVolumeSupported()418     public synchronized boolean isAudioVolumeSupported() {
419         return isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME);
420     }
421 
getSupportedAudioVolumeContexts()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      */
isExternalAudioVolumePersistent()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 
isAudioVolumeLimitSupported()449     public synchronized boolean isAudioVolumeLimitSupported() {
450         return isPropertySupportedLocked(AUDIO_VOLUME_LIMIT);
451     }
452 
isAudioVolumeMasterOnly()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      */
getCurrentFocusState()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 
ExtRoutingSourceInfo(int bitPosition, int physycalStreamNumber)497         public ExtRoutingSourceInfo(int bitPosition, int physycalStreamNumber) {
498             this.bitPosition = bitPosition;
499             this.physicalStreamNumber = physycalStreamNumber;
500         }
501 
502         @Override
toString()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      */
getExternalAudioRoutingTypes()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 
setExternalRoutingSource(int[] externalRoutings)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 
isPropertySupportedLocked(int property)565     private boolean isPropertySupportedLocked(int property) {
566         VehiclePropConfig config = mProperties.get(property);
567         return config != null;
568     }
569 
570     @Override
init()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
release()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
takeSupportedProperties( Collection<VehiclePropConfig> allProperties)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
handleHalEvents(List<VehiclePropValue> values)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 
getAudioParameterKeys()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 
setAudioParameters(String parameters)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 
getAudioParameters(String keys)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 
setOnParameterChangeListener(OnParameterChangeListener listener)682     public synchronized void setOnParameterChangeListener(OnParameterChangeListener listener) {
683         mOnParameterChangeListener = listener;
684     }
685 
dispatchEventToListener(AudioHalFocusListener focusListener, AudioHalVolumeListener volumeListener, OnParameterChangeListener parameterListener, List<VehiclePropValue> values)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
dump(PrintWriter writer)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