• 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.AUDIO_FEATURE_DYNAMIC_ROUTING;
19 import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING;
20 import static android.car.media.CarAudioManager.CarAudioFeature;
21 import static android.car.media.CarAudioManager.INVALID_VOLUME_GROUP_ID;
22 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
23 import static android.media.AudioManager.FLAG_PLAY_SOUND;
24 
25 import static com.android.car.audio.CarVolume.VERSION_TWO;
26 import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_DUCKING;
27 import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_FOCUS;
28 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.annotation.UserIdInt;
33 import android.car.Car;
34 import android.car.CarOccupantZoneManager;
35 import android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener;
36 import android.car.media.CarAudioManager;
37 import android.car.media.CarAudioPatchHandle;
38 import android.car.media.ICarAudio;
39 import android.car.media.ICarVolumeCallback;
40 import android.content.BroadcastReceiver;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.content.IntentFilter;
44 import android.content.pm.PackageManager;
45 import android.media.AudioAttributes;
46 import android.media.AudioAttributes.AttributeSystemUsage;
47 import android.media.AudioAttributes.AttributeUsage;
48 import android.media.AudioDeviceAttributes;
49 import android.media.AudioDeviceInfo;
50 import android.media.AudioDevicePort;
51 import android.media.AudioFocusInfo;
52 import android.media.AudioFormat;
53 import android.media.AudioGain;
54 import android.media.AudioGainConfig;
55 import android.media.AudioManager;
56 import android.media.AudioPatch;
57 import android.media.AudioPlaybackConfiguration;
58 import android.media.AudioPortConfig;
59 import android.media.audiopolicy.AudioPolicy;
60 import android.os.IBinder;
61 import android.os.Looper;
62 import android.os.SystemClock;
63 import android.os.UserHandle;
64 import android.telephony.Annotation.CallState;
65 import android.telephony.TelephonyManager;
66 import android.text.TextUtils;
67 import android.util.IndentingPrintWriter;
68 import android.util.Log;
69 import android.util.Slog;
70 import android.util.SparseArray;
71 import android.util.SparseIntArray;
72 import android.view.KeyEvent;
73 
74 import com.android.car.CarLocalServices;
75 import com.android.car.CarLog;
76 import com.android.car.CarOccupantZoneService;
77 import com.android.car.CarServiceBase;
78 import com.android.car.R;
79 import com.android.car.audio.CarAudioContext.AudioContext;
80 import com.android.car.audio.hal.AudioControlFactory;
81 import com.android.car.audio.hal.AudioControlWrapper;
82 import com.android.car.audio.hal.AudioControlWrapperV1;
83 import com.android.car.audio.hal.HalAudioFocus;
84 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
85 import com.android.internal.annotations.GuardedBy;
86 import com.android.internal.util.Preconditions;
87 import com.android.server.utils.Slogf;
88 
89 import org.xmlpull.v1.XmlPullParserException;
90 
91 import java.io.File;
92 import java.io.FileInputStream;
93 import java.io.IOException;
94 import java.io.InputStream;
95 import java.util.ArrayList;
96 import java.util.Arrays;
97 import java.util.HashMap;
98 import java.util.HashSet;
99 import java.util.List;
100 import java.util.Map;
101 import java.util.Objects;
102 import java.util.Set;
103 import java.util.stream.Collectors;
104 
105 /**
106  * Service responsible for interaction with car's audio system.
107  */
108 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
109 
110     // Enable to allowed for delayed audio focus in car audio service.
111     private static final boolean ENABLE_DELAYED_AUDIO_FOCUS = true;
112 
113     static final @AttributeUsage int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
114     static final @AudioContext int DEFAULT_AUDIO_CONTEXT = CarAudioContext.getContextForUsage(
115             CarAudioService.DEFAULT_AUDIO_USAGE);
116 
117     // CarAudioService reads configuration from the following paths respectively.
118     // If the first one is found, all others are ignored.
119     // If no one is found, it fallbacks to car_volume_groups.xml resource file.
120     private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {
121             "/vendor/etc/car_audio_configuration.xml",
122             "/system/etc/car_audio_configuration.xml"
123     };
124 
125     private static final @AttributeSystemUsage int[] SYSTEM_USAGES = new int[] {
126             AudioAttributes.USAGE_CALL_ASSISTANT,
127             AudioAttributes.USAGE_EMERGENCY,
128             AudioAttributes.USAGE_SAFETY,
129             AudioAttributes.USAGE_VEHICLE_STATUS,
130             AudioAttributes.USAGE_ANNOUNCEMENT
131     };
132 
133     private final Object mImplLock = new Object();
134 
135     private final Context mContext;
136     private final TelephonyManager mTelephonyManager;
137     private final AudioManager mAudioManager;
138     private final boolean mUseDynamicRouting;
139     private final boolean mUseCarVolumeGroupMuting;
140     private final boolean mUseHalDuckingSignals;
141     private final @CarVolume.CarVolumeListVersion int mAudioVolumeAdjustmentContextsVersion;
142     private final boolean mPersistMasterMuteState;
143     private final CarAudioSettings mCarAudioSettings;
144     private final CarVolume mCarVolume;
145     private final int mKeyEventTimeoutMs;
146     private AudioControlWrapper mAudioControlWrapper;
147     private CarDucking mCarDucking;
148     private CarVolumeGroupMuting mCarVolumeGroupMuting;
149     private HalAudioFocus mHalAudioFocus;
150 
151     private CarOccupantZoneService mOccupantZoneService;
152 
153     private CarOccupantZoneManager mOccupantZoneManager;
154 
155     /**
156      * Simulates {@link ICarVolumeCallback} when it's running in legacy mode.
157      * This receiver assumes the intent is sent to {@link CarAudioManager#PRIMARY_AUDIO_ZONE}.
158      */
159     private final BroadcastReceiver mLegacyVolumeChangedReceiver = new BroadcastReceiver() {
160         @Override
161         public void onReceive(Context context, Intent intent) {
162             final int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
163             switch (intent.getAction()) {
164                 case AudioManager.VOLUME_CHANGED_ACTION:
165                     int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
166                     int groupId = getVolumeGroupIdForStreamType(streamType);
167                     if (groupId == -1) {
168                         Slog.w(CarLog.TAG_AUDIO, "Unknown stream type: " + streamType);
169                     } else {
170                         callbackGroupVolumeChange(zoneId, groupId, 0);
171                     }
172                     break;
173                 case AudioManager.MASTER_MUTE_CHANGED_ACTION:
174                     callbackMasterMuteChange(zoneId, 0);
175                     break;
176             }
177         }
178     };
179 
180     private AudioPolicy mAudioPolicy;
181     private CarZonesAudioFocus mFocusHandler;
182     private String mCarAudioConfigurationPath;
183     private SparseIntArray mAudioZoneIdToOccupantZoneIdMapping;
184     @GuardedBy("mImplLock")
185     private SparseArray<CarAudioZone> mCarAudioZones;
186     private final CarVolumeCallbackHandler mCarVolumeCallbackHandler;
187     private final SparseIntArray mAudioZoneIdToUserIdMapping;
188     private final SystemClockWrapper mClock = new SystemClockWrapper();
189 
190 
191     // TODO do not store uid mapping here instead use the uid
192     //  device affinity in audio policy when available
193     private Map<Integer, Integer> mUidToZoneMap;
194     private OccupantZoneConfigChangeListener
195             mOccupantZoneConfigChangeListener = new CarAudioOccupantConfigChangeListener();
196     private CarAudioPlaybackCallback mCarAudioPlaybackCallback;
197     private CarAudioPowerListener mCarAudioPowerListener;
198 
CarAudioService(Context context)199     public CarAudioService(Context context) {
200         mContext = context;
201         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
202         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
203 
204         mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
205         mKeyEventTimeoutMs =
206                 mContext.getResources().getInteger(R.integer.audioVolumeKeyEventTimeoutMs);
207         mUseHalDuckingSignals = mContext.getResources().getBoolean(
208                 R.bool.audioUseHalDuckingSignals);
209 
210         mUidToZoneMap = new HashMap<>();
211         mCarVolumeCallbackHandler = new CarVolumeCallbackHandler();
212         mCarAudioSettings = new CarAudioSettings(mContext.getContentResolver());
213         mAudioZoneIdToUserIdMapping = new SparseIntArray();
214         mAudioVolumeAdjustmentContextsVersion =
215                 mContext.getResources().getInteger(R.integer.audioVolumeAdjustmentContextsVersion);
216         mCarVolume = new CarVolume(mClock,
217                 mAudioVolumeAdjustmentContextsVersion, mKeyEventTimeoutMs);
218         boolean useCarVolumeGroupMuting = mUseDynamicRouting && mContext.getResources().getBoolean(
219                 R.bool.audioUseCarVolumeGroupMuting);
220         if (mAudioVolumeAdjustmentContextsVersion != VERSION_TWO && useCarVolumeGroupMuting) {
221             throw new IllegalArgumentException("audioUseCarVolumeGroupMuting is enabled but "
222                     + "this requires audioVolumeAdjustmentContextsVersion 2,"
223                     + " instead version " + mAudioVolumeAdjustmentContextsVersion + " was found");
224         }
225         mUseCarVolumeGroupMuting = useCarVolumeGroupMuting;
226         mPersistMasterMuteState = !mUseCarVolumeGroupMuting && mContext.getResources().getBoolean(
227                 R.bool.audioPersistMasterMuteState);
228     }
229 
230     /**
231      * Dynamic routing and volume groups are set only if
232      * {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
233      */
234     @Override
init()235     public void init() {
236         synchronized (mImplLock) {
237             mOccupantZoneService = CarLocalServices.getService(CarOccupantZoneService.class);
238             Car car = new Car(mContext, /* service= */null, /* handler= */ null);
239             mOccupantZoneManager = new CarOccupantZoneManager(car, mOccupantZoneService);
240             if (mUseDynamicRouting) {
241                 setupDynamicRoutingLocked();
242                 setupHalAudioFocusListenerLocked();
243                 setupAudioConfigurationCallbackLocked();
244                 setupPowerPolicyListener();
245             } else {
246                 Slog.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
247                 setupLegacyVolumeChangedListener();
248             }
249 
250             mAudioManager.setSupportedSystemUsages(SYSTEM_USAGES);
251         }
252 
253         restoreMasterMuteState();
254     }
255 
setupPowerPolicyListener()256     private void setupPowerPolicyListener() {
257         mCarAudioPowerListener = CarAudioPowerListener.newCarAudioPowerListener(this);
258         mCarAudioPowerListener.startListeningForPolicyChanges();
259     }
260 
restoreMasterMuteState()261     private void restoreMasterMuteState() {
262         if (mUseCarVolumeGroupMuting) {
263             return;
264         }
265         // Restore master mute state if applicable
266         if (mPersistMasterMuteState) {
267             boolean storedMasterMute = mCarAudioSettings.getMasterMute();
268             setMasterMute(storedMasterMute, 0);
269         }
270     }
271 
272     @Override
release()273     public void release() {
274         synchronized (mImplLock) {
275             if (mUseDynamicRouting) {
276                 if (mAudioPolicy != null) {
277                     mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
278                     mAudioPolicy = null;
279                     mFocusHandler.setOwningPolicy(null, null);
280                     mFocusHandler = null;
281                 }
282             } else {
283                 mContext.unregisterReceiver(mLegacyVolumeChangedReceiver);
284             }
285 
286             mCarVolumeCallbackHandler.release();
287 
288             if (mHalAudioFocus != null) {
289                 mHalAudioFocus.unregisterFocusListener();
290             }
291 
292             if (mAudioControlWrapper != null) {
293                 mAudioControlWrapper.unlinkToDeath();
294                 mAudioControlWrapper = null;
295             }
296 
297             if (mCarAudioPowerListener != null) {
298                 mCarAudioPowerListener.stopListeningForPolicyChanges();
299             }
300         }
301     }
302 
303     @Override
304     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)305     public void dump(IndentingPrintWriter writer) {
306         writer.println("*CarAudioService*");
307         writer.increaseIndent();
308 
309         writer.println("Configurations:");
310         writer.increaseIndent();
311         writer.printf("Run in legacy mode? %b\n", !mUseDynamicRouting);
312         writer.printf("Persist master mute state? %b\n", mPersistMasterMuteState);
313         writer.printf("Use hal ducking signals %b\n", mUseHalDuckingSignals);
314         writer.printf("Volume context priority list version: %d\n",
315                 mAudioVolumeAdjustmentContextsVersion);
316         writer.printf("Volume key event timeout ms: %d\n", mKeyEventTimeoutMs);
317         if (mCarAudioConfigurationPath != null) {
318             writer.printf("Car audio configuration path: %s\n", mCarAudioConfigurationPath);
319         }
320         writer.decreaseIndent();
321         writer.println();
322 
323         writer.println("Current State:");
324         writer.increaseIndent();
325         writer.printf("Master muted? %b\n", mAudioManager.isMasterMute());
326         if (mCarAudioPowerListener != null) {
327             writer.printf("Audio enabled? %b\n", mCarAudioPowerListener.isAudioEnabled());
328         }
329         writer.decreaseIndent();
330         writer.println();
331 
332         if (mUseDynamicRouting) {
333             writer.printf("Volume Group Mute Enabled? %b\n", mUseCarVolumeGroupMuting);
334             synchronized (mImplLock) {
335                 for (int i = 0; i < mCarAudioZones.size(); i++) {
336                     CarAudioZone zone = mCarAudioZones.valueAt(i);
337                     zone.dump(writer);
338                 }
339             }
340             writer.println();
341             writer.println("UserId to Zone Mapping:");
342             writer.increaseIndent();
343             for (int index = 0; index < mAudioZoneIdToUserIdMapping.size(); index++) {
344                 int audioZoneId = mAudioZoneIdToUserIdMapping.keyAt(index);
345                 writer.printf("UserId %d mapped to zone %d\n",
346                         mAudioZoneIdToUserIdMapping.get(audioZoneId),
347                         audioZoneId);
348             }
349             writer.decreaseIndent();
350             writer.println();
351             writer.println("Audio Zone to Occupant Zone Mapping:");
352             writer.increaseIndent();
353             for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
354                 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
355                 writer.printf("AudioZoneId %d mapped to OccupantZoneId %d\n", audioZoneId,
356                         mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId));
357             }
358             writer.decreaseIndent();
359             writer.println();
360             writer.println("UID to Zone Mapping:");
361             writer.increaseIndent();
362             for (int callingId : mUidToZoneMap.keySet()) {
363                 writer.printf("UID %d mapped to zone %d\n",
364                         callingId,
365                         mUidToZoneMap.get(callingId));
366             }
367             writer.decreaseIndent();
368 
369             writer.println();
370             mFocusHandler.dump(writer);
371 
372             writer.println();
373             getAudioControlWrapperLocked().dump(writer);
374 
375             if (mHalAudioFocus != null) {
376                 writer.println();
377                 mHalAudioFocus.dump(writer);
378             } else {
379                 writer.println("No HalAudioFocus instance\n");
380             }
381             if (mCarDucking != null) {
382                 writer.println();
383                 mCarDucking.dump(writer);
384             }
385             if (mCarVolumeGroupMuting != null) {
386                 mCarVolumeGroupMuting.dump(writer);
387             }
388 
389         }
390         writer.decreaseIndent();
391     }
392 
393     @Override
isAudioFeatureEnabled(@arAudioFeature int audioFeatureType)394     public boolean isAudioFeatureEnabled(@CarAudioFeature int audioFeatureType) {
395         switch (audioFeatureType) {
396             case AUDIO_FEATURE_DYNAMIC_ROUTING:
397                 return mUseDynamicRouting;
398             case AUDIO_FEATURE_VOLUME_GROUP_MUTING:
399                 return mUseCarVolumeGroupMuting;
400             default:
401                 throw new IllegalArgumentException("Unknown Audio Feature type: "
402                         + audioFeatureType);
403         }
404     }
405 
406     /**
407      * @see {@link android.car.media.CarAudioManager#setGroupVolume(int, int, int, int)}
408      */
409     @Override
setGroupVolume(int zoneId, int groupId, int index, int flags)410     public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
411         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
412         callbackGroupVolumeChange(zoneId, groupId, flags);
413         // For legacy stream type based volume control
414         if (!mUseDynamicRouting) {
415             mAudioManager.setStreamVolume(
416                     CarAudioDynamicRouting.STREAM_TYPES[groupId], index, flags);
417             return;
418         }
419         synchronized (mImplLock) {
420             CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
421             group.setCurrentGainIndex(index);
422         }
423     }
424 
callbackGroupVolumeChange(int zoneId, int groupId, int flags)425     private void callbackGroupVolumeChange(int zoneId, int groupId, int flags) {
426         if (mUseDynamicRouting && !isPlaybackOnVolumeGroupActive(zoneId, groupId)) {
427             flags |= FLAG_PLAY_SOUND;
428         }
429         mCarVolumeCallbackHandler.onVolumeGroupChange(zoneId, groupId, flags);
430     }
431 
callbackGroupMuteChanged(int zoneId, int groupId, int flags)432     private void callbackGroupMuteChanged(int zoneId, int groupId, int flags) {
433         mCarVolumeCallbackHandler.onGroupMuteChange(zoneId, groupId, flags);
434     }
435 
setMasterMute(boolean mute, int flags)436     void setMasterMute(boolean mute, int flags) {
437         mAudioManager.setMasterMute(mute, flags);
438 
439         // Master Mute only appliers to primary zone
440         callbackMasterMuteChange(PRIMARY_AUDIO_ZONE, flags);
441 
442         // When the master mute is turned ON, we want the playing app to get a "pause" command.
443         // When the volume is unmuted, we want to resume playback.
444         int keycode = mute ? KeyEvent.KEYCODE_MEDIA_PAUSE : KeyEvent.KEYCODE_MEDIA_PLAY;
445 
446         dispatchMediaKeyEvent(keycode);
447     }
448 
dispatchMediaKeyEvent(int keycode)449     private void dispatchMediaKeyEvent(int keycode) {
450         long currentTime = SystemClock.uptimeMillis();
451         KeyEvent keyDown = new KeyEvent(/* downTime= */ currentTime, /* eventTime= */ currentTime,
452                 KeyEvent.ACTION_DOWN, keycode, /* repeat= */ 0);
453         mAudioManager.dispatchMediaKeyEvent(keyDown);
454 
455         KeyEvent keyUp = new KeyEvent(/* downTime= */ currentTime, /* eventTime= */ currentTime,
456                 KeyEvent.ACTION_UP, keycode, /* repeat= */ 0);
457         mAudioManager.dispatchMediaKeyEvent(keyUp);
458     }
459 
callbackMasterMuteChange(int zoneId, int flags)460     void callbackMasterMuteChange(int zoneId, int flags) {
461         mCarVolumeCallbackHandler.onMasterMuteChanged(zoneId, flags);
462 
463         // Persists master mute state if applicable
464         if (mPersistMasterMuteState) {
465             mCarAudioSettings.storeMasterMute(mAudioManager.isMasterMute());
466         }
467     }
468 
469     /**
470      * @see {@link android.car.media.CarAudioManager#getGroupMaxVolume(int, int)}
471      */
472     @Override
getGroupMaxVolume(int zoneId, int groupId)473     public int getGroupMaxVolume(int zoneId, int groupId) {
474         synchronized (mImplLock) {
475             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
476 
477             // For legacy stream type based volume control
478             if (!mUseDynamicRouting) {
479                 return mAudioManager.getStreamMaxVolume(
480                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
481             }
482 
483             CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
484             return group.getMaxGainIndex();
485         }
486     }
487 
488     /**
489      * @see {@link android.car.media.CarAudioManager#getGroupMinVolume(int, int)}
490      */
491     @Override
getGroupMinVolume(int zoneId, int groupId)492     public int getGroupMinVolume(int zoneId, int groupId) {
493         synchronized (mImplLock) {
494             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
495 
496             // For legacy stream type based volume control
497             if (!mUseDynamicRouting) {
498                 return mAudioManager.getStreamMinVolume(
499                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
500             }
501 
502             CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
503             return group.getMinGainIndex();
504         }
505     }
506 
507     /**
508      * @see {@link android.car.media.CarAudioManager#getGroupVolume(int, int)}
509      */
510     @Override
getGroupVolume(int zoneId, int groupId)511     public int getGroupVolume(int zoneId, int groupId) {
512         synchronized (mImplLock) {
513             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
514 
515             // For legacy stream type based volume control
516             if (!mUseDynamicRouting) {
517                 return mAudioManager.getStreamVolume(
518                         CarAudioDynamicRouting.STREAM_TYPES[groupId]);
519             }
520 
521             CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
522             return group.getCurrentGainIndex();
523         }
524     }
525 
526     @GuardedBy("mImplLock")
getCarVolumeGroupLocked(int zoneId, int groupId)527     private CarVolumeGroup getCarVolumeGroupLocked(int zoneId, int groupId) {
528         return getCarAudioZoneLocked(zoneId).getVolumeGroup(groupId);
529     }
530 
setupLegacyVolumeChangedListener()531     private void setupLegacyVolumeChangedListener() {
532         IntentFilter intentFilter = new IntentFilter();
533         intentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
534         intentFilter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);
535         mContext.registerReceiver(mLegacyVolumeChangedReceiver, intentFilter);
536     }
537 
generateCarAudioDeviceInfos()538     private List<CarAudioDeviceInfo> generateCarAudioDeviceInfos() {
539         AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
540                 AudioManager.GET_DEVICES_OUTPUTS);
541 
542         return Arrays.stream(deviceInfos)
543                 .filter(info -> info.getType() == AudioDeviceInfo.TYPE_BUS)
544                 .map(CarAudioDeviceInfo::new)
545                 .collect(Collectors.toList());
546     }
547 
getAllInputDevices()548     private AudioDeviceInfo[] getAllInputDevices() {
549         return mAudioManager.getDevices(
550                 AudioManager.GET_DEVICES_INPUTS);
551     }
552 
loadCarAudioConfigurationLocked( List<CarAudioDeviceInfo> carAudioDeviceInfos)553     private SparseArray<CarAudioZone> loadCarAudioConfigurationLocked(
554             List<CarAudioDeviceInfo> carAudioDeviceInfos) {
555         AudioDeviceInfo[] inputDevices = getAllInputDevices();
556         try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
557             CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mCarAudioSettings,
558                     inputStream, carAudioDeviceInfos, inputDevices, mUseCarVolumeGroupMuting);
559             mAudioZoneIdToOccupantZoneIdMapping =
560                     zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping();
561             return zonesHelper.loadAudioZones();
562         } catch (IOException | XmlPullParserException e) {
563             throw new RuntimeException("Failed to parse audio zone configuration", e);
564         }
565     }
566 
loadVolumeGroupConfigurationWithAudioControlLocked( List<CarAudioDeviceInfo> carAudioDeviceInfos)567     private SparseArray<CarAudioZone> loadVolumeGroupConfigurationWithAudioControlLocked(
568             List<CarAudioDeviceInfo> carAudioDeviceInfos) {
569         AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
570         if (!(audioControlWrapper instanceof AudioControlWrapperV1)) {
571             throw new IllegalStateException(
572                     "Updated version of IAudioControl no longer supports CarAudioZonesHelperLegacy."
573                     + " Please provide car_audio_configuration.xml.");
574         }
575         CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
576                 R.xml.car_volume_groups, carAudioDeviceInfos,
577                 (AudioControlWrapperV1) audioControlWrapper, mCarAudioSettings);
578         return legacyHelper.loadAudioZones();
579     }
580 
581     @GuardedBy("mImplLock")
loadCarAudioZonesLocked()582     private void loadCarAudioZonesLocked() {
583         List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
584 
585         mCarAudioConfigurationPath = getAudioConfigurationPath();
586         if (mCarAudioConfigurationPath != null) {
587             mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos);
588         } else {
589             mCarAudioZones = loadVolumeGroupConfigurationWithAudioControlLocked(
590                     carAudioDeviceInfos);
591         }
592 
593         CarAudioZonesValidator.validate(mCarAudioZones);
594     }
595 
596     @GuardedBy("mImplLock")
setupDynamicRoutingLocked()597     private void setupDynamicRoutingLocked() {
598         final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
599         builder.setLooper(Looper.getMainLooper());
600 
601         loadCarAudioZonesLocked();
602 
603         for (int i = 0; i < mCarAudioZones.size(); i++) {
604             CarAudioZone zone = mCarAudioZones.valueAt(i);
605             // Ensure HAL gets our initial value
606             zone.synchronizeCurrentGainIndex();
607             Slog.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
608         }
609 
610         CarAudioDynamicRouting.setupAudioDynamicRouting(builder, mCarAudioZones);
611 
612         // Attach the {@link AudioPolicyVolumeCallback}
613         CarAudioPolicyVolumeCallback
614                 .addVolumeCallbackToPolicy(builder, this, mAudioManager,
615                         mUseCarVolumeGroupMuting);
616 
617 
618         AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
619         if (mUseHalDuckingSignals) {
620             if (audioControlWrapper.supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_DUCKING)) {
621                 mCarDucking = new CarDucking(mCarAudioZones, audioControlWrapper);
622             }
623         }
624 
625         if (mUseCarVolumeGroupMuting) {
626             mCarVolumeGroupMuting = new CarVolumeGroupMuting(mCarAudioZones, audioControlWrapper);
627         }
628 
629         // Configure our AudioPolicy to handle focus events.
630         // This gives us the ability to decide which audio focus requests to accept and bypasses
631         // the framework ducking logic.
632         mFocusHandler = CarZonesAudioFocus.createCarZonesAudioFocus(mAudioManager,
633                 mContext.getPackageManager(),
634                 mCarAudioZones,
635                 mCarAudioSettings,
636                 ENABLE_DELAYED_AUDIO_FOCUS,
637                 mCarDucking);
638         builder.setAudioPolicyFocusListener(mFocusHandler);
639         builder.setIsAudioFocusPolicy(true);
640 
641         mAudioPolicy = builder.build();
642 
643         // Connect the AudioPolicy and the focus listener
644         mFocusHandler.setOwningPolicy(this, mAudioPolicy);
645 
646         int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
647         if (r != AudioManager.SUCCESS) {
648             throw new RuntimeException("registerAudioPolicy failed " + r);
649         }
650 
651         setupOccupantZoneInfo();
652     }
653 
setupAudioConfigurationCallbackLocked()654     private void setupAudioConfigurationCallbackLocked() {
655         mCarAudioPlaybackCallback =
656                 new CarAudioPlaybackCallback(getCarAudioZone(PRIMARY_AUDIO_ZONE),
657                         mClock, mKeyEventTimeoutMs);
658         mAudioManager.registerAudioPlaybackCallback(mCarAudioPlaybackCallback, null);
659     }
660 
setupOccupantZoneInfo()661     private void setupOccupantZoneInfo() {
662         CarOccupantZoneService occupantZoneService;
663         CarOccupantZoneManager occupantZoneManager;
664         SparseIntArray audioZoneIdToOccupantZoneMapping;
665         OccupantZoneConfigChangeListener listener;
666         synchronized (mImplLock) {
667             audioZoneIdToOccupantZoneMapping = mAudioZoneIdToOccupantZoneIdMapping;
668             occupantZoneService = mOccupantZoneService;
669             occupantZoneManager = mOccupantZoneManager;
670             listener = mOccupantZoneConfigChangeListener;
671         }
672         occupantZoneService.setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping);
673         occupantZoneManager.registerOccupantZoneConfigChangeListener(listener);
674     }
675 
setupHalAudioFocusListenerLocked()676     private void setupHalAudioFocusListenerLocked() {
677         AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
678         if (!audioControlWrapper.supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_FOCUS)) {
679             Slog.d(CarLog.TAG_AUDIO, "HalAudioFocus is not supported on this device");
680             return;
681         }
682 
683         mHalAudioFocus = new HalAudioFocus(mAudioManager, mAudioControlWrapper, getAudioZoneIds());
684         mHalAudioFocus.registerFocusListener();
685     }
686 
687     /**
688      * Read from {@link #AUDIO_CONFIGURATION_PATHS} respectively.
689      * @return File path of the first hit in {@link #AUDIO_CONFIGURATION_PATHS}
690      */
691     @Nullable
getAudioConfigurationPath()692     private String getAudioConfigurationPath() {
693         for (String path : AUDIO_CONFIGURATION_PATHS) {
694             File configuration = new File(path);
695             if (configuration.exists()) {
696                 return path;
697             }
698         }
699         return null;
700     }
701 
702     @Override
setFadeTowardFront(float value)703     public void setFadeTowardFront(float value) {
704         synchronized (mImplLock) {
705             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
706             getAudioControlWrapperLocked().setFadeTowardFront(value);
707         }
708     }
709 
710     @Override
setBalanceTowardRight(float value)711     public void setBalanceTowardRight(float value) {
712         synchronized (mImplLock) {
713             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
714             getAudioControlWrapperLocked().setBalanceTowardRight(value);
715         }
716     }
717 
718     /**
719      * @return Array of accumulated device addresses, empty array if we found nothing
720      */
721     @Override
getExternalSources()722     public @NonNull String[] getExternalSources() {
723         synchronized (mImplLock) {
724             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
725             List<String> sourceAddresses = new ArrayList<>();
726 
727             AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
728             if (devices.length == 0) {
729                 Slog.w(CarLog.TAG_AUDIO, "getExternalSources, no input devices found.");
730             }
731 
732             // Collect the list of non-microphone input ports
733             for (AudioDeviceInfo info : devices) {
734                 switch (info.getType()) {
735                     // TODO:  Can we trim this set down? Especially duplicates like FM vs FM_TUNER?
736                     case AudioDeviceInfo.TYPE_FM:
737                     case AudioDeviceInfo.TYPE_FM_TUNER:
738                     case AudioDeviceInfo.TYPE_TV_TUNER:
739                     case AudioDeviceInfo.TYPE_HDMI:
740                     case AudioDeviceInfo.TYPE_AUX_LINE:
741                     case AudioDeviceInfo.TYPE_LINE_ANALOG:
742                     case AudioDeviceInfo.TYPE_LINE_DIGITAL:
743                     case AudioDeviceInfo.TYPE_USB_ACCESSORY:
744                     case AudioDeviceInfo.TYPE_USB_DEVICE:
745                     case AudioDeviceInfo.TYPE_USB_HEADSET:
746                     case AudioDeviceInfo.TYPE_IP:
747                     case AudioDeviceInfo.TYPE_BUS:
748                         String address = info.getAddress();
749                         if (TextUtils.isEmpty(address)) {
750                             Slog.w(CarLog.TAG_AUDIO,
751                                     "Discarded device with empty address, type=" + info.getType());
752                         } else {
753                             sourceAddresses.add(address);
754                         }
755                 }
756             }
757 
758             return sourceAddresses.toArray(new String[0]);
759         }
760     }
761 
762     @Override
createAudioPatch(String sourceAddress, @AttributeUsage int usage, int gainInMillibels)763     public CarAudioPatchHandle createAudioPatch(String sourceAddress,
764             @AttributeUsage int usage, int gainInMillibels) {
765         synchronized (mImplLock) {
766             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
767             return createAudioPatchLocked(sourceAddress, usage, gainInMillibels);
768         }
769     }
770 
771     @Override
releaseAudioPatch(CarAudioPatchHandle carPatch)772     public void releaseAudioPatch(CarAudioPatchHandle carPatch) {
773         synchronized (mImplLock) {
774             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
775             releaseAudioPatchLocked(carPatch);
776         }
777     }
778 
createAudioPatchLocked(String sourceAddress, @AttributeUsage int usage, int gainInMillibels)779     private CarAudioPatchHandle createAudioPatchLocked(String sourceAddress,
780             @AttributeUsage int usage, int gainInMillibels) {
781         // Find the named source port
782         AudioDeviceInfo sourcePortInfo = null;
783         AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
784         for (AudioDeviceInfo info : deviceInfos) {
785             if (sourceAddress.equals(info.getAddress())) {
786                 // This is the one for which we're looking
787                 sourcePortInfo = info;
788                 break;
789             }
790         }
791         Objects.requireNonNull(sourcePortInfo,
792                 "Specified source is not available: " + sourceAddress);
793 
794         // Find the output port associated with the given carUsage
795         AudioDevicePort sinkPort = Objects.requireNonNull(getAudioPort(usage),
796                 "Sink not available for usage: " + AudioAttributes.usageToString(usage));
797 
798         // {@link android.media.AudioPort#activeConfig()} is valid for mixer port only,
799         // since audio framework has no clue what's active on the device ports.
800         // Therefore we construct an empty / default configuration here, which the audio HAL
801         // implementation should ignore.
802         AudioPortConfig sinkConfig = sinkPort.buildConfig(0,
803                 AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT, null);
804         Slog.d(CarLog.TAG_AUDIO, "createAudioPatch sinkConfig: " + sinkConfig);
805 
806         // Configure the source port to match the output port except for a gain adjustment
807         final CarAudioDeviceInfo helper = new CarAudioDeviceInfo(sourcePortInfo);
808         AudioGain audioGain = Objects.requireNonNull(helper.getAudioGain(),
809                 "Gain controller not available for source port");
810 
811         // size of gain values is 1 in MODE_JOINT
812         AudioGainConfig audioGainConfig = audioGain.buildConfig(AudioGain.MODE_JOINT,
813                 audioGain.channelMask(), new int[] { gainInMillibels }, 0);
814         // Construct an empty / default configuration excepts gain config here and it's up to the
815         // audio HAL how to interpret this configuration, which the audio HAL
816         // implementation should ignore.
817         AudioPortConfig sourceConfig = sourcePortInfo.getPort().buildConfig(0,
818                 AudioFormat.CHANNEL_IN_DEFAULT, AudioFormat.ENCODING_DEFAULT, audioGainConfig);
819 
820         // Create an audioPatch to connect the two ports
821         AudioPatch[] patch = new AudioPatch[] { null };
822         int result = AudioManager.createAudioPatch(patch,
823                 new AudioPortConfig[] { sourceConfig },
824                 new AudioPortConfig[] { sinkConfig });
825         if (result != AudioManager.SUCCESS) {
826             throw new RuntimeException("createAudioPatch failed with code " + result);
827         }
828 
829         Objects.requireNonNull(patch[0],
830                 "createAudioPatch didn't provide expected single handle");
831         Slog.d(CarLog.TAG_AUDIO, "Audio patch created: " + patch[0]);
832 
833         // Ensure the initial volume on output device port
834         int groupId = getVolumeGroupIdForUsage(CarAudioManager.PRIMARY_AUDIO_ZONE, usage);
835         setGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE, groupId,
836                 getGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE, groupId), 0);
837 
838         return new CarAudioPatchHandle(patch[0]);
839     }
840 
releaseAudioPatchLocked(CarAudioPatchHandle carPatch)841     private void releaseAudioPatchLocked(CarAudioPatchHandle carPatch) {
842         Objects.requireNonNull(carPatch);
843         // NOTE:  AudioPolicyService::removeNotificationClient will take care of this automatically
844         //        if the client that created a patch quits.
845         ArrayList<AudioPatch> patches = new ArrayList<>();
846         int result = mAudioManager.listAudioPatches(patches);
847         if (result != AudioManager.SUCCESS) {
848             throw new RuntimeException("listAudioPatches failed with code " + result);
849         }
850 
851         // Look for a patch that matches the provided user side handle
852         for (AudioPatch patch : patches) {
853             if (carPatch.represents(patch)) {
854                 // Found it!
855                 result = AudioManager.releaseAudioPatch(patch);
856                 if (result != AudioManager.SUCCESS) {
857                     throw new RuntimeException("releaseAudioPatch failed with code " + result);
858                 }
859                 return;
860             }
861         }
862 
863         // If we didn't find a match, then something went awry, but it's probably not fatal...
864         Slog.e(CarLog.TAG_AUDIO, "releaseAudioPatch found no match for " + carPatch);
865     }
866 
867     @Override
getVolumeGroupCount(int zoneId)868     public int getVolumeGroupCount(int zoneId) {
869         synchronized (mImplLock) {
870             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
871             // For legacy stream type based volume control
872             if (!mUseDynamicRouting) return CarAudioDynamicRouting.STREAM_TYPES.length;
873 
874             return getCarAudioZoneLocked(zoneId).getVolumeGroupCount();
875         }
876     }
877 
878     @Override
getVolumeGroupIdForUsage(int zoneId, @AttributeUsage int usage)879     public int getVolumeGroupIdForUsage(int zoneId, @AttributeUsage int usage) {
880         synchronized (mImplLock) {
881             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
882 
883             if (!mUseDynamicRouting) {
884                 for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPE_USAGES.length; i++) {
885                     if (usage == CarAudioDynamicRouting.STREAM_TYPE_USAGES[i]) {
886                         return i;
887                     }
888                 }
889 
890                 return INVALID_VOLUME_GROUP_ID;
891             }
892 
893             @AudioContext int audioContext = CarAudioContext.getContextForUsage(usage);
894             return getVolumeGroupIdForAudioContextLocked(zoneId, audioContext);
895         }
896     }
897 
898     @GuardedBy("mImplLock")
getVolumeGroupIdForAudioContextLocked(int zoneId, @AudioContext int audioContext)899     private int getVolumeGroupIdForAudioContextLocked(int zoneId, @AudioContext int audioContext) {
900         CarVolumeGroup[] groups = getCarAudioZoneLocked(zoneId).getVolumeGroups();
901         for (int i = 0; i < groups.length; i++) {
902             int[] groupAudioContexts = groups[i].getContexts();
903             for (int groupAudioContext : groupAudioContexts) {
904                 if (audioContext == groupAudioContext) {
905                     return i;
906                 }
907             }
908         }
909         return INVALID_VOLUME_GROUP_ID;
910     }
911 
912     @Override
getUsagesForVolumeGroupId(int zoneId, int groupId)913     public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
914         synchronized (mImplLock) {
915             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
916 
917             // For legacy stream type based volume control
918             if (!mUseDynamicRouting) {
919                 return new int[] { CarAudioDynamicRouting.STREAM_TYPE_USAGES[groupId] };
920             }
921 
922             CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
923             Set<Integer> contexts =
924                     Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
925             final List<Integer> usages = new ArrayList<>();
926             for (@AudioContext int context : contexts) {
927                 int[] usagesForContext = CarAudioContext.getUsagesForContext(context);
928                 for (@AttributeUsage int usage : usagesForContext) {
929                     usages.add(usage);
930                 }
931             }
932             return usages.stream().mapToInt(i -> i).toArray();
933         }
934     }
935 
936     @Override
isPlaybackOnVolumeGroupActive(int zoneId, int groupId)937     public boolean isPlaybackOnVolumeGroupActive(int zoneId, int groupId) {
938         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
939         requireDynamicRouting();
940         Preconditions.checkArgument(isAudioZoneIdValid(zoneId),
941                 "Invalid audio zone id %d", zoneId);
942 
943         return CarVolume.isAnyContextActive(getContextsForVolumeGroupId(zoneId, groupId),
944                 getActiveContextsFromPlaybackConfigurations(zoneId), getCallStateForZone(zoneId),
945                 getActiveHalUsagesForZone(zoneId));
946     }
947 
getCallStateForZone(int zoneId)948     private @CallState int getCallStateForZone(int zoneId) {
949         synchronized (mImplLock) {
950             // Only driver can use telephony stack
951             if (getUserIdForZoneLocked(zoneId) == mOccupantZoneService.getDriverUserId()) {
952                 return mTelephonyManager.getCallState();
953             }
954         }
955         return TelephonyManager.CALL_STATE_IDLE;
956     }
957 
getActiveContextsFromPlaybackConfigurations(int zoneId)958     private List<Integer> getActiveContextsFromPlaybackConfigurations(int zoneId) {
959         List<AudioPlaybackConfiguration> configurations = mAudioManager
960                 .getActivePlaybackConfigurations();
961         return getCarAudioZone(zoneId).findActiveContextsFromPlaybackConfigurations(configurations);
962     }
963 
964 
getContextsForVolumeGroupId(int zoneId, int groupId)965     private @NonNull @AudioContext int[] getContextsForVolumeGroupId(int zoneId, int groupId) {
966         synchronized (mImplLock) {
967             CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
968             return group.getContexts();
969         }
970     }
971 
972     /**
973      * Gets the ids of all available audio zones
974      *
975      * @return Array of available audio zones ids
976      */
977     @Override
getAudioZoneIds()978     public @NonNull int[] getAudioZoneIds() {
979         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
980         requireDynamicRouting();
981         synchronized (mImplLock) {
982             int[] zoneIds = new int[mCarAudioZones.size()];
983             for (int i = 0; i < mCarAudioZones.size(); i++) {
984                 zoneIds[i] = mCarAudioZones.keyAt(i);
985             }
986             return zoneIds;
987         }
988     }
989 
990     /**
991      * Gets the audio zone id currently mapped to uid,
992      *
993      * <p><b>Note:</b> Will use uid mapping first, followed by uid's {@userId} mapping.
994      * defaults to PRIMARY_AUDIO_ZONE if no mapping exist
995      *
996      * @param uid The uid
997      * @return zone id mapped to uid
998      */
999     @Override
getZoneIdForUid(int uid)1000     public int getZoneIdForUid(int uid) {
1001         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
1002         requireDynamicRouting();
1003         synchronized (mImplLock) {
1004             if (mUidToZoneMap.containsKey(uid)) {
1005                 return mUidToZoneMap.get(uid);
1006             }
1007             int userId = UserHandle.getUserId(uid);
1008             return getZoneIdForUserIdLocked(userId);
1009         }
1010     }
1011 
getZoneIdForUserIdLocked(@serIdInt int userId)1012     private int getZoneIdForUserIdLocked(@UserIdInt int userId) {
1013         int audioZoneId = mOccupantZoneService.getAudioZoneIdForOccupant(
1014                 mOccupantZoneService.getOccupantZoneIdForUserId(userId));
1015         if (audioZoneId != CarAudioManager.INVALID_AUDIO_ZONE) {
1016             return audioZoneId;
1017         }
1018         Slog.w(CarLog.TAG_AUDIO,
1019                 "getZoneIdForUid userId " + userId
1020                         + " does not have a zone. Defaulting to PRIMARY_AUDIO_ZONE:"
1021                         + CarAudioManager.PRIMARY_AUDIO_ZONE);
1022         return CarAudioManager.PRIMARY_AUDIO_ZONE;
1023     }
1024 
1025     /**
1026      * Maps the audio zone id to uid
1027      *
1028      * @param zoneId The audio zone id
1029      * @param uid The uid to map
1030      *
1031      * <p><b>Note:</b> Will throw if occupant zone mapping exist, as uid and occupant zone mapping
1032      * do not work in conjunction.
1033      *
1034      * @return true if the device affinities, for devices in zone, are successfully set
1035      */
1036     @Override
setZoneIdForUid(int zoneId, int uid)1037     public boolean setZoneIdForUid(int zoneId, int uid) {
1038         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
1039         requireDynamicRouting();
1040         synchronized (mImplLock) {
1041             checkAudioZoneIdLocked(zoneId);
1042             Slog.i(CarLog.TAG_AUDIO, "setZoneIdForUid Calling uid "
1043                     + uid + " mapped to : "
1044                     + zoneId);
1045 
1046             // If occupant mapping exist uid routing can not be used
1047             requiredOccupantZoneMappingDisabledLocked();
1048 
1049             // Figure out if anything is currently holding focus,
1050             // This will change the focus to transient loss while we are switching zones
1051             Integer currentZoneId = mUidToZoneMap.get(uid);
1052             ArrayList<AudioFocusInfo> currentFocusHoldersForUid = new ArrayList<>();
1053             ArrayList<AudioFocusInfo> currentFocusLosersForUid = new ArrayList<>();
1054             if (currentZoneId != null) {
1055                 currentFocusHoldersForUid = mFocusHandler.getAudioFocusHoldersForUid(uid,
1056                         currentZoneId.intValue());
1057                 currentFocusLosersForUid = mFocusHandler.getAudioFocusLosersForUid(uid,
1058                         currentZoneId.intValue());
1059                 if (!currentFocusHoldersForUid.isEmpty() || !currentFocusLosersForUid.isEmpty()) {
1060                     // Order matters here: Remove the focus losers first
1061                     // then do the current holder to prevent loser from popping up while
1062                     // the focus is being remove for current holders
1063                     // Remove focus for current focus losers
1064                     mFocusHandler.transientlyLoseInFocusInZone(currentFocusLosersForUid,
1065                             currentZoneId.intValue());
1066                     // Remove focus for current holders
1067                     mFocusHandler.transientlyLoseInFocusInZone(currentFocusHoldersForUid,
1068                             currentZoneId.intValue());
1069                 }
1070             }
1071 
1072             // if the current uid is in the list
1073             // remove it from the list
1074 
1075             if (checkAndRemoveUidLocked(uid)) {
1076                 if (setZoneIdForUidNoCheckLocked(zoneId, uid)) {
1077                     // Order matters here: Regain focus for
1078                     // Previously lost focus holders then regain
1079                     // focus for holders that had it last
1080                     // Regain focus for the focus losers from previous zone
1081                     if (!currentFocusLosersForUid.isEmpty()) {
1082                         regainAudioFocusLocked(currentFocusLosersForUid, zoneId);
1083                     }
1084                     // Regain focus for the focus holders from previous zone
1085                     if (!currentFocusHoldersForUid.isEmpty()) {
1086                         regainAudioFocusLocked(currentFocusHoldersForUid, zoneId);
1087                     }
1088                     return true;
1089                 }
1090             }
1091             return false;
1092         }
1093     }
1094 
1095     @Override
getOutputDeviceAddressForUsage(int zoneId, @AttributeUsage int usage)1096     public String getOutputDeviceAddressForUsage(int zoneId, @AttributeUsage int usage) {
1097         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
1098         requireDynamicRouting();
1099         int contextForUsage = CarAudioContext.getContextForUsage(usage);
1100         Preconditions.checkArgument(contextForUsage != CarAudioContext.INVALID,
1101                 "Invalid audio attribute usage %d", usage);
1102         return getCarAudioZone(zoneId).getAddressForContext(contextForUsage);
1103     }
1104 
1105     /**
1106      * Regain focus for the focus list passed in
1107      * @param afiList focus info list to regain
1108      * @param zoneId zone id where the focus holder belong
1109      */
regainAudioFocusLocked(ArrayList<AudioFocusInfo> afiList, int zoneId)1110     void regainAudioFocusLocked(ArrayList<AudioFocusInfo> afiList, int zoneId) {
1111         for (AudioFocusInfo info : afiList) {
1112             if (mFocusHandler.reevaluateAndRegainAudioFocus(info)
1113                     != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1114                 Slog.i(CarLog.TAG_AUDIO,
1115                         " Focus could not be granted for entry "
1116                                 + info.getClientId()
1117                                 + " uid " + info.getClientUid()
1118                                 + " in zone " + zoneId);
1119             }
1120         }
1121     }
1122 
1123     /**
1124      * Removes the current mapping of the uid, focus will be lost in zone
1125      * @param uid The uid to remove
1126      *
1127      * <p><b>Note:</b> Will throw if occupant zone mapping exist, as uid and occupant zone mapping
1128      * do not work in conjunction.
1129      *
1130      * return true if all the devices affinities currently
1131      *            mapped to uid are successfully removed
1132      */
1133     @Override
clearZoneIdForUid(int uid)1134     public boolean clearZoneIdForUid(int uid) {
1135         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
1136         requireDynamicRouting();
1137         synchronized (mImplLock) {
1138             // Throw so as to not set the wrong expectation,
1139             // that routing will be changed if clearZoneIdForUid is called.
1140             requiredOccupantZoneMappingDisabledLocked();
1141 
1142             return checkAndRemoveUidLocked(uid);
1143         }
1144     }
1145 
1146     /**
1147      * Sets the zone id for uid
1148      * @param zoneId zone id to map to uid
1149      * @param uid uid to map
1150      * @return true if setting uid device affinity is successful
1151      */
1152     @GuardedBy("mImplLock")
setZoneIdForUidNoCheckLocked(int zoneId, int uid)1153     private boolean setZoneIdForUidNoCheckLocked(int zoneId, int uid) {
1154         Slog.d(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Calling uid "
1155                 + uid + " mapped to " + zoneId);
1156         //Request to add uid device affinity
1157         List<AudioDeviceInfo> deviceInfos = getCarAudioZoneLocked(zoneId).getAudioDeviceInfos();
1158         if (mAudioPolicy.setUidDeviceAffinity(uid, deviceInfos)) {
1159             // TODO do not store uid mapping here instead use the uid
1160             //  device affinity in audio policy when available
1161             mUidToZoneMap.put(uid, zoneId);
1162             return true;
1163         }
1164         Slog.w(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Failed set device affinity for uid "
1165                 + uid + " in zone " + zoneId);
1166         return false;
1167     }
1168 
1169     /**
1170      * Check if uid is attached to a zone and remove it
1171      * @param uid unique id to remove
1172      * @return true if the uid was successfully removed or mapping was not assigned
1173      */
checkAndRemoveUidLocked(int uid)1174     private boolean checkAndRemoveUidLocked(int uid) {
1175         Integer zoneId = mUidToZoneMap.get(uid);
1176         if (zoneId != null) {
1177             Slog.i(CarLog.TAG_AUDIO, "checkAndRemoveUid removing Calling uid "
1178                     + uid + " from zone " + zoneId);
1179             if (mAudioPolicy.removeUidDeviceAffinity(uid)) {
1180                 // TODO use the uid device affinity in audio policy when available
1181                 mUidToZoneMap.remove(uid);
1182                 return true;
1183             }
1184             //failed to remove device affinity from zone devices
1185             Slog.w(CarLog.TAG_AUDIO,
1186                     "checkAndRemoveUid Failed remove device affinity for uid "
1187                             + uid + " in zone " +  zoneId);
1188             return false;
1189         }
1190         return true;
1191     }
1192 
1193     @Override
registerVolumeCallback(@onNull IBinder binder)1194     public void registerVolumeCallback(@NonNull IBinder binder) {
1195         synchronized (mImplLock) {
1196             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1197             mCarVolumeCallbackHandler.registerCallback(binder);
1198         }
1199     }
1200 
1201     @Override
unregisterVolumeCallback(@onNull IBinder binder)1202     public void unregisterVolumeCallback(@NonNull IBinder binder) {
1203         synchronized (mImplLock) {
1204             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1205             mCarVolumeCallbackHandler.unregisterCallback(binder);
1206         }
1207     }
1208 
1209     /**
1210      * @see {@link android.car.media.CarAudioManager#isVolumeGroupMuted(int, int)}
1211      */
1212     @Override
isVolumeGroupMuted(int zoneId, int groupId)1213     public boolean isVolumeGroupMuted(int zoneId, int groupId) {
1214         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1215         requireDynamicRouting();
1216         if (!mUseCarVolumeGroupMuting) {
1217             return false;
1218         }
1219         synchronized (mImplLock) {
1220             CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
1221             return group.isMuted();
1222         }
1223     }
1224 
1225     /**
1226      * @see {@link android.car.media.CarAudioManager#setVolumeGroupMute(int, int, boolean, int)}
1227      */
1228     @Override
setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags)1229     public void setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags) {
1230         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1231         requireDynamicRouting();
1232         requireVolumeGroupMuting();
1233         synchronized (mImplLock) {
1234             CarVolumeGroup group = getCarVolumeGroupLocked(zoneId, groupId);
1235             group.setMute(mute);
1236             callbackGroupMuteChanged(zoneId, groupId, flags);
1237         }
1238         mCarVolumeGroupMuting.carMuteChanged();
1239     }
1240 
1241     @Override
getInputDevicesForZoneId(int zoneId)1242     public @NonNull List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId) {
1243         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
1244         requireDynamicRouting();
1245 
1246         return getCarAudioZone(zoneId).getInputAudioDevices();
1247     }
1248 
setAudioEnabled(boolean isAudioEnabled)1249     void setAudioEnabled(boolean isAudioEnabled) {
1250         if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1251             Slogf.d(CarLog.TAG_AUDIO, "Setting isAudioEnabled to %b", isAudioEnabled);
1252         }
1253 
1254         mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ !isAudioEnabled);
1255         if (mUseCarVolumeGroupMuting) {
1256             mCarVolumeGroupMuting.setRestrictMuting(/* isMutingRestricted= */ !isAudioEnabled);
1257         }
1258         // TODO(b/176258537) if not using group volume, then set master mute accordingly
1259     }
1260 
enforcePermission(String permissionName)1261     private void enforcePermission(String permissionName) {
1262         if (mContext.checkCallingOrSelfPermission(permissionName)
1263                 != PackageManager.PERMISSION_GRANTED) {
1264             throw new SecurityException("requires permission " + permissionName);
1265         }
1266     }
1267 
requireDynamicRouting()1268     private void requireDynamicRouting() {
1269         Preconditions.checkState(mUseDynamicRouting, "Dynamic routing is required");
1270     }
1271 
requireVolumeGroupMuting()1272     private void requireVolumeGroupMuting() {
1273         Preconditions.checkState(mUseCarVolumeGroupMuting,
1274                 "Car Volume Group Muting is required");
1275     }
1276 
requiredOccupantZoneMappingDisabledLocked()1277     private void requiredOccupantZoneMappingDisabledLocked() {
1278         if (isOccupantZoneMappingAvailableLocked()) {
1279             throw new IllegalStateException(
1280                     "UID based routing is not supported while using occupant zone mapping");
1281         }
1282     }
1283 
1284     /**
1285      * @return {@link AudioDevicePort} that handles the given car audio usage.
1286      * Multiple usages may share one {@link AudioDevicePort}
1287      */
getAudioPort(@ttributeUsage int usage)1288     private @Nullable AudioDevicePort getAudioPort(@AttributeUsage int usage) {
1289         int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
1290         final int groupId = getVolumeGroupIdForUsage(zoneId, usage);
1291         final CarVolumeGroup group = Objects.requireNonNull(
1292                 getCarVolumeGroupLocked(zoneId, groupId),
1293                 "Can not find CarVolumeGroup by usage: "
1294                         + AudioAttributes.usageToString(usage));
1295         return group.getAudioDevicePortForContext(CarAudioContext.getContextForUsage(usage));
1296     }
1297 
getSuggestedAudioContextForPrimaryZone()1298     @AudioContext int getSuggestedAudioContextForPrimaryZone() {
1299         int zoneId = PRIMARY_AUDIO_ZONE;
1300         return mCarVolume.getSuggestedAudioContextAndSaveIfFound(
1301                 getAllActiveContextsForPrimaryZone(), getCallStateForZone(zoneId),
1302                 getActiveHalUsagesForZone(zoneId));
1303     }
1304 
getActiveHalUsagesForZone(int zoneId)1305     private int[] getActiveHalUsagesForZone(int zoneId) {
1306         if (mHalAudioFocus == null) {
1307             return new int[0];
1308         }
1309         return mHalAudioFocus.getActiveUsagesForZone(zoneId);
1310     }
1311 
1312     /**
1313      * Gets volume group by a given legacy stream type
1314      * @param streamType Legacy stream type such as {@link AudioManager#STREAM_MUSIC}
1315      * @return volume group id mapped from stream type
1316      */
getVolumeGroupIdForStreamType(int streamType)1317     private int getVolumeGroupIdForStreamType(int streamType) {
1318         int groupId = INVALID_VOLUME_GROUP_ID;
1319         for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPES.length; i++) {
1320             if (streamType == CarAudioDynamicRouting.STREAM_TYPES[i]) {
1321                 groupId = i;
1322                 break;
1323             }
1324         }
1325         return groupId;
1326     }
1327 
handleOccupantZoneUserChanged()1328     private void handleOccupantZoneUserChanged() {
1329         int driverUserId = mOccupantZoneService.getDriverUserId();
1330         synchronized (mImplLock) {
1331             if (!isOccupantZoneMappingAvailableLocked()) {
1332                 adjustZonesToUserIdLocked(driverUserId);
1333                 return;
1334             }
1335             int occupantZoneForDriver =  getOccupantZoneIdForDriver();
1336             Set<Integer> assignedZones = new HashSet<Integer>();
1337             for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
1338                 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
1339                 int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId);
1340                 assignedZones.add(audioZoneId);
1341                 updateUserForOccupantZoneLocked(occupantZoneId, audioZoneId, driverUserId,
1342                         occupantZoneForDriver);
1343             }
1344 
1345             assignMissingZonesToDriverLocked(driverUserId, assignedZones);
1346         }
1347         restoreVolumeGroupMuteState();
1348     }
1349 
restoreVolumeGroupMuteState()1350     private void restoreVolumeGroupMuteState() {
1351         if (!mUseCarVolumeGroupMuting) {
1352             return;
1353         }
1354         mCarVolumeGroupMuting.carMuteChanged();
1355     }
1356 
assignMissingZonesToDriverLocked(@serIdInt int driverUserId, Set<Integer> assignedZones)1357     private void assignMissingZonesToDriverLocked(@UserIdInt int driverUserId,
1358             Set<Integer> assignedZones) {
1359         for (int i = 0; i < mCarAudioZones.size(); i++) {
1360             CarAudioZone zone = mCarAudioZones.valueAt(i);
1361             if (assignedZones.contains(zone.getId())) {
1362                 continue;
1363             }
1364             assignUserIdToAudioZoneLocked(zone, driverUserId);
1365         }
1366     }
1367 
adjustZonesToUserIdLocked(@serIdInt int userId)1368     private void adjustZonesToUserIdLocked(@UserIdInt int userId) {
1369         for (int i = 0; i < mCarAudioZones.size(); i++) {
1370             CarAudioZone zone = mCarAudioZones.valueAt(i);
1371             assignUserIdToAudioZoneLocked(zone, userId);
1372         }
1373     }
1374 
assignUserIdToAudioZoneLocked(CarAudioZone zone, @UserIdInt int userId)1375     private void assignUserIdToAudioZoneLocked(CarAudioZone zone, @UserIdInt int userId) {
1376         if (userId == getUserIdForZoneLocked(zone.getId())) {
1377             if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1378                 Slog.d(CarLog.TAG_AUDIO,
1379                         "assignUserIdToAudioZone userId(" + userId
1380                                 + ") already assigned to audioZoneId("
1381                                 + zone.getId() + ")");
1382             }
1383             return;
1384         }
1385         if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1386             Slog.d(CarLog.TAG_AUDIO,
1387                     "assignUserIdToAudioZone assigning userId("
1388                             + userId + ") to audioZoneId("
1389                             + zone.getId() + ")");
1390         }
1391         zone.updateVolumeGroupsSettingsForUser(userId);
1392         mFocusHandler.updateUserForZoneId(zone.getId(), userId);
1393         setUserIdForAudioZoneLocked(userId, zone.getId());
1394     }
1395 
isOccupantZoneMappingAvailableLocked()1396     private boolean isOccupantZoneMappingAvailableLocked() {
1397         return mAudioZoneIdToOccupantZoneIdMapping.size() > 0;
1398     }
1399 
1400     @GuardedBy("mImplLock")
updateUserForOccupantZoneLocked(int occupantZoneId, int audioZoneId, @UserIdInt int driverUserId, int occupantZoneForDriver)1401     private void updateUserForOccupantZoneLocked(int occupantZoneId, int audioZoneId,
1402             @UserIdInt int driverUserId, int occupantZoneForDriver) {
1403         CarAudioZone audioZone = getCarAudioZoneLocked(audioZoneId);
1404         int userId = mOccupantZoneService.getUserForOccupant(occupantZoneId);
1405         int prevUserId = getUserIdForZoneLocked(audioZoneId);
1406 
1407         if (userId == prevUserId) {
1408             if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1409                 Slog.d(CarLog.TAG_AUDIO, "updateUserForOccupantZone userId(" + userId
1410                         + ") already assigned to audioZoneId(" + audioZoneId + ")");
1411             }
1412             return;
1413         }
1414         if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1415             Slog.d(CarLog.TAG_AUDIO,
1416                     "updateUserForOccupantZone assigning userId("
1417                             + userId + ") to audioZoneId(" + audioZoneId + ")");
1418         }
1419         // If the user has changed, be sure to remove from current routing
1420         // This would be true even if the new user is UserHandle.USER_NULL,
1421         // as that indicates the user has logged out.
1422         removeUserIdDeviceAffinitiesLocked(prevUserId);
1423 
1424         if (userId == UserHandle.USER_NULL) {
1425             // Reset zone back to driver user id
1426             resetZoneToDefaultUser(audioZone, driverUserId);
1427             setUserIdForAudioZoneLocked(userId, audioZoneId);
1428             return;
1429         }
1430 
1431         // Only set user id device affinities for driver when it is the driver's occupant zone
1432         if (userId != driverUserId || occupantZoneId == occupantZoneForDriver) {
1433             setUserIdDeviceAffinitiesLocked(audioZone, userId, audioZoneId);
1434         }
1435         audioZone.updateVolumeGroupsSettingsForUser(userId);
1436         mFocusHandler.updateUserForZoneId(audioZoneId, userId);
1437         setUserIdForAudioZoneLocked(userId, audioZoneId);
1438     }
1439 
getOccupantZoneIdForDriver()1440     private int getOccupantZoneIdForDriver() {
1441         List<CarOccupantZoneManager.OccupantZoneInfo> occupantZoneInfos =
1442                 mOccupantZoneManager.getAllOccupantZones();
1443         for (CarOccupantZoneManager.OccupantZoneInfo info: occupantZoneInfos) {
1444             if (info.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
1445                 return info.zoneId;
1446             }
1447         }
1448         return CarOccupantZoneManager.OccupantZoneInfo.INVALID_ZONE_ID;
1449     }
1450 
setUserIdDeviceAffinitiesLocked(CarAudioZone zone, @UserIdInt int userId, int audioZoneId)1451     private void setUserIdDeviceAffinitiesLocked(CarAudioZone zone, @UserIdInt int userId,
1452             int audioZoneId) {
1453         if (!mAudioPolicy.setUserIdDeviceAffinity(userId, zone.getAudioDeviceInfos())) {
1454             throw new IllegalStateException(String.format(
1455                     "setUserIdDeviceAffinity for userId %d in zone %d Failed,"
1456                             + " could not set audio routing.",
1457                     userId, audioZoneId));
1458         }
1459     }
1460 
resetZoneToDefaultUser(CarAudioZone zone, @UserIdInt int driverUserId)1461     private void resetZoneToDefaultUser(CarAudioZone zone, @UserIdInt int driverUserId) {
1462         resetCarZonesAudioFocus(zone.getId(), driverUserId);
1463         zone.updateVolumeGroupsSettingsForUser(driverUserId);
1464     }
1465 
resetCarZonesAudioFocus(int audioZoneId, @UserIdInt int driverUserId)1466     private void resetCarZonesAudioFocus(int audioZoneId, @UserIdInt int driverUserId) {
1467         mFocusHandler.updateUserForZoneId(audioZoneId, driverUserId);
1468     }
1469 
removeUserIdDeviceAffinitiesLocked(@serIdInt int userId)1470     private void removeUserIdDeviceAffinitiesLocked(@UserIdInt int userId) {
1471         if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1472             Slog.d(CarLog.TAG_AUDIO,
1473                     "removeUserIdDeviceAffinities(" + userId + ") Succeeded");
1474         }
1475         if (userId == UserHandle.USER_NULL) {
1476             return;
1477         }
1478         if (!mAudioPolicy.removeUserIdDeviceAffinity(userId)) {
1479             Slog.e(CarLog.TAG_AUDIO, "removeUserIdDeviceAffinities(" + userId + ") Failed");
1480             return;
1481         }
1482     }
1483 
getUserIdForZoneLocked(int audioZoneId)1484     private @UserIdInt int getUserIdForZoneLocked(int audioZoneId) {
1485         return mAudioZoneIdToUserIdMapping.get(audioZoneId, UserHandle.USER_NULL);
1486     }
1487 
setUserIdForAudioZoneLocked(@serIdInt int userId, int audioZoneId)1488     private void setUserIdForAudioZoneLocked(@UserIdInt int userId, int audioZoneId) {
1489         mAudioZoneIdToUserIdMapping.put(audioZoneId, userId);
1490     }
1491 
getAudioControlWrapperLocked()1492     private AudioControlWrapper getAudioControlWrapperLocked() {
1493         if (mAudioControlWrapper == null) {
1494             mAudioControlWrapper = AudioControlFactory.newAudioControl();
1495             mAudioControlWrapper.linkToDeath(this::resetHalAudioFocus);
1496         }
1497         return mAudioControlWrapper;
1498     }
1499 
resetHalAudioFocus()1500     private void resetHalAudioFocus() {
1501         if (mHalAudioFocus != null) {
1502             mHalAudioFocus.reset();
1503             mHalAudioFocus.registerFocusListener();
1504         }
1505     }
1506 
isAudioZoneIdValid(int zoneId)1507     boolean isAudioZoneIdValid(int zoneId) {
1508         synchronized (mImplLock) {
1509             return mCarAudioZones.contains(zoneId);
1510         }
1511     }
1512 
getCarAudioZone(int zoneId)1513     private CarAudioZone getCarAudioZone(int zoneId) {
1514         synchronized (mImplLock) {
1515             return getCarAudioZoneLocked(zoneId);
1516         }
1517     }
1518 
1519     @GuardedBy("mImplLock")
getCarAudioZoneLocked(int zoneId)1520     private CarAudioZone getCarAudioZoneLocked(int zoneId) {
1521         checkAudioZoneIdLocked(zoneId);
1522         return mCarAudioZones.get(zoneId);
1523     }
1524 
1525     @GuardedBy("mImplLock")
checkAudioZoneIdLocked(int zoneId)1526     private void checkAudioZoneIdLocked(int zoneId) {
1527         Preconditions.checkArgument(mCarAudioZones.contains(zoneId),
1528                 "Invalid audio zone Id " + zoneId);
1529     }
1530 
getVolumeGroupIdForAudioContext(int zoneId, int suggestedContext)1531     int getVolumeGroupIdForAudioContext(int zoneId, int suggestedContext) {
1532         synchronized (mImplLock) {
1533             return getVolumeGroupIdForAudioContextLocked(zoneId, suggestedContext);
1534         }
1535     }
1536 
1537     /**
1538      * Resets the last selected volume context.
1539      */
resetSelectedVolumeContext()1540     public void resetSelectedVolumeContext() {
1541         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1542         mCarVolume.resetSelectedVolumeContext();
1543         synchronized (mImplLock) {
1544             mCarAudioPlaybackCallback.resetStillActiveContexts();
1545         }
1546     }
1547 
1548     private class CarAudioOccupantConfigChangeListener implements OccupantZoneConfigChangeListener {
1549         @Override
onOccupantZoneConfigChanged(int flags)1550         public void onOccupantZoneConfigChanged(int flags) {
1551             if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
1552                 Slog.d(CarLog.TAG_AUDIO,
1553                         "onOccupantZoneConfigChanged(" + flags + ")");
1554             }
1555             if (((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER)
1556                     == CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER)
1557                     || ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY)
1558                     == CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY)) {
1559                 handleOccupantZoneUserChanged();
1560             }
1561         }
1562     }
1563 
getAllActiveContextsForPrimaryZone()1564     private List<Integer> getAllActiveContextsForPrimaryZone() {
1565         synchronized (mImplLock) {
1566             return mCarAudioPlaybackCallback.getAllActiveContextsForPrimaryZone();
1567         }
1568     }
1569 
1570     static final class SystemClockWrapper {
uptimeMillis()1571         public long uptimeMillis() {
1572             return SystemClock.uptimeMillis();
1573         }
1574     }
1575 }
1576