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.audio;
17 
18 import static android.car.media.CarAudioManager.INVALID_VOLUME_GROUP_ID;
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.UserIdInt;
23 import android.car.Car;
24 import android.car.CarOccupantZoneManager;
25 import android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener;
26 import android.car.media.CarAudioManager;
27 import android.car.media.CarAudioPatchHandle;
28 import android.car.media.ICarAudio;
29 import android.car.media.ICarVolumeCallback;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.pm.PackageManager;
35 import android.media.AudioAttributes;
36 import android.media.AudioAttributes.AttributeSystemUsage;
37 import android.media.AudioAttributes.AttributeUsage;
38 import android.media.AudioDeviceAttributes;
39 import android.media.AudioDeviceInfo;
40 import android.media.AudioDevicePort;
41 import android.media.AudioFocusInfo;
42 import android.media.AudioFormat;
43 import android.media.AudioGain;
44 import android.media.AudioGainConfig;
45 import android.media.AudioManager;
46 import android.media.AudioPatch;
47 import android.media.AudioPlaybackConfiguration;
48 import android.media.AudioPortConfig;
49 import android.media.audiopolicy.AudioPolicy;
50 import android.os.IBinder;
51 import android.os.Looper;
52 import android.os.UserHandle;
53 import android.telephony.Annotation.CallState;
54 import android.telephony.TelephonyManager;
55 import android.text.TextUtils;
56 import android.util.Log;
57 import android.util.SparseIntArray;
58 import android.view.KeyEvent;
59 
60 import com.android.car.CarLocalServices;
61 import com.android.car.CarLog;
62 import com.android.car.CarOccupantZoneService;
63 import com.android.car.CarServiceBase;
64 import com.android.car.R;
65 import com.android.car.audio.CarAudioContext.AudioContext;
66 import com.android.car.audio.hal.AudioControlFactory;
67 import com.android.car.audio.hal.AudioControlWrapper;
68 import com.android.car.audio.hal.AudioControlWrapperV1;
69 import com.android.car.audio.hal.HalAudioFocus;
70 import com.android.internal.util.Preconditions;
71 
72 import org.xmlpull.v1.XmlPullParserException;
73 
74 import java.io.File;
75 import java.io.FileInputStream;
76 import java.io.IOException;
77 import java.io.InputStream;
78 import java.io.PrintWriter;
79 import java.util.ArrayList;
80 import java.util.Arrays;
81 import java.util.HashMap;
82 import java.util.List;
83 import java.util.Map;
84 import java.util.Objects;
85 import java.util.Set;
86 import java.util.stream.Collectors;
87 
88 /**
89  * Service responsible for interaction with car's audio system.
90  */
91 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
92     // Turning this off will result in falling back to the default focus policy of Android
93     // (which boils down to "grant if not in a phone call, else deny").
94     // Aside from the obvious effect of ignoring the logic in CarAudioFocus, this will also
95     // result in the framework taking over responsibility for ducking in TRANSIENT_LOSS cases.
96     // Search for "DUCK_VSHAPE" in PLaybackActivityMonitor.java to see where this happens.
97     private static boolean sUseCarAudioFocus = true;
98 
99     // Enable to allowed for delayed audio focus in car audio service.
100     private static final boolean ENABLE_DELAYED_AUDIO_FOCUS = true;
101 
102     static final @AttributeUsage int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
103     static final @AudioContext int DEFAULT_AUDIO_CONTEXT = CarAudioContext.getContextForUsage(
104             CarAudioService.DEFAULT_AUDIO_USAGE);
105 
106     // CarAudioService reads configuration from the following paths respectively.
107     // If the first one is found, all others are ignored.
108     // If no one is found, it fallbacks to car_volume_groups.xml resource file.
109     private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {
110             "/vendor/etc/car_audio_configuration.xml",
111             "/system/etc/car_audio_configuration.xml"
112     };
113 
114     private static final @AttributeSystemUsage int[] SYSTEM_USAGES = new int[] {
115             AudioAttributes.USAGE_CALL_ASSISTANT,
116             AudioAttributes.USAGE_EMERGENCY,
117             AudioAttributes.USAGE_SAFETY,
118             AudioAttributes.USAGE_VEHICLE_STATUS,
119             AudioAttributes.USAGE_ANNOUNCEMENT
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 boolean mPersistMasterMuteState;
129     private final CarAudioSettings mCarAudioSettings;
130     private AudioControlWrapper mAudioControlWrapper;
131     private HalAudioFocus mHalAudioFocus;
132 
133     private CarOccupantZoneService mOccupantZoneService;
134 
135     private CarOccupantZoneManager mOccupantZoneManager;
136 
137     private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
138             new AudioPolicy.AudioPolicyVolumeCallback() {
139         @Override
140         public void onVolumeAdjustment(int adjustment) {
141             int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
142             @AudioContext int suggestedContext = getSuggestedAudioContext();
143 
144             int groupId;
145             synchronized (mImplLock) {
146                 groupId = getVolumeGroupIdForAudioContextLocked(zoneId, suggestedContext);
147             }
148 
149             if (Log.isLoggable(CarLog.TAG_AUDIO, Log.VERBOSE)) {
150                 Log.v(CarLog.TAG_AUDIO, "onVolumeAdjustment: "
151                         + AudioManager.adjustToString(adjustment) + " suggested audio context: "
152                         + CarAudioContext.toString(suggestedContext) + " suggested volume group: "
153                         + groupId);
154             }
155 
156             final int currentVolume = getGroupVolume(zoneId, groupId);
157             final int flags = AudioManager.FLAG_FROM_KEY | AudioManager.FLAG_SHOW_UI;
158             switch (adjustment) {
159                 case AudioManager.ADJUST_LOWER:
160                     int minValue = Math.max(currentVolume - 1, getGroupMinVolume(zoneId, groupId));
161                     setGroupVolume(zoneId, groupId, minValue , flags);
162                     break;
163                 case AudioManager.ADJUST_RAISE:
164                     int maxValue =  Math.min(currentVolume + 1, getGroupMaxVolume(zoneId, groupId));
165                     setGroupVolume(zoneId, groupId, maxValue, flags);
166                     break;
167                 case AudioManager.ADJUST_MUTE:
168                     setMasterMute(true, flags);
169                     callbackMasterMuteChange(zoneId, flags);
170                     break;
171                 case AudioManager.ADJUST_UNMUTE:
172                     setMasterMute(false, flags);
173                     callbackMasterMuteChange(zoneId, flags);
174                     break;
175                 case AudioManager.ADJUST_TOGGLE_MUTE:
176                     setMasterMute(!mAudioManager.isMasterMute(), flags);
177                     callbackMasterMuteChange(zoneId, flags);
178                     break;
179                 case AudioManager.ADJUST_SAME:
180                 default:
181                     break;
182             }
183         }
184     };
185 
186     /**
187      * Simulates {@link ICarVolumeCallback} when it's running in legacy mode.
188      * This receiver assumes the intent is sent to {@link CarAudioManager#PRIMARY_AUDIO_ZONE}.
189      */
190     private final BroadcastReceiver mLegacyVolumeChangedReceiver = new BroadcastReceiver() {
191         @Override
192         public void onReceive(Context context, Intent intent) {
193             final int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
194             switch (intent.getAction()) {
195                 case AudioManager.VOLUME_CHANGED_ACTION:
196                     int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
197                     int groupId = getVolumeGroupIdForStreamType(streamType);
198                     if (groupId == -1) {
199                         Log.w(CarLog.TAG_AUDIO, "Unknown stream type: " + streamType);
200                     } else {
201                         callbackGroupVolumeChange(zoneId, groupId, 0);
202                     }
203                     break;
204                 case AudioManager.MASTER_MUTE_CHANGED_ACTION:
205                     callbackMasterMuteChange(zoneId, 0);
206                     break;
207             }
208         }
209     };
210 
211     private AudioPolicy mAudioPolicy;
212     private CarZonesAudioFocus mFocusHandler;
213     private String mCarAudioConfigurationPath;
214     private SparseIntArray mAudioZoneIdToOccupantZoneIdMapping;
215     private CarAudioZone[] mCarAudioZones;
216     private final CarVolumeCallbackHandler mCarVolumeCallbackHandler;
217     private final SparseIntArray mAudioZoneIdToUserIdMapping;
218 
219 
220     // TODO do not store uid mapping here instead use the uid
221     //  device affinity in audio policy when available
222     private Map<Integer, Integer> mUidToZoneMap;
223     private OccupantZoneConfigChangeListener
224             mOccupantZoneConfigChangeListener = new CarAudioOccupantConfigChangeListener();
225 
CarAudioService(Context context)226     public CarAudioService(Context context) {
227         mContext = context;
228         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
229         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
230         mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
231         mPersistMasterMuteState = mContext.getResources().getBoolean(
232                 R.bool.audioPersistMasterMuteState);
233         mUidToZoneMap = new HashMap<>();
234         mCarVolumeCallbackHandler = new CarVolumeCallbackHandler();
235         mCarAudioSettings = new CarAudioSettings(mContext.getContentResolver());
236         mAudioZoneIdToUserIdMapping = new SparseIntArray();
237     }
238 
239     /**
240      * Dynamic routing and volume groups are set only if
241      * {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
242      */
243     @Override
init()244     public void init() {
245         synchronized (mImplLock) {
246             mOccupantZoneService = CarLocalServices.getService(CarOccupantZoneService.class);
247             Car car = new Car(mContext, /* service= */null, /* handler= */ null);
248             mOccupantZoneManager = new CarOccupantZoneManager(car, mOccupantZoneService);
249             if (mUseDynamicRouting) {
250                 setupDynamicRoutingLocked();
251                 setupHalAudioFocusListenerLocked();
252             } else {
253                 Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
254                 setupLegacyVolumeChangedListener();
255             }
256 
257             // Restore master mute state if applicable
258             if (mPersistMasterMuteState) {
259                 boolean storedMasterMute = mCarAudioSettings.getMasterMute();
260                 setMasterMute(storedMasterMute, 0);
261             }
262 
263             mAudioManager.setSupportedSystemUsages(SYSTEM_USAGES);
264         }
265     }
266 
267     @Override
release()268     public void release() {
269         synchronized (mImplLock) {
270             if (mUseDynamicRouting) {
271                 if (mAudioPolicy != null) {
272                     mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
273                     mAudioPolicy = null;
274                     mFocusHandler.setOwningPolicy(null, null);
275                     mFocusHandler = null;
276                 }
277             } else {
278                 mContext.unregisterReceiver(mLegacyVolumeChangedReceiver);
279             }
280 
281             mCarVolumeCallbackHandler.release();
282 
283             if (mHalAudioFocus != null) {
284                 mHalAudioFocus.unregisterFocusListener();
285             }
286 
287             if (mAudioControlWrapper != null) {
288                 mAudioControlWrapper.unlinkToDeath();
289                 mAudioControlWrapper = null;
290             }
291         }
292     }
293 
294     @Override
dump(PrintWriter writer)295     public void dump(PrintWriter writer) {
296         writer.println("*CarAudioService*");
297         writer.println("\tRun in legacy mode? " + (!mUseDynamicRouting));
298         writer.println("\tPersist master mute state? " + mPersistMasterMuteState);
299         writer.println("\tMaster muted? " + mAudioManager.isMasterMute());
300         if (mCarAudioConfigurationPath != null) {
301             writer.println("\tCar audio configuration path: " + mCarAudioConfigurationPath);
302         }
303         // Empty line for comfortable reading
304         writer.println();
305         if (mUseDynamicRouting) {
306             for (CarAudioZone zone : mCarAudioZones) {
307                 zone.dump("\t", writer);
308             }
309             writer.println();
310             writer.println("\tUserId to Zone Mapping:");
311             for (int index = 0; index < mAudioZoneIdToUserIdMapping.size(); index++) {
312                 int audioZoneId = mAudioZoneIdToUserIdMapping.keyAt(index);
313                 writer.printf("\t\tUserId %d mapped to zone %d\n",
314                         mAudioZoneIdToUserIdMapping.get(audioZoneId),
315                         audioZoneId);
316             }
317             writer.println("\tUID to Zone Mapping:");
318             for (int callingId : mUidToZoneMap.keySet()) {
319                 writer.printf("\t\tUID %d mapped to zone %d\n",
320                         callingId,
321                         mUidToZoneMap.get(callingId));
322             }
323 
324             writer.println();
325             mFocusHandler.dump("\t", writer);
326 
327             writer.println();
328             getAudioControlWrapperLocked().dump("\t", writer);
329 
330             if (mHalAudioFocus != null) {
331                 writer.println();
332                 mHalAudioFocus.dump("\t", writer);
333             } else {
334                 writer.println("\tNo HalAudioFocus instance\n");
335             }
336         }
337 
338     }
339 
340     @Override
isDynamicRoutingEnabled()341     public boolean isDynamicRoutingEnabled() {
342         return mUseDynamicRouting;
343     }
344 
345     /**
346      * @see {@link android.car.media.CarAudioManager#setGroupVolume(int, int, int, int)}
347      */
348     @Override
setGroupVolume(int zoneId, int groupId, int index, int flags)349     public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
350         synchronized (mImplLock) {
351             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
352 
353             callbackGroupVolumeChange(zoneId, groupId, flags);
354             // For legacy stream type based volume control
355             if (!mUseDynamicRouting) {
356                 mAudioManager.setStreamVolume(
357                         CarAudioDynamicRouting.STREAM_TYPES[groupId], index, flags);
358                 return;
359             }
360 
361             CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
362             group.setCurrentGainIndex(index);
363         }
364     }
365 
callbackGroupVolumeChange(int zoneId, int groupId, int flags)366     private void callbackGroupVolumeChange(int zoneId, int groupId, int flags) {
367         mCarVolumeCallbackHandler.onVolumeGroupChange(zoneId, groupId, flags);
368     }
369 
setMasterMute(boolean mute, int flags)370     private void setMasterMute(boolean mute, int flags) {
371         mAudioManager.setMasterMute(mute, flags);
372 
373         // When the master mute is turned ON, we want the playing app to get a "pause" command.
374         // When the volume is unmuted, we want to resume playback.
375         int keycode = mute ? KeyEvent.KEYCODE_MEDIA_PAUSE : KeyEvent.KEYCODE_MEDIA_PLAY;
376         mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keycode));
377         mAudioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keycode));
378     }
379 
callbackMasterMuteChange(int zoneId, int flags)380     private void callbackMasterMuteChange(int zoneId, int flags) {
381         mCarVolumeCallbackHandler.onMasterMuteChanged(zoneId, flags);
382 
383         // Persists master mute state if applicable
384         if (mPersistMasterMuteState) {
385             mCarAudioSettings.storeMasterMute(mAudioManager.isMasterMute());
386         }
387     }
388 
389     /**
390      * @see {@link android.car.media.CarAudioManager#getGroupMaxVolume(int, int)}
391      */
392     @Override
getGroupMaxVolume(int zoneId, int groupId)393     public int getGroupMaxVolume(int zoneId, int groupId) {
394         synchronized (mImplLock) {
395             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
396 
397             // For legacy stream type based volume control
398             if (!mUseDynamicRouting) {
399                 return mAudioManager.getStreamMaxVolume(
400                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
401             }
402 
403             CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
404             return group.getMaxGainIndex();
405         }
406     }
407 
408     /**
409      * @see {@link android.car.media.CarAudioManager#getGroupMinVolume(int, int)}
410      */
411     @Override
getGroupMinVolume(int zoneId, int groupId)412     public int getGroupMinVolume(int zoneId, int groupId) {
413         synchronized (mImplLock) {
414             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
415 
416             // For legacy stream type based volume control
417             if (!mUseDynamicRouting) {
418                 return mAudioManager.getStreamMinVolume(
419                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
420             }
421 
422             CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
423             return group.getMinGainIndex();
424         }
425     }
426 
427     /**
428      * @see {@link android.car.media.CarAudioManager#getGroupVolume(int, int)}
429      */
430     @Override
getGroupVolume(int zoneId, int groupId)431     public int getGroupVolume(int zoneId, int groupId) {
432         synchronized (mImplLock) {
433             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
434 
435             // For legacy stream type based volume control
436             if (!mUseDynamicRouting) {
437                 return mAudioManager.getStreamVolume(
438                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
439             }
440 
441             CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
442             return group.getCurrentGainIndex();
443         }
444     }
445 
getCarVolumeGroup(int zoneId, int groupId)446     private CarVolumeGroup getCarVolumeGroup(int zoneId, int groupId) {
447         Objects.requireNonNull(mCarAudioZones);
448         Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
449                 "zoneId out of range: " + zoneId);
450         return mCarAudioZones[zoneId].getVolumeGroup(groupId);
451     }
452 
setupLegacyVolumeChangedListener()453     private void setupLegacyVolumeChangedListener() {
454         IntentFilter intentFilter = new IntentFilter();
455         intentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
456         intentFilter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);
457         mContext.registerReceiver(mLegacyVolumeChangedReceiver, intentFilter);
458     }
459 
generateCarAudioDeviceInfos()460     private List<CarAudioDeviceInfo> generateCarAudioDeviceInfos() {
461         AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
462                 AudioManager.GET_DEVICES_OUTPUTS);
463 
464         return Arrays.stream(deviceInfos)
465                 .filter(info -> info.getType() == AudioDeviceInfo.TYPE_BUS)
466                 .map(CarAudioDeviceInfo::new)
467                 .collect(Collectors.toList());
468     }
469 
getAllInputDevices()470     private AudioDeviceInfo[] getAllInputDevices() {
471         return mAudioManager.getDevices(
472                 AudioManager.GET_DEVICES_INPUTS);
473     }
474 
loadCarAudioConfigurationLocked( List<CarAudioDeviceInfo> carAudioDeviceInfos)475     private CarAudioZone[] loadCarAudioConfigurationLocked(
476             List<CarAudioDeviceInfo> carAudioDeviceInfos) {
477         AudioDeviceInfo[] inputDevices = getAllInputDevices();
478         try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
479             CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mCarAudioSettings,
480                     inputStream, carAudioDeviceInfos, inputDevices);
481             mAudioZoneIdToOccupantZoneIdMapping =
482                     zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping();
483             return zonesHelper.loadAudioZones();
484         } catch (IOException | XmlPullParserException e) {
485             throw new RuntimeException("Failed to parse audio zone configuration", e);
486         }
487     }
488 
loadVolumeGroupConfigurationWithAudioControlLocked( List<CarAudioDeviceInfo> carAudioDeviceInfos)489     private CarAudioZone[] loadVolumeGroupConfigurationWithAudioControlLocked(
490             List<CarAudioDeviceInfo> carAudioDeviceInfos) {
491         AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
492         if (!(audioControlWrapper instanceof AudioControlWrapperV1)) {
493             throw new IllegalStateException(
494                     "Updated version of IAudioControl no longer supports CarAudioZonesHelperLegacy."
495                     + " Please provide car_audio_configuration.xml.");
496         }
497         CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
498                 R.xml.car_volume_groups, carAudioDeviceInfos,
499                 (AudioControlWrapperV1) audioControlWrapper, mCarAudioSettings);
500         return legacyHelper.loadAudioZones();
501     }
502 
loadCarAudioZonesLocked()503     private void loadCarAudioZonesLocked() {
504         List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
505 
506         mCarAudioConfigurationPath = getAudioConfigurationPath();
507         if (mCarAudioConfigurationPath != null) {
508             mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos);
509         } else {
510             mCarAudioZones = loadVolumeGroupConfigurationWithAudioControlLocked(
511                     carAudioDeviceInfos);
512         }
513 
514         CarAudioZonesValidator.validate(mCarAudioZones);
515     }
516 
setupDynamicRoutingLocked()517     private void setupDynamicRoutingLocked() {
518         final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
519         builder.setLooper(Looper.getMainLooper());
520 
521         loadCarAudioZonesLocked();
522 
523         for (CarAudioZone zone : mCarAudioZones) {
524             // Ensure HAL gets our initial value
525             zone.synchronizeCurrentGainIndex();
526             Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
527         }
528 
529         // Setup dynamic routing rules by usage
530         final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
531         dynamicRouting.setupAudioDynamicRouting(builder);
532 
533         // Attach the {@link AudioPolicyVolumeCallback}
534         builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
535 
536         if (sUseCarAudioFocus) {
537             // Configure our AudioPolicy to handle focus events.
538             // This gives us the ability to decide which audio focus requests to accept and bypasses
539             // the framework ducking logic.
540             mFocusHandler = new CarZonesAudioFocus(mAudioManager,
541                     mContext.getPackageManager(),
542                     mCarAudioZones,
543                     mCarAudioSettings, ENABLE_DELAYED_AUDIO_FOCUS);
544             builder.setAudioPolicyFocusListener(mFocusHandler);
545             builder.setIsAudioFocusPolicy(true);
546         }
547 
548         mAudioPolicy = builder.build();
549         if (sUseCarAudioFocus) {
550             // Connect the AudioPolicy and the focus listener
551             mFocusHandler.setOwningPolicy(this, mAudioPolicy);
552         }
553 
554         int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
555         if (r != AudioManager.SUCCESS) {
556             throw new RuntimeException("registerAudioPolicy failed " + r);
557         }
558 
559         setupOccupantZoneInfo();
560     }
561 
setupOccupantZoneInfo()562     private void setupOccupantZoneInfo() {
563         CarOccupantZoneService occupantZoneService;
564         CarOccupantZoneManager occupantZoneManager;
565         SparseIntArray audioZoneIdToOccupantZoneMapping;
566         OccupantZoneConfigChangeListener listener;
567         synchronized (mImplLock) {
568             audioZoneIdToOccupantZoneMapping = mAudioZoneIdToOccupantZoneIdMapping;
569             occupantZoneService = mOccupantZoneService;
570             occupantZoneManager = mOccupantZoneManager;
571             listener = mOccupantZoneConfigChangeListener;
572         }
573         occupantZoneService.setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping);
574         occupantZoneManager.registerOccupantZoneConfigChangeListener(listener);
575     }
576 
setupHalAudioFocusListenerLocked()577     private void setupHalAudioFocusListenerLocked() {
578         AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
579         if (!audioControlWrapper.supportsHalAudioFocus()) {
580             Log.d(CarLog.TAG_AUDIO, "HalAudioFocus is not supported on this device");
581             return;
582         }
583 
584         mHalAudioFocus = new HalAudioFocus(mAudioManager, mAudioControlWrapper, getAudioZoneIds());
585         mHalAudioFocus.registerFocusListener();
586     }
587 
588     /**
589      * Read from {@link #AUDIO_CONFIGURATION_PATHS} respectively.
590      * @return File path of the first hit in {@link #AUDIO_CONFIGURATION_PATHS}
591      */
592     @Nullable
getAudioConfigurationPath()593     private String getAudioConfigurationPath() {
594         for (String path : AUDIO_CONFIGURATION_PATHS) {
595             File configuration = new File(path);
596             if (configuration.exists()) {
597                 return path;
598             }
599         }
600         return null;
601     }
602 
603     @Override
setFadeTowardFront(float value)604     public void setFadeTowardFront(float value) {
605         synchronized (mImplLock) {
606             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
607             getAudioControlWrapperLocked().setFadeTowardFront(value);
608         }
609     }
610 
611     @Override
setBalanceTowardRight(float value)612     public void setBalanceTowardRight(float value) {
613         synchronized (mImplLock) {
614             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
615             getAudioControlWrapperLocked().setBalanceTowardRight(value);
616         }
617     }
618 
619     /**
620      * @return Array of accumulated device addresses, empty array if we found nothing
621      */
622     @Override
getExternalSources()623     public @NonNull String[] getExternalSources() {
624         synchronized (mImplLock) {
625             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
626             List<String> sourceAddresses = new ArrayList<>();
627 
628             AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
629             if (devices.length == 0) {
630                 Log.w(CarLog.TAG_AUDIO, "getExternalSources, no input devices found.");
631             }
632 
633             // Collect the list of non-microphone input ports
634             for (AudioDeviceInfo info : devices) {
635                 switch (info.getType()) {
636                     // TODO:  Can we trim this set down? Especially duplicates like FM vs FM_TUNER?
637                     case AudioDeviceInfo.TYPE_FM:
638                     case AudioDeviceInfo.TYPE_FM_TUNER:
639                     case AudioDeviceInfo.TYPE_TV_TUNER:
640                     case AudioDeviceInfo.TYPE_HDMI:
641                     case AudioDeviceInfo.TYPE_AUX_LINE:
642                     case AudioDeviceInfo.TYPE_LINE_ANALOG:
643                     case AudioDeviceInfo.TYPE_LINE_DIGITAL:
644                     case AudioDeviceInfo.TYPE_USB_ACCESSORY:
645                     case AudioDeviceInfo.TYPE_USB_DEVICE:
646                     case AudioDeviceInfo.TYPE_USB_HEADSET:
647                     case AudioDeviceInfo.TYPE_IP:
648                     case AudioDeviceInfo.TYPE_BUS:
649                         String address = info.getAddress();
650                         if (TextUtils.isEmpty(address)) {
651                             Log.w(CarLog.TAG_AUDIO,
652                                     "Discarded device with empty address, type=" + info.getType());
653                         } else {
654                             sourceAddresses.add(address);
655                         }
656                 }
657             }
658 
659             return sourceAddresses.toArray(new String[0]);
660         }
661     }
662 
663     @Override
createAudioPatch(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)664     public CarAudioPatchHandle createAudioPatch(String sourceAddress,
665             @AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
666         synchronized (mImplLock) {
667             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
668             return createAudioPatchLocked(sourceAddress, usage, gainInMillibels);
669         }
670     }
671 
672     @Override
releaseAudioPatch(CarAudioPatchHandle carPatch)673     public void releaseAudioPatch(CarAudioPatchHandle carPatch) {
674         synchronized (mImplLock) {
675             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
676             releaseAudioPatchLocked(carPatch);
677         }
678     }
679 
createAudioPatchLocked(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)680     private CarAudioPatchHandle createAudioPatchLocked(String sourceAddress,
681             @AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
682         // Find the named source port
683         AudioDeviceInfo sourcePortInfo = null;
684         AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
685         for (AudioDeviceInfo info : deviceInfos) {
686             if (sourceAddress.equals(info.getAddress())) {
687                 // This is the one for which we're looking
688                 sourcePortInfo = info;
689                 break;
690             }
691         }
692         Objects.requireNonNull(sourcePortInfo,
693                 "Specified source is not available: " + sourceAddress);
694 
695         // Find the output port associated with the given carUsage
696         AudioDevicePort sinkPort = Objects.requireNonNull(getAudioPort(usage),
697                 "Sink not available for usage: " + AudioAttributes.usageToString(usage));
698 
699         // {@link android.media.AudioPort#activeConfig()} is valid for mixer port only,
700         // since audio framework has no clue what's active on the device ports.
701         // Therefore we construct an empty / default configuration here, which the audio HAL
702         // implementation should ignore.
703         AudioPortConfig sinkConfig = sinkPort.buildConfig(0,
704                 AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT, null);
705         Log.d(CarLog.TAG_AUDIO, "createAudioPatch sinkConfig: " + sinkConfig);
706 
707         // Configure the source port to match the output port except for a gain adjustment
708         final CarAudioDeviceInfo helper = new CarAudioDeviceInfo(sourcePortInfo);
709         AudioGain audioGain = Objects.requireNonNull(helper.getAudioGain(),
710                 "Gain controller not available for source port");
711 
712         // size of gain values is 1 in MODE_JOINT
713         AudioGainConfig audioGainConfig = audioGain.buildConfig(AudioGain.MODE_JOINT,
714                 audioGain.channelMask(), new int[] { gainInMillibels }, 0);
715         // Construct an empty / default configuration excepts gain config here and it's up to the
716         // audio HAL how to interpret this configuration, which the audio HAL
717         // implementation should ignore.
718         AudioPortConfig sourceConfig = sourcePortInfo.getPort().buildConfig(0,
719                 AudioFormat.CHANNEL_IN_DEFAULT, AudioFormat.ENCODING_DEFAULT, audioGainConfig);
720 
721         // Create an audioPatch to connect the two ports
722         AudioPatch[] patch = new AudioPatch[] { null };
723         int result = AudioManager.createAudioPatch(patch,
724                 new AudioPortConfig[] { sourceConfig },
725                 new AudioPortConfig[] { sinkConfig });
726         if (result != AudioManager.SUCCESS) {
727             throw new RuntimeException("createAudioPatch failed with code " + result);
728         }
729 
730         Objects.requireNonNull(patch[0],
731                 "createAudioPatch didn't provide expected single handle");
732         Log.d(CarLog.TAG_AUDIO, "Audio patch created: " + patch[0]);
733 
734         // Ensure the initial volume on output device port
735         int groupId = getVolumeGroupIdForUsage(CarAudioManager.PRIMARY_AUDIO_ZONE, usage);
736         setGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE, groupId,
737                 getGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE, groupId), 0);
738 
739         return new CarAudioPatchHandle(patch[0]);
740     }
741 
releaseAudioPatchLocked(CarAudioPatchHandle carPatch)742     private void releaseAudioPatchLocked(CarAudioPatchHandle carPatch) {
743         Objects.requireNonNull(carPatch);
744         // NOTE:  AudioPolicyService::removeNotificationClient will take care of this automatically
745         //        if the client that created a patch quits.
746         ArrayList<AudioPatch> patches = new ArrayList<>();
747         int result = mAudioManager.listAudioPatches(patches);
748         if (result != AudioManager.SUCCESS) {
749             throw new RuntimeException("listAudioPatches failed with code " + result);
750         }
751 
752         // Look for a patch that matches the provided user side handle
753         for (AudioPatch patch : patches) {
754             if (carPatch.represents(patch)) {
755                 // Found it!
756                 result = AudioManager.releaseAudioPatch(patch);
757                 if (result != AudioManager.SUCCESS) {
758                     throw new RuntimeException("releaseAudioPatch failed with code " + result);
759                 }
760                 return;
761             }
762         }
763 
764         // If we didn't find a match, then something went awry, but it's probably not fatal...
765         Log.e(CarLog.TAG_AUDIO, "releaseAudioPatch found no match for " + carPatch);
766     }
767 
768     @Override
getVolumeGroupCount(int zoneId)769     public int getVolumeGroupCount(int zoneId) {
770         synchronized (mImplLock) {
771             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
772             // For legacy stream type based volume control
773             if (!mUseDynamicRouting) return CarAudioDynamicRouting.STREAM_TYPES.length;
774 
775             Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
776                     "zoneId out of range: " + zoneId);
777             return mCarAudioZones[zoneId].getVolumeGroupCount();
778         }
779     }
780 
781     @Override
getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)782     public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
783         synchronized (mImplLock) {
784             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
785 
786             if (!mUseDynamicRouting) {
787                 for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPE_USAGES.length; i++) {
788                     if (usage == CarAudioDynamicRouting.STREAM_TYPE_USAGES[i]) {
789                         return i;
790                     }
791                 }
792 
793                 return INVALID_VOLUME_GROUP_ID;
794             }
795 
796             Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
797                     "zoneId out of range: " + zoneId);
798 
799             @AudioContext int audioContext = CarAudioContext.getContextForUsage(usage);
800             return getVolumeGroupIdForAudioContextLocked(zoneId, audioContext);
801         }
802     }
803 
getVolumeGroupIdForAudioContextLocked(int zoneId, @AudioContext int audioContext)804     private int getVolumeGroupIdForAudioContextLocked(int zoneId, @AudioContext int audioContext) {
805         CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
806         for (int i = 0; i < groups.length; i++) {
807             int[] groupAudioContexts = groups[i].getContexts();
808             for (int groupAudioContext : groupAudioContexts) {
809                 if (audioContext == groupAudioContext) {
810                     return i;
811                 }
812             }
813         }
814         return INVALID_VOLUME_GROUP_ID;
815     }
816 
817     @Override
getUsagesForVolumeGroupId(int zoneId, int groupId)818     public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
819         synchronized (mImplLock) {
820             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
821 
822             // For legacy stream type based volume control
823             if (!mUseDynamicRouting) {
824                 return new int[] { CarAudioDynamicRouting.STREAM_TYPE_USAGES[groupId] };
825             }
826 
827             CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
828             Set<Integer> contexts =
829                     Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
830             final List<Integer> usages = new ArrayList<>();
831             for (@AudioContext int context : contexts) {
832                 int[] usagesForContext = CarAudioContext.getUsagesForContext(context);
833                 for (@AudioAttributes.AttributeUsage int usage : usagesForContext) {
834                     usages.add(usage);
835                 }
836             }
837             return usages.stream().mapToInt(i -> i).toArray();
838         }
839     }
840 
841     /**
842      * Gets the ids of all available audio zones
843      *
844      * @return Array of available audio zones ids
845      */
846     @Override
getAudioZoneIds()847     public @NonNull int[] getAudioZoneIds() {
848         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
849         requireDynamicRouting();
850         synchronized (mImplLock) {
851             return Arrays.stream(mCarAudioZones).mapToInt(CarAudioZone::getId).toArray();
852         }
853     }
854 
855     /**
856      * Gets the audio zone id currently mapped to uid,
857      *
858      * <p><b>Note:</b> Will use uid mapping first, followed by uid's {@userId} mapping.
859      * defaults to PRIMARY_AUDIO_ZONE if no mapping exist
860      *
861      * @param uid The uid
862      * @return zone id mapped to uid
863      */
864     @Override
getZoneIdForUid(int uid)865     public int getZoneIdForUid(int uid) {
866         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
867         requireDynamicRouting();
868         synchronized (mImplLock) {
869             if (mUidToZoneMap.containsKey(uid)) {
870                 return mUidToZoneMap.get(uid);
871             }
872             int userId = UserHandle.getUserId(uid);
873             return getZoneIdForUserIdLocked(userId);
874         }
875     }
876 
getZoneIdForUserIdLocked(@serIdInt int userId)877     private int getZoneIdForUserIdLocked(@UserIdInt int userId) {
878         int audioZoneId = mOccupantZoneService.getAudioZoneIdForOccupant(
879                 mOccupantZoneService.getOccupantZoneIdForUserId(userId));
880         if (audioZoneId != CarAudioManager.INVALID_AUDIO_ZONE) {
881             return audioZoneId;
882         }
883         Log.w(CarLog.TAG_AUDIO,
884                 "getZoneIdForUid userId " + userId
885                         + " does not have a zone. Defaulting to PRIMARY_AUDIO_ZONE:"
886                         + CarAudioManager.PRIMARY_AUDIO_ZONE);
887         return CarAudioManager.PRIMARY_AUDIO_ZONE;
888     }
889 
890     /**
891      * Maps the audio zone id to uid
892      *
893      * @param zoneId The audio zone id
894      * @param uid The uid to map
895      * @return true if the device affinities, for devices in zone, are successfully set
896      */
897     @Override
setZoneIdForUid(int zoneId, int uid)898     public boolean setZoneIdForUid(int zoneId, int uid) {
899         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
900         requireDynamicRouting();
901         Preconditions.checkArgument(isAudioZoneIdValid(zoneId),
902                 "Invalid audio zone id %d", zoneId);
903         synchronized (mImplLock) {
904             Log.i(CarLog.TAG_AUDIO, "setZoneIdForUid Calling uid "
905                     + uid + " mapped to : "
906                     + zoneId);
907 
908             // Figure out if anything is currently holding focus,
909             // This will change the focus to transient loss while we are switching zones
910             Integer currentZoneId = mUidToZoneMap.get(uid);
911             ArrayList<AudioFocusInfo> currentFocusHoldersForUid = new ArrayList<>();
912             ArrayList<AudioFocusInfo> currentFocusLosersForUid = new ArrayList<>();
913             if (currentZoneId != null) {
914                 currentFocusHoldersForUid = mFocusHandler.getAudioFocusHoldersForUid(uid,
915                         currentZoneId.intValue());
916                 currentFocusLosersForUid = mFocusHandler.getAudioFocusLosersForUid(uid,
917                         currentZoneId.intValue());
918                 if (!currentFocusHoldersForUid.isEmpty() || !currentFocusLosersForUid.isEmpty()) {
919                     // Order matters here: Remove the focus losers first
920                     // then do the current holder to prevent loser from popping up while
921                     // the focus is being remove for current holders
922                     // Remove focus for current focus losers
923                     mFocusHandler.transientlyLoseInFocusInZone(currentFocusLosersForUid,
924                             currentZoneId.intValue());
925                     // Remove focus for current holders
926                     mFocusHandler.transientlyLoseInFocusInZone(currentFocusHoldersForUid,
927                             currentZoneId.intValue());
928                 }
929             }
930 
931             // if the current uid is in the list
932             // remove it from the list
933 
934             if (checkAndRemoveUidLocked(uid)) {
935                 if (setZoneIdForUidNoCheckLocked(zoneId, uid)) {
936                     // Order matters here: Regain focus for
937                     // Previously lost focus holders then regain
938                     // focus for holders that had it last
939                     // Regain focus for the focus losers from previous zone
940                     if (!currentFocusLosersForUid.isEmpty()) {
941                         regainAudioFocusLocked(currentFocusLosersForUid, zoneId);
942                     }
943                     // Regain focus for the focus holders from previous zone
944                     if (!currentFocusHoldersForUid.isEmpty()) {
945                         regainAudioFocusLocked(currentFocusHoldersForUid, zoneId);
946                     }
947                     return true;
948                 }
949             }
950             return false;
951         }
952     }
953 
954     @Override
getOutputDeviceAddressForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)955     public String getOutputDeviceAddressForUsage(int zoneId,
956             @AudioAttributes.AttributeUsage int usage) {
957         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
958         requireDynamicRouting();
959         Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
960                 "zoneId (" + zoneId + ")");
961         int contextForUsage = CarAudioContext.getContextForUsage(usage);
962         Preconditions.checkArgument(contextForUsage != CarAudioContext.INVALID,
963                 "Invalid audio attribute usage %d", usage);
964         return mCarAudioZones[zoneId].getAddressForContext(contextForUsage);
965     }
966 
967     /**
968      * Regain focus for the focus list passed in
969      * @param afiList focus info list to regain
970      * @param zoneId zone id where the focus holder belong
971      */
regainAudioFocusLocked(ArrayList<AudioFocusInfo> afiList, int zoneId)972     void regainAudioFocusLocked(ArrayList<AudioFocusInfo> afiList, int zoneId) {
973         for (AudioFocusInfo info : afiList) {
974             if (mFocusHandler.reevaluateAndRegainAudioFocus(info)
975                     != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
976                 Log.i(CarLog.TAG_AUDIO,
977                         " Focus could not be granted for entry "
978                                 + info.getClientId()
979                                 + " uid " + info.getClientUid()
980                                 + " in zone " + zoneId);
981             }
982         }
983     }
984 
985     /**
986      * Removes the current mapping of the uid, focus will be lost in zone
987      * @param uid The uid to remove
988      * return true if all the devices affinities currently
989      *            mapped to uid are successfully removed
990      */
991     @Override
clearZoneIdForUid(int uid)992     public boolean clearZoneIdForUid(int uid) {
993         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
994         requireDynamicRouting();
995         synchronized (mImplLock) {
996             return checkAndRemoveUidLocked(uid);
997         }
998     }
999 
1000     /**
1001      * Sets the zone id for uid
1002      * @param zoneId zone id to map to uid
1003      * @param uid uid to map
1004      * @return true if setting uid device affinity is successful
1005      */
setZoneIdForUidNoCheckLocked(int zoneId, int uid)1006     private boolean setZoneIdForUidNoCheckLocked(int zoneId, int uid) {
1007         Log.d(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Calling uid "
1008                 + uid + " mapped to " + zoneId);
1009         //Request to add uid device affinity
1010         if (mAudioPolicy.setUidDeviceAffinity(uid, mCarAudioZones[zoneId].getAudioDeviceInfos())) {
1011             // TODO do not store uid mapping here instead use the uid
1012             //  device affinity in audio policy when available
1013             mUidToZoneMap.put(uid, zoneId);
1014             return true;
1015         }
1016         Log.w(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Failed set device affinity for uid "
1017                 + uid + " in zone " + zoneId);
1018         return false;
1019     }
1020 
1021     /**
1022      * Check if uid is attached to a zone and remove it
1023      * @param uid unique id to remove
1024      * @return true if the uid was successfully removed or mapping was not assigned
1025      */
checkAndRemoveUidLocked(int uid)1026     private boolean checkAndRemoveUidLocked(int uid) {
1027         Integer zoneId = mUidToZoneMap.get(uid);
1028         if (zoneId != null) {
1029             Log.i(CarLog.TAG_AUDIO, "checkAndRemoveUid removing Calling uid "
1030                     + uid + " from zone " + zoneId);
1031             if (mAudioPolicy.removeUidDeviceAffinity(uid)) {
1032                 // TODO use the uid device affinity in audio policy when available
1033                 mUidToZoneMap.remove(uid);
1034                 return true;
1035             }
1036             //failed to remove device affinity from zone devices
1037             Log.w(CarLog.TAG_AUDIO,
1038                     "checkAndRemoveUid Failed remove device affinity for uid "
1039                             + uid + " in zone " +  zoneId);
1040             return false;
1041         }
1042         return true;
1043     }
1044 
1045     @Override
registerVolumeCallback(@onNull IBinder binder)1046     public void registerVolumeCallback(@NonNull IBinder binder) {
1047         synchronized (mImplLock) {
1048             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1049             mCarVolumeCallbackHandler.registerCallback(binder);
1050         }
1051     }
1052 
1053     @Override
unregisterVolumeCallback(@onNull IBinder binder)1054     public void unregisterVolumeCallback(@NonNull IBinder binder) {
1055         synchronized (mImplLock) {
1056             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1057             mCarVolumeCallbackHandler.unregisterCallback(binder);
1058         }
1059     }
1060 
1061     @Override
getInputDevicesForZoneId(int zoneId)1062     public @NonNull List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId) {
1063         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
1064         requireDynamicRouting();
1065         Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
1066                 "zoneId out of range: " + zoneId);
1067         for (CarAudioZone zone : mCarAudioZones) {
1068             if (zone.getId() == zoneId) {
1069                 return zone.getInputAudioDevices();
1070             }
1071         }
1072         throw new IllegalArgumentException("zoneId does not exist" + zoneId);
1073     }
1074 
enforcePermission(String permissionName)1075     private void enforcePermission(String permissionName) {
1076         if (mContext.checkCallingOrSelfPermission(permissionName)
1077                 != PackageManager.PERMISSION_GRANTED) {
1078             throw new SecurityException("requires permission " + permissionName);
1079         }
1080     }
1081 
requireDynamicRouting()1082     private void requireDynamicRouting() {
1083         Preconditions.checkState(mUseDynamicRouting, "Dynamic routing is required");
1084     }
1085 
1086     /**
1087      * @return {@link AudioDevicePort} that handles the given car audio usage.
1088      * Multiple usages may share one {@link AudioDevicePort}
1089      */
getAudioPort(@udioAttributes.AttributeUsage int usage)1090     private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) {
1091         int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
1092         final int groupId = getVolumeGroupIdForUsage(zoneId, usage);
1093         final CarVolumeGroup group = Objects.requireNonNull(
1094                 mCarAudioZones[zoneId].getVolumeGroup(groupId),
1095                 "Can not find CarVolumeGroup by usage: "
1096                         + AudioAttributes.usageToString(usage));
1097         return group.getAudioDevicePortForContext(CarAudioContext.getContextForUsage(usage));
1098     }
1099 
getSuggestedAudioContext()1100     private @AudioContext int getSuggestedAudioContext() {
1101         @CallState int callState = mTelephonyManager.getCallState();
1102         List<AudioPlaybackConfiguration> configurations =
1103                 mAudioManager.getActivePlaybackConfigurations();
1104         return CarVolume.getSuggestedAudioContext(configurations, callState);
1105     }
1106 
1107     /**
1108      * Gets volume group by a given legacy stream type
1109      * @param streamType Legacy stream type such as {@link AudioManager#STREAM_MUSIC}
1110      * @return volume group id mapped from stream type
1111      */
getVolumeGroupIdForStreamType(int streamType)1112     private int getVolumeGroupIdForStreamType(int streamType) {
1113         int groupId = INVALID_VOLUME_GROUP_ID;
1114         for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPES.length; i++) {
1115             if (streamType == CarAudioDynamicRouting.STREAM_TYPES[i]) {
1116                 groupId = i;
1117                 break;
1118             }
1119         }
1120         return groupId;
1121     }
1122 
handleOccupantZoneUserChanged()1123     private void handleOccupantZoneUserChanged() {
1124         int driverUserId = mOccupantZoneService.getDriverUserId();
1125         synchronized (mImplLock) {
1126             if (!isOccupantZoneMappingAvailable()) {
1127                 //No occupant zone to audio zone mapping, re-adjust to settings driver.
1128                 for (int index = 0; index < mCarAudioZones.length; index++) {
1129                     CarAudioZone zone = mCarAudioZones[index];
1130                     zone.updateVolumeGroupsForUser(driverUserId);
1131                     mFocusHandler.updateUserForZoneId(zone.getId(), driverUserId);
1132                 }
1133                 return;
1134             }
1135             int occupantZoneForDriver =  getOccupantZoneIdForDriver();
1136             for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
1137                 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
1138                 int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId);
1139                 updateUserForOccupantZoneLocked(occupantZoneId, audioZoneId, driverUserId,
1140                         occupantZoneForDriver);
1141             }
1142         }
1143     }
1144 
isOccupantZoneMappingAvailable()1145     private boolean isOccupantZoneMappingAvailable() {
1146         return mAudioZoneIdToOccupantZoneIdMapping.size() > 0;
1147     }
1148 
updateUserForOccupantZoneLocked(int occupantZoneId, int audioZoneId, @UserIdInt int driverUserId, int occupantZoneForDriver)1149     private void updateUserForOccupantZoneLocked(int occupantZoneId, int audioZoneId,
1150             @UserIdInt int driverUserId, int occupantZoneForDriver) {
1151         CarAudioZone zone = getAudioZoneForZoneIdLocked(audioZoneId);
1152         int userId = mOccupantZoneService.getUserForOccupant(occupantZoneId);
1153         int prevUserId = getUserIdForZoneLocked(audioZoneId);
1154 
1155         Objects.requireNonNull(zone, () ->
1156                 "setUserIdDeviceAffinity for userId " + userId
1157                         + " in zone " + audioZoneId + " Failed, invalid zone.");
1158 
1159         // user in occupant zone has not changed
1160         if (userId == prevUserId) {
1161             return;
1162         }
1163         // If the user has changed, be sure to remove from current routing
1164         // This would be true even if the new user is UserHandle.USER_NULL,
1165         // as that indicates the user has logged out.
1166         removeUserIdDeviceAffinitiesLocked(prevUserId);
1167 
1168         if (userId == UserHandle.USER_NULL) {
1169             // Reset zone back to driver user id
1170             resetZoneToDefaultUser(zone, driverUserId);
1171             return;
1172         }
1173 
1174         // Only set user id device affinities for driver when it is the driver's occupant zone
1175         if (userId != driverUserId || occupantZoneId == occupantZoneForDriver) {
1176             setUserIdDeviceAffinitiesLocked(zone, userId, audioZoneId);
1177             mAudioZoneIdToUserIdMapping.put(audioZoneId, userId);
1178         }
1179         zone.updateVolumeGroupsForUser(userId);
1180         mFocusHandler.updateUserForZoneId(audioZoneId, userId);
1181     }
1182 
getOccupantZoneIdForDriver()1183     private int getOccupantZoneIdForDriver() {
1184         List<CarOccupantZoneManager.OccupantZoneInfo> occupantZoneInfos =
1185                 mOccupantZoneManager.getAllOccupantZones();
1186         for (CarOccupantZoneManager.OccupantZoneInfo info: occupantZoneInfos) {
1187             if (info.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
1188                 return info.zoneId;
1189             }
1190         }
1191         return CarOccupantZoneManager.OccupantZoneInfo.INVALID_ZONE_ID;
1192     }
1193 
setUserIdDeviceAffinitiesLocked(CarAudioZone zone, @UserIdInt int userId, int audioZoneId)1194     private void setUserIdDeviceAffinitiesLocked(CarAudioZone zone, @UserIdInt int userId,
1195             int audioZoneId) {
1196         if (!mAudioPolicy.setUserIdDeviceAffinity(userId, zone.getAudioDeviceInfos())) {
1197             throw new IllegalStateException(String.format(
1198                     "setUserIdDeviceAffinity for userId %d in zone %d Failed,"
1199                             + " could not set audio routing.",
1200                     userId, audioZoneId));
1201         }
1202     }
1203 
resetZoneToDefaultUser(CarAudioZone zone, @UserIdInt int driverUserId)1204     private void resetZoneToDefaultUser(CarAudioZone zone, @UserIdInt int driverUserId) {
1205         resetCarZonesAudioFocus(zone.getId(), driverUserId);
1206         zone.updateVolumeGroupsForUser(driverUserId);
1207     }
1208 
resetCarZonesAudioFocus(int audioZoneId, @UserIdInt int driverUserId)1209     private void resetCarZonesAudioFocus(int audioZoneId, @UserIdInt int driverUserId) {
1210         mFocusHandler.updateUserForZoneId(audioZoneId, driverUserId);
1211     }
1212 
getAudioZoneForZoneIdLocked(int audioZoneId)1213     private CarAudioZone getAudioZoneForZoneIdLocked(int audioZoneId) {
1214         for (CarAudioZone zone : mCarAudioZones) {
1215             if (zone.getId() == audioZoneId) {
1216                 return zone;
1217             }
1218         }
1219         return null;
1220     }
1221 
removeUserIdDeviceAffinitiesLocked(@serIdInt int userId)1222     private void removeUserIdDeviceAffinitiesLocked(@UserIdInt int userId) {
1223         if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1224             Log.d(CarLog.TAG_AUDIO,
1225                     "removeUserIdDeviceAffinities(" + userId + ") Succeeded");
1226         }
1227         if (userId == UserHandle.USER_NULL) {
1228             return;
1229         }
1230         if (!mAudioPolicy.removeUserIdDeviceAffinity(userId)) {
1231             Log.e(CarLog.TAG_AUDIO, "removeUserIdDeviceAffinities(" + userId + ") Failed");
1232             return;
1233         }
1234     }
1235 
getUserIdForZoneLocked(int audioZoneId)1236     private @UserIdInt int getUserIdForZoneLocked(int audioZoneId) {
1237         return mAudioZoneIdToUserIdMapping.get(audioZoneId, UserHandle.USER_NULL);
1238     }
1239 
getAudioControlWrapperLocked()1240     private AudioControlWrapper getAudioControlWrapperLocked() {
1241         if (mAudioControlWrapper == null) {
1242             mAudioControlWrapper = AudioControlFactory.newAudioControl();
1243             mAudioControlWrapper.linkToDeath(this::resetHalAudioFocus);
1244         }
1245         return mAudioControlWrapper;
1246     }
1247 
resetHalAudioFocus()1248     private void resetHalAudioFocus() {
1249         if (mHalAudioFocus != null) {
1250             mHalAudioFocus.reset();
1251             mHalAudioFocus.registerFocusListener();
1252         }
1253     }
1254 
isAudioZoneIdValid(int zoneId)1255     boolean isAudioZoneIdValid(int zoneId) {
1256         for (CarAudioZone zone : mCarAudioZones) {
1257             if (zone.getId() == zoneId) {
1258                 return true;
1259             }
1260         }
1261         return false;
1262     }
1263 
1264     private class CarAudioOccupantConfigChangeListener implements OccupantZoneConfigChangeListener {
1265         @Override
onOccupantZoneConfigChanged(int flags)1266         public void onOccupantZoneConfigChanged(int flags) {
1267             if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1268                 Log.d(CarLog.TAG_AUDIO,
1269                         "onOccupantZoneConfigChanged(" + flags + ")");
1270             }
1271             if (((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER)
1272                     == CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER)
1273                     || ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY)
1274                     == CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY)) {
1275                 handleOccupantZoneUserChanged();
1276             }
1277         }
1278     }
1279 }
1280