• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.car.Car;
21 import android.car.media.CarAudioPatchHandle;
22 import android.car.media.ICarAudio;
23 import android.car.media.ICarVolumeCallback;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.PackageManager;
29 import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
30 import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
31 import android.media.AudioAttributes;
32 import android.media.AudioDeviceInfo;
33 import android.media.AudioDevicePort;
34 import android.media.AudioFormat;
35 import android.media.AudioGain;
36 import android.media.AudioGainConfig;
37 import android.media.AudioManager;
38 import android.media.AudioPatch;
39 import android.media.AudioPlaybackConfiguration;
40 import android.media.AudioPortConfig;
41 import android.media.AudioSystem;
42 import android.media.audiopolicy.AudioMix;
43 import android.media.audiopolicy.AudioMixingRule;
44 import android.media.audiopolicy.AudioPolicy;
45 import android.os.IBinder;
46 import android.os.Looper;
47 import android.os.RemoteException;
48 import android.telephony.TelephonyManager;
49 import android.text.TextUtils;
50 import android.util.Log;
51 import android.util.SparseArray;
52 import android.util.SparseIntArray;
53 
54 import com.android.internal.util.Preconditions;
55 
56 import java.io.PrintWriter;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.HashSet;
60 import java.util.List;
61 import java.util.NoSuchElementException;
62 import java.util.Set;
63 import java.util.stream.Collectors;
64 
65 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
66 
67     private static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
68 
69     private static final int[] CONTEXT_NUMBERS = new int[] {
70             ContextNumber.MUSIC,
71             ContextNumber.NAVIGATION,
72             ContextNumber.VOICE_COMMAND,
73             ContextNumber.CALL_RING,
74             ContextNumber.CALL,
75             ContextNumber.ALARM,
76             ContextNumber.NOTIFICATION,
77             ContextNumber.SYSTEM_SOUND
78     };
79 
80     private static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();
81 
82     // For legacy stream type based volume control.
83     // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned.
84     private static final int[] STREAM_TYPES = new int[] {
85             AudioManager.STREAM_MUSIC,
86             AudioManager.STREAM_ALARM,
87             AudioManager.STREAM_RING
88     };
89     private static final int[] STREAM_TYPE_USAGES = new int[] {
90             AudioAttributes.USAGE_MEDIA,
91             AudioAttributes.USAGE_ALARM,
92             AudioAttributes.USAGE_NOTIFICATION_RINGTONE
93     };
94 
95     static {
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC)96         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC)97         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL)98         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING, ContextNumber.CALL)99         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
100                 ContextNumber.CALL);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM)101         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION)102         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING)103         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST, ContextNumber.NOTIFICATION)104         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
105                 ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT, ContextNumber.NOTIFICATION)106         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
107                 ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED, ContextNumber.NOTIFICATION)108         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
109                 ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION)110         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY, ContextNumber.VOICE_COMMAND)111         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
112                 ContextNumber.VOICE_COMMAND);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, ContextNumber.NAVIGATION)113         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
114                 ContextNumber.NAVIGATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION, ContextNumber.SYSTEM_SOUND)115         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
116                 ContextNumber.SYSTEM_SOUND);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC)117         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID)118         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND)119         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND);
120     }
121 
122     private final Object mImplLock = new Object();
123 
124     private final Context mContext;
125     private final TelephonyManager mTelephonyManager;
126     private final AudioManager mAudioManager;
127     private final boolean mUseDynamicRouting;
128     private final SparseIntArray mContextToBus = new SparseIntArray();
129     private final SparseArray<CarAudioDeviceInfo> mCarAudioDeviceInfos = new SparseArray<>();
130 
131     private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
132             new AudioPolicy.AudioPolicyVolumeCallback() {
133         @Override
134         public void onVolumeAdjustment(int adjustment) {
135             final int usage = getSuggestedAudioUsage();
136             Log.v(CarLog.TAG_AUDIO,
137                     "onVolumeAdjustment: " + AudioManager.adjustToString(adjustment)
138                             + " suggested usage: " + AudioAttributes.usageToString(usage));
139             final int groupId = getVolumeGroupIdForUsage(usage);
140             final int currentVolume = getGroupVolume(groupId);
141             final int flags = AudioManager.FLAG_FROM_KEY | AudioManager.FLAG_SHOW_UI;
142             switch (adjustment) {
143                 case AudioManager.ADJUST_LOWER:
144                     if (currentVolume > getGroupMinVolume(groupId)) {
145                         setGroupVolume(groupId, currentVolume - 1, flags);
146                     }
147                     break;
148                 case AudioManager.ADJUST_RAISE:
149                     if (currentVolume < getGroupMaxVolume(groupId)) {
150                         setGroupVolume(groupId, currentVolume + 1, flags);
151                     }
152                     break;
153                 case AudioManager.ADJUST_MUTE:
154                     mAudioManager.setMasterMute(true, flags);
155                     callbackMasterMuteChange(flags);
156                     break;
157                 case AudioManager.ADJUST_UNMUTE:
158                     mAudioManager.setMasterMute(false, flags);
159                     callbackMasterMuteChange(flags);
160                     break;
161                 case AudioManager.ADJUST_TOGGLE_MUTE:
162                     mAudioManager.setMasterMute(!mAudioManager.isMasterMute(), flags);
163                     callbackMasterMuteChange(flags);
164                     break;
165                 case AudioManager.ADJUST_SAME:
166                 default:
167                     break;
168             }
169         }
170     };
171 
172     private final BinderInterfaceContainer<ICarVolumeCallback> mVolumeCallbackContainer =
173             new BinderInterfaceContainer<>();
174 
175     /**
176      * Simulates {@link ICarVolumeCallback} when it's running in legacy mode.
177      */
178     private final BroadcastReceiver mLegacyVolumeChangedReceiver = new BroadcastReceiver() {
179         @Override
180         public void onReceive(Context context, Intent intent) {
181             switch (intent.getAction()) {
182                 case AudioManager.VOLUME_CHANGED_ACTION:
183                     int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
184                     int groupId = getVolumeGroupIdForStreamType(streamType);
185                     if (groupId == -1) {
186                         Log.w(CarLog.TAG_AUDIO, "Unknown stream type: " + streamType);
187                     } else {
188                         callbackGroupVolumeChange(groupId, 0);
189                     }
190                     break;
191                 case AudioManager.MASTER_MUTE_CHANGED_ACTION:
192                     callbackMasterMuteChange(0);
193                     break;
194             }
195         }
196     };
197 
198     private AudioPolicy mAudioPolicy;
199     private CarVolumeGroup[] mCarVolumeGroups;
200 
CarAudioService(Context context)201     public CarAudioService(Context context) {
202         mContext = context;
203         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
204         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
205         mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
206     }
207 
208     /**
209      * Dynamic routing and volume groups are set only if
210      * {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
211      */
212     @Override
init()213     public void init() {
214         synchronized (mImplLock) {
215             if (!mUseDynamicRouting) {
216                 Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode");
217                 setupLegacyVolumeChangedListener();
218             } else {
219                 setupDynamicRouting();
220                 setupVolumeGroups();
221             }
222         }
223     }
224 
225     @Override
release()226     public void release() {
227         synchronized (mImplLock) {
228             if (mUseDynamicRouting) {
229                 if (mAudioPolicy != null) {
230                     mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
231                     mAudioPolicy = null;
232                 }
233             } else {
234                 mContext.unregisterReceiver(mLegacyVolumeChangedReceiver);
235             }
236 
237             mVolumeCallbackContainer.clear();
238         }
239     }
240 
241     @Override
dump(PrintWriter writer)242     public void dump(PrintWriter writer) {
243         writer.println("*CarAudioService*");
244         writer.println("\tRun in legacy mode? " + (!mUseDynamicRouting));
245         writer.println("\tMaster mute? " + mAudioManager.isMasterMute());
246         // Empty line for comfortable reading
247         writer.println();
248         if (mUseDynamicRouting) {
249             for (CarVolumeGroup group : mCarVolumeGroups) {
250                 group.dump(writer);
251             }
252         }
253     }
254 
255     /**
256      * @see {@link android.car.media.CarAudioManager#setGroupVolume(int, int, int)}
257      */
258     @Override
setGroupVolume(int groupId, int index, int flags)259     public void setGroupVolume(int groupId, int index, int flags) {
260         synchronized (mImplLock) {
261             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
262 
263             callbackGroupVolumeChange(groupId, flags);
264             // For legacy stream type based volume control
265             if (!mUseDynamicRouting) {
266                 mAudioManager.setStreamVolume(STREAM_TYPES[groupId], index, flags);
267                 return;
268             }
269 
270             CarVolumeGroup group = getCarVolumeGroup(groupId);
271             group.setCurrentGainIndex(index);
272         }
273     }
274 
callbackGroupVolumeChange(int groupId, int flags)275     private void callbackGroupVolumeChange(int groupId, int flags) {
276         for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback :
277                 mVolumeCallbackContainer.getInterfaces()) {
278             try {
279                 callback.binderInterface.onGroupVolumeChanged(groupId, flags);
280             } catch (RemoteException e) {
281                 Log.e(CarLog.TAG_AUDIO, "Failed to callback onGroupVolumeChanged", e);
282             }
283         }
284     }
285 
callbackMasterMuteChange(int flags)286     private void callbackMasterMuteChange(int flags) {
287         for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback :
288                 mVolumeCallbackContainer.getInterfaces()) {
289             try {
290                 callback.binderInterface.onMasterMuteChanged(flags);
291             } catch (RemoteException e) {
292                 Log.e(CarLog.TAG_AUDIO, "Failed to callback onMasterMuteChanged", e);
293             }
294         }
295     }
296 
297     /**
298      * @see {@link android.car.media.CarAudioManager#getGroupMaxVolume(int)}
299      */
300     @Override
getGroupMaxVolume(int groupId)301     public int getGroupMaxVolume(int groupId) {
302         synchronized (mImplLock) {
303             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
304 
305             // For legacy stream type based volume control
306             if (!mUseDynamicRouting) {
307                 return mAudioManager.getStreamMaxVolume(STREAM_TYPES[groupId]);
308             }
309 
310             CarVolumeGroup group = getCarVolumeGroup(groupId);
311             return group.getMaxGainIndex();
312         }
313     }
314 
315     /**
316      * @see {@link android.car.media.CarAudioManager#getGroupMinVolume(int)}
317      */
318     @Override
getGroupMinVolume(int groupId)319     public int getGroupMinVolume(int groupId) {
320         synchronized (mImplLock) {
321             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
322 
323             // For legacy stream type based volume control
324             if (!mUseDynamicRouting) {
325                 return mAudioManager.getStreamMinVolume(STREAM_TYPES[groupId]);
326             }
327 
328             CarVolumeGroup group = getCarVolumeGroup(groupId);
329             return group.getMinGainIndex();
330         }
331     }
332 
333     /**
334      * @see {@link android.car.media.CarAudioManager#getGroupVolume(int)}
335      */
336     @Override
getGroupVolume(int groupId)337     public int getGroupVolume(int groupId) {
338         synchronized (mImplLock) {
339             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
340 
341             // For legacy stream type based volume control
342             if (!mUseDynamicRouting) {
343                 return mAudioManager.getStreamVolume(STREAM_TYPES[groupId]);
344             }
345 
346             CarVolumeGroup group = getCarVolumeGroup(groupId);
347             return group.getCurrentGainIndex();
348         }
349     }
350 
getCarVolumeGroup(int groupId)351     private CarVolumeGroup getCarVolumeGroup(int groupId) {
352         Preconditions.checkNotNull(mCarVolumeGroups);
353         Preconditions.checkArgument(groupId >= 0 && groupId < mCarVolumeGroups.length,
354                 "groupId out of range: " + groupId);
355         return mCarVolumeGroups[groupId];
356     }
357 
setupLegacyVolumeChangedListener()358     private void setupLegacyVolumeChangedListener() {
359         IntentFilter intentFilter = new IntentFilter();
360         intentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
361         intentFilter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);
362         mContext.registerReceiver(mLegacyVolumeChangedReceiver, intentFilter);
363     }
364 
setupDynamicRouting()365     private void setupDynamicRouting() {
366         final IAudioControl audioControl = getAudioControl();
367         if (audioControl == null) {
368             return;
369         }
370         AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl);
371         int r = mAudioManager.registerAudioPolicy(audioPolicy);
372         if (r != AudioManager.SUCCESS) {
373             throw new RuntimeException("registerAudioPolicy failed " + r);
374         }
375         mAudioPolicy = audioPolicy;
376     }
377 
setupVolumeGroups()378     private void setupVolumeGroups() {
379         Preconditions.checkArgument(mCarAudioDeviceInfos.size() > 0,
380                 "No bus device is configured to setup volume groups");
381         final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper(
382                 mContext, R.xml.car_volume_groups);
383         mCarVolumeGroups = helper.loadVolumeGroups();
384         for (CarVolumeGroup group : mCarVolumeGroups) {
385             for (int contextNumber : group.getContexts()) {
386                 int busNumber = mContextToBus.get(contextNumber);
387                 group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber));
388             }
389 
390             // Now that we have all our contexts, ensure the HAL gets our intial value
391             group.setCurrentGainIndex(group.getCurrentGainIndex());
392 
393             Log.v(CarLog.TAG_AUDIO, "Processed volume group: " + group);
394         }
395         // Perform validation after all volume groups are processed
396         if (!validateVolumeGroups()) {
397             throw new RuntimeException("Invalid volume groups configuration");
398         }
399     }
400 
401     /**
402      * Constraints applied here:
403      *
404      * - One context should not appear in two groups
405      * - All contexts are assigned
406      * - One bus should not appear in two groups
407      * - All gain controllers in the same group have same step value
408      *
409      * Note that it is fine that there are buses not appear in any group, those buses may be
410      * reserved for other usages.
411      * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)}
412      *
413      * See also the car_volume_groups.xml configuration
414      */
validateVolumeGroups()415     private boolean validateVolumeGroups() {
416         Set<Integer> contextSet = new HashSet<>();
417         Set<Integer> busNumberSet = new HashSet<>();
418         for (CarVolumeGroup group : mCarVolumeGroups) {
419             // One context should not appear in two groups
420             for (int context : group.getContexts()) {
421                 if (contextSet.contains(context)) {
422                     Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context);
423                     return false;
424                 }
425                 contextSet.add(context);
426             }
427 
428             // One bus should not appear in two groups
429             for (int busNumber : group.getBusNumbers()) {
430                 if (busNumberSet.contains(busNumber)) {
431                     Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber);
432                     return false;
433                 }
434                 busNumberSet.add(busNumber);
435             }
436         }
437 
438         // All contexts are assigned
439         if (contextSet.size() != CONTEXT_NUMBERS.length) {
440             Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group");
441             Log.e(CarLog.TAG_AUDIO, "Assigned contexts "
442                     + Arrays.toString(contextSet.toArray(new Integer[contextSet.size()])));
443             Log.e(CarLog.TAG_AUDIO, "All contexts " + Arrays.toString(CONTEXT_NUMBERS));
444             return false;
445         }
446 
447         return true;
448     }
449 
450     @Nullable
getDynamicAudioPolicy(@onNull IAudioControl audioControl)451     private AudioPolicy getDynamicAudioPolicy(@NonNull IAudioControl audioControl) {
452         AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
453         builder.setLooper(Looper.getMainLooper());
454 
455         // 1st, enumerate all output bus device ports
456         AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
457         if (deviceInfos.length == 0) {
458             Log.e(CarLog.TAG_AUDIO, "getDynamicAudioPolicy, no output device available, ignore");
459             return null;
460         }
461         for (AudioDeviceInfo info : deviceInfos) {
462             Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
463                     info.getId(), info.getAddress(), info.getType()));
464             if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
465                 final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
466                 // See also the audio_policy_configuration.xml and getBusForContext in
467                 // audio control HAL, the bus number should be no less than zero.
468                 if (carInfo.getBusNumber() >= 0) {
469                     mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo);
470                     Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
471                 }
472             }
473         }
474 
475         // 2nd, map context to physical bus
476         try {
477             for (int contextNumber : CONTEXT_NUMBERS) {
478                 int busNumber = audioControl.getBusForContext(contextNumber);
479                 mContextToBus.put(contextNumber, busNumber);
480                 CarAudioDeviceInfo info = mCarAudioDeviceInfos.get(busNumber);
481                 if (info == null) {
482                     Log.w(CarLog.TAG_AUDIO, "No bus configured for context: " + contextNumber);
483                 }
484             }
485         } catch (RemoteException e) {
486             Log.e(CarLog.TAG_AUDIO, "Error mapping context to physical bus", e);
487         }
488 
489         // 3rd, enumerate all physical buses and build the routing policy.
490         // Note that one can not register audio mix for same bus more than once.
491         for (int i = 0; i < mCarAudioDeviceInfos.size(); i++) {
492             int busNumber = mCarAudioDeviceInfos.keyAt(i);
493             boolean hasContext = false;
494             CarAudioDeviceInfo info = mCarAudioDeviceInfos.valueAt(i);
495             AudioFormat mixFormat = new AudioFormat.Builder()
496                     .setSampleRate(info.getSampleRate())
497                     .setEncoding(info.getEncodingFormat())
498                     .setChannelMask(info.getChannelCount())
499                     .build();
500             AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
501             for (int j = 0; j < mContextToBus.size(); j++) {
502                 if (mContextToBus.valueAt(j) == busNumber) {
503                     hasContext = true;
504                     int contextNumber = mContextToBus.keyAt(j);
505                     int[] usages = getUsagesForContext(contextNumber);
506                     for (int usage : usages) {
507                         mixingRuleBuilder.addRule(
508                                 new AudioAttributes.Builder().setUsage(usage).build(),
509                                 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
510                     }
511                     Log.i(CarLog.TAG_AUDIO, "Bus number: " + busNumber
512                             + " contextNumber: " + contextNumber
513                             + " sampleRate: " + info.getSampleRate()
514                             + " channels: " + info.getChannelCount()
515                             + " usages: " + Arrays.toString(usages));
516                 }
517             }
518             if (hasContext) {
519                 // It's a valid case that an audio output bus is defined in
520                 // audio_policy_configuration and no context is assigned to it.
521                 // In such case, do not build a policy mix with zero rules.
522                 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
523                         .setFormat(mixFormat)
524                         .setDevice(info.getAudioDeviceInfo())
525                         .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
526                         .build();
527                 builder.addMix(audioMix);
528             }
529         }
530 
531         // 4th, attach the {@link AudioPolicyVolumeCallback}
532         builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
533 
534         return builder.build();
535     }
536 
getUsagesForContext(int contextNumber)537     private int[] getUsagesForContext(int contextNumber) {
538         final List<Integer> usages = new ArrayList<>();
539         for (int i = 0; i < USAGE_TO_CONTEXT.size(); i++) {
540             if (USAGE_TO_CONTEXT.valueAt(i) == contextNumber) {
541                 usages.add(USAGE_TO_CONTEXT.keyAt(i));
542             }
543         }
544         return usages.stream().mapToInt(i -> i).toArray();
545     }
546 
547     @Override
setFadeTowardFront(float value)548     public void setFadeTowardFront(float value) {
549         synchronized (mImplLock) {
550             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
551             final IAudioControl audioControlHal = getAudioControl();
552             if (audioControlHal != null) {
553                 try {
554                     audioControlHal.setFadeTowardFront(value);
555                 } catch (RemoteException e) {
556                     Log.e(CarLog.TAG_AUDIO, "setFadeTowardFront failed", e);
557                 }
558             }
559         }
560     }
561 
562     @Override
setBalanceTowardRight(float value)563     public void setBalanceTowardRight(float value) {
564         synchronized (mImplLock) {
565             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
566             final IAudioControl audioControlHal = getAudioControl();
567             if (audioControlHal != null) {
568                 try {
569                     audioControlHal.setBalanceTowardRight(value);
570                 } catch (RemoteException e) {
571                     Log.e(CarLog.TAG_AUDIO, "setBalanceTowardRight failed", e);
572                 }
573             }
574         }
575     }
576 
577     /**
578      * @return Array of accumulated device addresses, empty array if we found nothing
579      */
580     @Override
getExternalSources()581     public @NonNull String[] getExternalSources() {
582         synchronized (mImplLock) {
583             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
584             List<String> sourceAddresses = new ArrayList<>();
585 
586             AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
587             if (devices.length == 0) {
588                 Log.w(CarLog.TAG_AUDIO, "getExternalSources, no input devices found.");
589             }
590 
591             // Collect the list of non-microphone input ports
592             for (AudioDeviceInfo info : devices) {
593                 switch (info.getType()) {
594                     // TODO:  Can we trim this set down? Especially duplicates like FM vs FM_TUNER?
595                     case AudioDeviceInfo.TYPE_FM:
596                     case AudioDeviceInfo.TYPE_FM_TUNER:
597                     case AudioDeviceInfo.TYPE_TV_TUNER:
598                     case AudioDeviceInfo.TYPE_HDMI:
599                     case AudioDeviceInfo.TYPE_AUX_LINE:
600                     case AudioDeviceInfo.TYPE_LINE_ANALOG:
601                     case AudioDeviceInfo.TYPE_LINE_DIGITAL:
602                     case AudioDeviceInfo.TYPE_USB_ACCESSORY:
603                     case AudioDeviceInfo.TYPE_USB_DEVICE:
604                     case AudioDeviceInfo.TYPE_USB_HEADSET:
605                     case AudioDeviceInfo.TYPE_IP:
606                     case AudioDeviceInfo.TYPE_BUS:
607                         String address = info.getAddress();
608                         if (TextUtils.isEmpty(address)) {
609                             Log.w(CarLog.TAG_AUDIO,
610                                     "Discarded device with empty address, type=" + info.getType());
611                         } else {
612                             sourceAddresses.add(address);
613                         }
614                 }
615             }
616 
617             return sourceAddresses.toArray(new String[sourceAddresses.size()]);
618         }
619     }
620 
621     @Override
createAudioPatch(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)622     public CarAudioPatchHandle createAudioPatch(String sourceAddress,
623             @AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
624         synchronized (mImplLock) {
625             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
626             return createAudioPatchLocked(sourceAddress, usage, gainInMillibels);
627         }
628     }
629 
630     @Override
releaseAudioPatch(CarAudioPatchHandle carPatch)631     public void releaseAudioPatch(CarAudioPatchHandle carPatch) {
632         synchronized (mImplLock) {
633             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
634             releaseAudioPatchLocked(carPatch);
635         }
636     }
637 
createAudioPatchLocked(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)638     private CarAudioPatchHandle createAudioPatchLocked(String sourceAddress,
639             @AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
640         // Find the named source port
641         AudioDeviceInfo sourcePortInfo = null;
642         AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
643         for (AudioDeviceInfo info : deviceInfos) {
644             if (sourceAddress.equals(info.getAddress())) {
645                 // This is the one for which we're looking
646                 sourcePortInfo = info;
647                 break;
648             }
649         }
650         Preconditions.checkNotNull(sourcePortInfo,
651                 "Specified source is not available: " + sourceAddress);
652 
653         // Find the output port associated with the given carUsage
654         AudioDevicePort sinkPort = Preconditions.checkNotNull(getAudioPort(usage),
655                 "Sink not available for usage: " + AudioAttributes.usageToString(usage));
656 
657         // {@link android.media.AudioPort#activeConfig()} is valid for mixer port only,
658         // since audio framework has no clue what's active on the device ports.
659         // Therefore we construct an empty / default configuration here, which the audio HAL
660         // implementation should ignore.
661         AudioPortConfig sinkConfig = sinkPort.buildConfig(0,
662                 AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT, null);
663         Log.d(CarLog.TAG_AUDIO, "createAudioPatch sinkConfig: " + sinkConfig);
664 
665         // Configure the source port to match the output port except for a gain adjustment
666         final CarAudioDeviceInfo helper = new CarAudioDeviceInfo(sourcePortInfo);
667         AudioGain audioGain = Preconditions.checkNotNull(helper.getAudioGain(),
668                 "Gain controller not available for source port");
669 
670         // size of gain values is 1 in MODE_JOINT
671         AudioGainConfig audioGainConfig = audioGain.buildConfig(AudioGain.MODE_JOINT,
672                 audioGain.channelMask(), new int[] { gainInMillibels }, 0);
673         // Construct an empty / default configuration excepts gain config here and it's up to the
674         // audio HAL how to interpret this configuration, which the audio HAL
675         // implementation should ignore.
676         AudioPortConfig sourceConfig = sourcePortInfo.getPort().buildConfig(0,
677                 AudioFormat.CHANNEL_IN_DEFAULT, AudioFormat.ENCODING_DEFAULT, audioGainConfig);
678 
679         // Create an audioPatch to connect the two ports
680         AudioPatch[] patch = new AudioPatch[] { null };
681         int result = AudioManager.createAudioPatch(patch,
682                 new AudioPortConfig[] { sourceConfig },
683                 new AudioPortConfig[] { sinkConfig });
684         if (result != AudioManager.SUCCESS) {
685             throw new RuntimeException("createAudioPatch failed with code " + result);
686         }
687 
688         Preconditions.checkNotNull(patch[0],
689                 "createAudioPatch didn't provide expected single handle");
690         Log.d(CarLog.TAG_AUDIO, "Audio patch created: " + patch[0]);
691         return new CarAudioPatchHandle(patch[0]);
692     }
693 
releaseAudioPatchLocked(CarAudioPatchHandle carPatch)694     private void releaseAudioPatchLocked(CarAudioPatchHandle carPatch) {
695         // NOTE:  AudioPolicyService::removeNotificationClient will take care of this automatically
696         //        if the client that created a patch quits.
697 
698         // FIXME {@link AudioManager#listAudioPatches(ArrayList)} returns old generation of
699         // audio patches after creation
700         ArrayList<AudioPatch> patches = new ArrayList<>();
701         int result = AudioSystem.listAudioPatches(patches, new int[1]);
702         if (result != AudioManager.SUCCESS) {
703             throw new RuntimeException("listAudioPatches failed with code " + result);
704         }
705 
706         // Look for a patch that matches the provided user side handle
707         for (AudioPatch patch : patches) {
708             if (carPatch.represents(patch)) {
709                 // Found it!
710                 result = AudioManager.releaseAudioPatch(patch);
711                 if (result != AudioManager.SUCCESS) {
712                     throw new RuntimeException("releaseAudioPatch failed with code " + result);
713                 }
714                 return;
715             }
716         }
717 
718         // If we didn't find a match, then something went awry, but it's probably not fatal...
719         Log.e(CarLog.TAG_AUDIO, "releaseAudioPatch found no match for " + carPatch);
720     }
721 
722     @Override
getVolumeGroupCount()723     public int getVolumeGroupCount() {
724         synchronized (mImplLock) {
725             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
726 
727             // For legacy stream type based volume control
728             if (!mUseDynamicRouting) return STREAM_TYPES.length;
729 
730             return mCarVolumeGroups == null ? 0 : mCarVolumeGroups.length;
731         }
732     }
733 
734     @Override
getVolumeGroupIdForUsage(@udioAttributes.AttributeUsage int usage)735     public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) {
736         synchronized (mImplLock) {
737             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
738 
739             if (mCarVolumeGroups == null) {
740                 return -1;
741             }
742 
743             for (int i = 0; i < mCarVolumeGroups.length; i++) {
744                 int[] contexts = mCarVolumeGroups[i].getContexts();
745                 for (int context : contexts) {
746                     if (USAGE_TO_CONTEXT.get(usage) == context) {
747                         return i;
748                     }
749                 }
750             }
751             return -1;
752         }
753     }
754 
755     @Override
getUsagesForVolumeGroupId(int groupId)756     public @NonNull int[] getUsagesForVolumeGroupId(int groupId) {
757         synchronized (mImplLock) {
758             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
759 
760             // For legacy stream type based volume control
761             if (!mUseDynamicRouting) {
762                 return new int[] { STREAM_TYPE_USAGES[groupId] };
763             }
764 
765             CarVolumeGroup group = getCarVolumeGroup(groupId);
766             Set<Integer> contexts =
767                     Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
768             final List<Integer> usages = new ArrayList<>();
769             for (int i = 0; i < USAGE_TO_CONTEXT.size(); i++) {
770                 if (contexts.contains(USAGE_TO_CONTEXT.valueAt(i))) {
771                     usages.add(USAGE_TO_CONTEXT.keyAt(i));
772                 }
773             }
774             return usages.stream().mapToInt(i -> i).toArray();
775         }
776     }
777 
778     /**
779      * See {@link android.car.media.CarAudioManager#registerVolumeCallback(IBinder)}
780      */
781     @Override
registerVolumeCallback(@onNull IBinder binder)782     public void registerVolumeCallback(@NonNull IBinder binder) {
783         synchronized (mImplLock) {
784             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
785 
786             mVolumeCallbackContainer.addBinder(ICarVolumeCallback.Stub.asInterface(binder));
787         }
788     }
789 
790     /**
791      * See {@link android.car.media.CarAudioManager#unregisterVolumeCallback(IBinder)}
792      */
793     @Override
unregisterVolumeCallback(@onNull IBinder binder)794     public void unregisterVolumeCallback(@NonNull IBinder binder) {
795         synchronized (mImplLock) {
796             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
797 
798             mVolumeCallbackContainer.removeBinder(ICarVolumeCallback.Stub.asInterface(binder));
799         }
800     }
801 
enforcePermission(String permissionName)802     private void enforcePermission(String permissionName) {
803         if (mContext.checkCallingOrSelfPermission(permissionName)
804                 != PackageManager.PERMISSION_GRANTED) {
805             throw new SecurityException("requires permission " + permissionName);
806         }
807     }
808 
809     /**
810      * @return {@link AudioDevicePort} that handles the given car audio usage.
811      * Multiple usages may share one {@link AudioDevicePort}
812      */
getAudioPort(@udioAttributes.AttributeUsage int usage)813     private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) {
814         final int groupId = getVolumeGroupIdForUsage(usage);
815         final CarVolumeGroup group = Preconditions.checkNotNull(mCarVolumeGroups[groupId],
816                 "Can not find CarVolumeGroup by usage: "
817                         + AudioAttributes.usageToString(usage));
818         return group.getAudioDevicePortForContext(USAGE_TO_CONTEXT.get(usage));
819     }
820 
821     /**
822      * @return The suggested {@link AudioAttributes} usage to which the volume key events apply
823      */
getSuggestedAudioUsage()824     private @AudioAttributes.AttributeUsage int getSuggestedAudioUsage() {
825         int callState = mTelephonyManager.getCallState();
826         if (callState == TelephonyManager.CALL_STATE_RINGING) {
827             return AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
828         } else if (callState == TelephonyManager.CALL_STATE_OFFHOOK) {
829             return AudioAttributes.USAGE_VOICE_COMMUNICATION;
830         } else {
831             List<AudioPlaybackConfiguration> playbacks = mAudioManager
832                     .getActivePlaybackConfigurations()
833                     .stream()
834                     .filter(AudioPlaybackConfiguration::isActive)
835                     .collect(Collectors.toList());
836             if (!playbacks.isEmpty()) {
837                 // Get audio usage from active playbacks if there is any, last one if multiple
838                 return playbacks.get(playbacks.size() - 1).getAudioAttributes().getUsage();
839             } else {
840                 // TODO(b/72695246): Otherwise, get audio usage from foreground activity/window
841                 return DEFAULT_AUDIO_USAGE;
842             }
843         }
844     }
845 
846     /**
847      * Gets volume group by a given legacy stream type
848      * @param streamType Legacy stream type such as {@link AudioManager#STREAM_MUSIC}
849      * @return volume group id mapped from stream type
850      */
getVolumeGroupIdForStreamType(int streamType)851     private int getVolumeGroupIdForStreamType(int streamType) {
852         int groupId = -1;
853         for (int i = 0; i < STREAM_TYPES.length; i++) {
854             if (streamType == STREAM_TYPES[i]) {
855                 groupId = i;
856                 break;
857             }
858         }
859         return groupId;
860     }
861 
862     @Nullable
getAudioControl()863     private static IAudioControl getAudioControl() {
864         try {
865             return IAudioControl.getService();
866         } catch (RemoteException e) {
867             Log.e(CarLog.TAG_AUDIO, "Failed to get IAudioControl service", e);
868         } catch (NoSuchElementException e) {
869             Log.e(CarLog.TAG_AUDIO, "IAudioControl service not registered yet");
870         }
871         return null;
872     }
873 }
874