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