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