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