Home | History | Annotate | Download | only in car
      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 {
     96         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
     97         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
     98         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
     99         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
    100                 ContextNumber.CALL);
    101         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
    102         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
    103         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
    104         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
    105                 ContextNumber.NOTIFICATION);
    106         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
    107                 ContextNumber.NOTIFICATION);
    108         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
    109                 ContextNumber.NOTIFICATION);
    110         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
    111         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
    112                 ContextNumber.VOICE_COMMAND);
    113         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
    114                 ContextNumber.NAVIGATION);
    115         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
    116                 ContextNumber.SYSTEM_SOUND);
    117         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
    118         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
    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 
    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
    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
    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
    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
    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 
    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 
    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
    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
    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
    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 
    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 
    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 
    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 
    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      */
    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
    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 
    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
    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
    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
    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
    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
    631     public void releaseAudioPatch(CarAudioPatchHandle carPatch) {
    632         synchronized (mImplLock) {
    633             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
    634             releaseAudioPatchLocked(carPatch);
    635         }
    636     }
    637 
    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 
    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
    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
    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
    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
    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
    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 
    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      */
    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      */
    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      */
    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
    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