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