1 /* 2 * Copyright (C) 2021 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 17 package android.car.builtin.media; 18 19 import static android.media.AudioAttributes.USAGE_VIRTUAL_SOURCE; 20 import static android.media.AudioManager.EXTRA_VOLUME_STREAM_TYPE; 21 import static android.media.AudioManager.GET_DEVICES_INPUTS; 22 import static android.media.AudioManager.GET_DEVICES_OUTPUTS; 23 import static android.media.AudioManager.MASTER_MUTE_CHANGED_ACTION; 24 import static android.media.AudioManager.VOLUME_CHANGED_ACTION; 25 26 import android.annotation.NonNull; 27 import android.annotation.RequiresApi; 28 import android.annotation.SystemApi; 29 import android.car.builtin.annotation.AddedIn; 30 import android.car.builtin.annotation.PlatformVersion; 31 import android.car.builtin.util.Slogf; 32 import android.content.BroadcastReceiver; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.media.AudioAttributes; 37 import android.media.AudioAttributes.AttributeUsage; 38 import android.media.AudioDeviceInfo; 39 import android.media.AudioDevicePort; 40 import android.media.AudioFormat; 41 import android.media.AudioGain; 42 import android.media.AudioGainConfig; 43 import android.media.AudioManager; 44 import android.media.AudioPatch; 45 import android.media.AudioPortConfig; 46 import android.media.AudioSystem; 47 import android.media.audiopolicy.AudioProductStrategy; 48 import android.os.Build; 49 import android.text.TextUtils; 50 51 import com.android.internal.util.Preconditions; 52 53 import java.util.ArrayList; 54 import java.util.Map; 55 import java.util.Objects; 56 57 /** 58 * Helper for Audio related operations. 59 * 60 * @hide 61 */ 62 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 63 public final class AudioManagerHelper { 64 private static final String TAG = AudioManagerHelper.class.getSimpleName(); 65 66 @AddedIn(PlatformVersion.TIRAMISU_0) 67 public static final int UNDEFINED_STREAM_TYPE = -1; 68 69 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 70 @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0) 71 public static final String AUDIO_ATTRIBUTE_TAG_SEPARATOR = ";"; 72 AudioManagerHelper()73 private AudioManagerHelper() { 74 throw new UnsupportedOperationException(); 75 } 76 77 /** 78 * Set the audio device gain for device with {@code address} 79 * @param audioManager audio manager 80 * @param address Address for device to set gain 81 * @param gainInMillibels gain in millibels to set 82 * @param isOutput is the device an output device 83 * @return true if the gain was successfully set 84 */ 85 @AddedIn(PlatformVersion.TIRAMISU_0) setAudioDeviceGain(@onNull AudioManager audioManager, @NonNull String address, int gainInMillibels, boolean isOutput)86 public static boolean setAudioDeviceGain(@NonNull AudioManager audioManager, 87 @NonNull String address, int gainInMillibels, boolean isOutput) { 88 Preconditions.checkNotNull(audioManager, 89 "Audio Manager can not be null in set device gain, device address %s", address); 90 AudioDeviceInfo deviceInfo = getAudioDeviceInfo(audioManager, address, isOutput); 91 92 AudioGain audioGain = getAudioGain(deviceInfo.getPort()); 93 94 // size of gain values is 1 in MODE_JOINT 95 AudioGainConfig audioGainConfig = audioGain.buildConfig( 96 AudioGain.MODE_JOINT, 97 audioGain.channelMask(), 98 new int[] { gainInMillibels }, 99 0); 100 if (audioGainConfig == null) { 101 throw new IllegalStateException("Failed to construct AudioGainConfig for device " 102 + address); 103 } 104 105 return AudioManager.setAudioPortGain(deviceInfo.getPort(), audioGainConfig) 106 == AudioManager.SUCCESS; 107 } 108 getAudioDeviceInfo(@onNull AudioManager audioManager, @NonNull String address, boolean isOutput)109 private static AudioDeviceInfo getAudioDeviceInfo(@NonNull AudioManager audioManager, 110 @NonNull String address, boolean isOutput) { 111 Objects.requireNonNull(address, "Device address can not be null"); 112 Preconditions.checkStringNotEmpty(address, "Device Address can not be empty"); 113 114 AudioDeviceInfo[] devices = 115 audioManager.getDevices(isOutput ? GET_DEVICES_OUTPUTS : GET_DEVICES_INPUTS); 116 117 for (int index = 0; index < devices.length; index++) { 118 AudioDeviceInfo device = devices[index]; 119 if (address.equals(device.getAddress())) { 120 return device; 121 } 122 } 123 124 throw new IllegalStateException((isOutput ? "Output" : "Input") 125 + " Audio device info not found for device address " + address); 126 } 127 getAudioGain(@onNull AudioDevicePort deviceport)128 private static AudioGain getAudioGain(@NonNull AudioDevicePort deviceport) { 129 Objects.requireNonNull(deviceport, "Audio device port can not be null"); 130 Preconditions.checkArgument(deviceport.gains().length > 0, 131 "Audio device must have gains defined"); 132 for (int index = 0; index < deviceport.gains().length; index++) { 133 AudioGain gain = deviceport.gains()[index]; 134 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) { 135 return checkAudioGainConfiguration(gain); 136 } 137 } 138 throw new IllegalStateException("Audio device does not have a valid audio gain"); 139 } 140 checkAudioGainConfiguration(@onNull AudioGain audioGain)141 private static AudioGain checkAudioGainConfiguration(@NonNull AudioGain audioGain) { 142 Preconditions.checkArgument(audioGain.maxValue() >= audioGain.minValue(), 143 "Max gain %d is lower than min gain %d", 144 audioGain.maxValue(), audioGain.minValue()); 145 Preconditions.checkArgument((audioGain.defaultValue() >= audioGain.minValue()) 146 && (audioGain.defaultValue() <= audioGain.maxValue()), 147 "Default gain %d not in range (%d,%d)", audioGain.defaultValue(), 148 audioGain.minValue(), audioGain.maxValue()); 149 Preconditions.checkArgument( 150 ((audioGain.maxValue() - audioGain.minValue()) % audioGain.stepValue()) == 0, 151 "Gain step value %d greater than min gain to max gain range %d", 152 audioGain.stepValue(), audioGain.maxValue() - audioGain.minValue()); 153 Preconditions.checkArgument( 154 ((audioGain.defaultValue() - audioGain.minValue()) % audioGain.stepValue()) == 0, 155 "Gain step value %d greater than min gain to default gain range %d", 156 audioGain.stepValue(), audioGain.defaultValue() - audioGain.minValue()); 157 return audioGain; 158 } 159 160 /** 161 * Returns the audio gain information for the specified device. 162 * @param deviceInfo 163 * @return 164 */ 165 @AddedIn(PlatformVersion.TIRAMISU_0) getAudioGainInfo(@onNull AudioDeviceInfo deviceInfo)166 public static AudioGainInfo getAudioGainInfo(@NonNull AudioDeviceInfo deviceInfo) { 167 Objects.requireNonNull(deviceInfo); 168 return new AudioGainInfo(getAudioGain(deviceInfo.getPort())); 169 } 170 171 /** 172 * Creates an audio patch from source and sink source 173 * @param sourceDevice Source device for the patch 174 * @param sinkDevice Sink device of the patch 175 * @param gainInMillibels gain to apply to the source device 176 * @return The audio patch information that was created 177 */ 178 @AddedIn(PlatformVersion.TIRAMISU_0) createAudioPatch(@onNull AudioDeviceInfo sourceDevice, @NonNull AudioDeviceInfo sinkDevice, int gainInMillibels)179 public static AudioPatchInfo createAudioPatch(@NonNull AudioDeviceInfo sourceDevice, 180 @NonNull AudioDeviceInfo sinkDevice, int gainInMillibels) { 181 Preconditions.checkNotNull(sourceDevice, 182 "Source device can not be null, sink info %s", sinkDevice); 183 Preconditions.checkNotNull(sinkDevice, 184 "Sink device can not be null, source info %s", sourceDevice); 185 186 AudioDevicePort sinkPort = Preconditions.checkNotNull(sinkDevice.getPort(), 187 "Sink device [%s] does not contain an audio port", sinkDevice); 188 189 // {@link android.media.AudioPort#activeConfig()} is valid for mixer port only, 190 // since audio framework has no clue what's active on the device ports. 191 // Therefore we construct an empty / default configuration here, which the audio HAL 192 // implementation should ignore. 193 AudioPortConfig sinkConfig = sinkPort.buildConfig(0, 194 AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT, null); 195 Slogf.d(TAG, "createAudioPatch sinkConfig: " + sinkConfig); 196 197 // Configure the source port to match the output port except for a gain adjustment 198 AudioGain audioGain = Objects.requireNonNull(getAudioGain(sourceDevice.getPort()), 199 "Gain controller not available for source port"); 200 201 // size of gain values is 1 in MODE_JOINT 202 AudioGainConfig audioGainConfig = audioGain.buildConfig(AudioGain.MODE_JOINT, 203 audioGain.channelMask(), new int[] { gainInMillibels }, 0); 204 // Construct an empty / default configuration excepts gain config here and it's up to the 205 // audio HAL how to interpret this configuration, which the audio HAL 206 // implementation should ignore. 207 AudioPortConfig sourceConfig = sourceDevice.getPort().buildConfig(0, 208 AudioFormat.CHANNEL_IN_DEFAULT, AudioFormat.ENCODING_DEFAULT, audioGainConfig); 209 210 // Create an audioPatch to connect the two ports 211 AudioPatch[] patch = new AudioPatch[] { null }; 212 int result = AudioManager.createAudioPatch(patch, 213 new AudioPortConfig[] { sourceConfig }, 214 new AudioPortConfig[] { sinkConfig }); 215 if (result != AudioManager.SUCCESS) { 216 throw new RuntimeException("createAudioPatch failed with code " + result); 217 } 218 219 Preconditions.checkNotNull(patch[0], 220 "createAudioPatch didn't provide expected single handle [source: %s,sink: %s]", 221 sinkDevice, sourceDevice); 222 Slogf.d(TAG, "Audio patch created: " + patch[0]); 223 224 return createAudioPatchInfo(patch[0]); 225 } 226 createAudioPatchInfo(AudioPatch patch)227 private static AudioPatchInfo createAudioPatchInfo(AudioPatch patch) { 228 Preconditions.checkArgument(patch.sources().length == 1 229 && patch.sources()[0].port() instanceof AudioDevicePort, 230 "Accepts exactly one device port as source"); 231 Preconditions.checkArgument(patch.sinks().length == 1 232 && patch.sinks()[0].port() instanceof AudioDevicePort, 233 "Accepts exactly one device port as sink"); 234 235 return new AudioPatchInfo(((AudioDevicePort) patch.sources()[0].port()).address(), 236 ((AudioDevicePort) patch.sinks()[0].port()).address(), 237 patch.id()); 238 } 239 240 /** 241 * Releases audio patch handle 242 * @param audioManager manager to call for releasing of handle 243 * @param info patch information to release 244 * @return returns true if the patch was successfully removed 245 */ 246 @AddedIn(PlatformVersion.TIRAMISU_0) releaseAudioPatch(@onNull AudioManager audioManager, @NonNull AudioPatchInfo info)247 public static boolean releaseAudioPatch(@NonNull AudioManager audioManager, 248 @NonNull AudioPatchInfo info) { 249 Preconditions.checkNotNull(audioManager, 250 "Audio Manager can not be null in release audio patch for %s", info); 251 Preconditions.checkNotNull(info, 252 "Audio Patch Info can not be null in release audio patch for %s", info); 253 // NOTE: AudioPolicyService::removeNotificationClient will take care of this automatically 254 // if the client that created a patch quits. 255 ArrayList<AudioPatch> patches = new ArrayList<>(); 256 int result = audioManager.listAudioPatches(patches); 257 if (result != AudioManager.SUCCESS) { 258 throw new RuntimeException("listAudioPatches failed with code " + result); 259 } 260 261 // Look for a patch that matches the provided user side handle 262 for (AudioPatch patch : patches) { 263 if (info.represents(patch)) { 264 // Found it! 265 result = AudioManager.releaseAudioPatch(patch); 266 if (result != AudioManager.SUCCESS) { 267 throw new RuntimeException("releaseAudioPatch failed with code " + result); 268 } 269 return true; 270 } 271 } 272 return false; 273 } 274 275 /** 276 * Returns the string representation of {@link android.media.AudioAttributes.AttributeUsage}. 277 * 278 * <p>See {@link android.media.AudioAttributes.usageToString}. 279 */ 280 @AddedIn(PlatformVersion.TIRAMISU_0) usageToString(@ttributeUsage int usage)281 public static String usageToString(@AttributeUsage int usage) { 282 return AudioAttributes.usageToString(usage); 283 } 284 285 /** 286 * Returns the xsd string representation of 287 * {@link android.media.AudioAttributes.AttributeUsage}. 288 * 289 * <p>See {@link android.media.AudioAttributes.usageToXsdString}. 290 */ 291 @AddedIn(PlatformVersion.TIRAMISU_0) usageToXsdString(@ttributeUsage int usage)292 public static String usageToXsdString(@AttributeUsage int usage) { 293 return AudioAttributes.usageToXsdString(usage); 294 } 295 296 /** 297 * Returns {@link android.media.AudioAttributes.AttributeUsage} representation of 298 * xsd usage string. 299 * 300 * <p>See {@link android.media.AudioAttributes.xsdStringToUsage}. 301 */ 302 @AddedIn(PlatformVersion.TIRAMISU_0) xsdStringToUsage(String usage)303 public static int xsdStringToUsage(String usage) { 304 return AudioAttributes.xsdStringToUsage(usage); 305 } 306 307 /** 308 * Returns {@link android.media.AudioAttributes.AttributeUsage} for 309 * {@link android.media.AudioAttributes.AttributeUsage.USAGE_VIRTUAL_SOURCE}. 310 */ 311 @AddedIn(PlatformVersion.TIRAMISU_0) getUsageVirtualSource()312 public static int getUsageVirtualSource() { 313 return USAGE_VIRTUAL_SOURCE; 314 } 315 316 /** 317 * Returns the string representation of volume adjustment. 318 * 319 * <p>See {@link android.media.AudioManager#adjustToString(int)} 320 */ 321 @AddedIn(PlatformVersion.TIRAMISU_0) adjustToString(int adjustment)322 public static String adjustToString(int adjustment) { 323 return AudioManager.adjustToString(adjustment); 324 } 325 326 /** 327 * Sets the system master mute state. 328 * 329 * <p>See {@link android.media.AudioManager#setMasterMute(boolean, int)}. 330 */ 331 @AddedIn(PlatformVersion.TIRAMISU_0) setMasterMute(@onNull AudioManager audioManager, boolean mute, int flags)332 public static void setMasterMute(@NonNull AudioManager audioManager, boolean mute, int flags) { 333 Objects.requireNonNull(audioManager, "AudioManager must not be null."); 334 audioManager.setMasterMute(mute, flags); 335 } 336 337 /** 338 * Gets system master mute state. 339 * 340 * <p>See {@link android.media.AudioManager#isMasterMute()}. 341 */ 342 @AddedIn(PlatformVersion.TIRAMISU_0) isMasterMute(@onNull AudioManager audioManager)343 public static boolean isMasterMute(@NonNull AudioManager audioManager) { 344 Objects.requireNonNull(audioManager, "AudioManager must not be null."); 345 return audioManager.isMasterMute(); 346 } 347 348 /** 349 * Registers volume and mute receiver 350 */ 351 @AddedIn(PlatformVersion.TIRAMISU_0) registerVolumeAndMuteReceiver(Context context, VolumeAndMuteReceiver audioAndMuteHelper)352 public static void registerVolumeAndMuteReceiver(Context context, 353 VolumeAndMuteReceiver audioAndMuteHelper) { 354 Objects.requireNonNull(context, "Context can not be null."); 355 Objects.requireNonNull(audioAndMuteHelper, "Audio and Mute helper can not be null."); 356 357 IntentFilter intentFilter = new IntentFilter(); 358 intentFilter.addAction(VOLUME_CHANGED_ACTION); 359 intentFilter.addAction(MASTER_MUTE_CHANGED_ACTION); 360 context.registerReceiver(audioAndMuteHelper.getReceiver(), intentFilter, 361 Context.RECEIVER_NOT_EXPORTED); 362 } 363 364 /** 365 * Unregisters volume and mute receiver 366 */ 367 @AddedIn(PlatformVersion.TIRAMISU_0) unregisterVolumeAndMuteReceiver(Context context, VolumeAndMuteReceiver audioAndMuteHelper)368 public static void unregisterVolumeAndMuteReceiver(Context context, 369 VolumeAndMuteReceiver audioAndMuteHelper) { 370 Objects.requireNonNull(context, "Context can not be null."); 371 Objects.requireNonNull(audioAndMuteHelper, "Audio and Mute helper can not be null."); 372 373 context.unregisterReceiver(audioAndMuteHelper.getReceiver()); 374 } 375 376 /** 377 * Checks if the client id is equal to the telephony's focus client id. 378 */ 379 @AddedIn(PlatformVersion.TIRAMISU_0) isCallFocusRequestClientId(String clientId)380 public static boolean isCallFocusRequestClientId(String clientId) { 381 return AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId); 382 } 383 384 385 /** 386 * Audio gain information for a particular device: 387 * Contains Max, Min, Default gain and the step value between gain changes 388 */ 389 public static class AudioGainInfo { 390 391 private final int mMinGain; 392 private final int mMaxGain; 393 private final int mDefaultGain; 394 private final int mStepValue; 395 AudioGainInfo(AudioGain gain)396 private AudioGainInfo(AudioGain gain) { 397 mMinGain = gain.minValue(); 398 mMaxGain = gain.maxValue(); 399 mDefaultGain = gain.defaultValue(); 400 mStepValue = gain.stepValue(); 401 } 402 403 @AddedIn(PlatformVersion.TIRAMISU_0) getMinGain()404 public int getMinGain() { 405 return mMinGain; 406 } 407 408 @AddedIn(PlatformVersion.TIRAMISU_0) getMaxGain()409 public int getMaxGain() { 410 return mMaxGain; 411 } 412 413 @AddedIn(PlatformVersion.TIRAMISU_0) getDefaultGain()414 public int getDefaultGain() { 415 return mDefaultGain; 416 } 417 418 @AddedIn(PlatformVersion.TIRAMISU_0) getStepValue()419 public int getStepValue() { 420 return mStepValue; 421 } 422 } 423 424 /** 425 * Contains the audio patch information for the created audio patch: 426 * Patch handle id, source device address, sink device address 427 */ 428 public static class AudioPatchInfo { 429 private final int mHandleId; 430 431 private final String mSourceAddress; 432 private final String mSinkAddress; 433 434 AudioPatchInfo(@onNull String sourceAddress, @NonNull String sinkAddress, int handleId)435 public AudioPatchInfo(@NonNull String sourceAddress, @NonNull String sinkAddress, 436 int handleId) { 437 mSourceAddress = Preconditions.checkNotNull(sourceAddress, 438 "Source Address can not be null for patch id %d", handleId); 439 mSinkAddress = Preconditions.checkNotNull(sinkAddress, 440 "Sink Address can not be null for patch id %d", handleId); 441 mHandleId = handleId; 442 } 443 444 @AddedIn(PlatformVersion.TIRAMISU_0) getHandleId()445 public int getHandleId() { 446 return mHandleId; 447 } 448 449 @AddedIn(PlatformVersion.TIRAMISU_0) getSourceAddress()450 public String getSourceAddress() { 451 return mSourceAddress; 452 } 453 454 @AddedIn(PlatformVersion.TIRAMISU_0) getSinkAddress()455 public String getSinkAddress() { 456 return mSinkAddress; 457 } 458 459 @Override toString()460 public String toString() { 461 StringBuilder builder = new StringBuilder(); 462 builder.append("Source{ "); 463 builder.append(mSourceAddress); 464 builder.append("} Sink{ "); 465 builder.append(mSinkAddress); 466 builder.append("} Handle{ "); 467 builder.append(mHandleId); 468 builder.append("}"); 469 return builder.toString(); 470 } 471 represents(AudioPatch patch)472 private boolean represents(AudioPatch patch) { 473 return patch.id() == mHandleId; 474 } 475 } 476 477 /** 478 * Class to manage volume and mute changes from audio manager 479 */ 480 public abstract static class VolumeAndMuteReceiver { 481 482 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 483 484 @Override 485 public void onReceive(Context context, Intent intent) { 486 switch (intent.getAction()) { 487 case VOLUME_CHANGED_ACTION: 488 int streamType = 489 intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, UNDEFINED_STREAM_TYPE); 490 onVolumeChanged(streamType); 491 break; 492 case MASTER_MUTE_CHANGED_ACTION: 493 onMuteChanged(); 494 break; 495 default: 496 break; 497 } 498 } 499 }; 500 getReceiver()501 private BroadcastReceiver getReceiver() { 502 return mReceiver; 503 } 504 505 /** 506 * Called on volume changes 507 * @param streamType type of stream for the volume change 508 */ 509 @AddedIn(PlatformVersion.TIRAMISU_0) onVolumeChanged(int streamType)510 public abstract void onVolumeChanged(int streamType); 511 512 /** 513 * Called on mute changes 514 */ 515 @AddedIn(PlatformVersion.TIRAMISU_0) onMuteChanged()516 public abstract void onMuteChanged(); 517 } 518 519 /** 520 * Adds a tags to the {@link AudioAttributes}. 521 * 522 * <p>{@link AudioProductStrategy} may use additional information to override the current 523 * stream limitation used for routing. 524 * 525 * <p>As Bundler are not propagated to native layer, tags were used to be dispatched to the 526 * AudioPolicyManager. 527 * 528 * @param builder {@link AudioAttributes.Builder} helper to build {@link AudioAttributes} 529 * @param tag to be added to the {@link AudioAttributes} once built. 530 */ 531 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 532 @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0) addTagToAudioAttributes(@onNull AudioAttributes.Builder builder, @NonNull String tag)533 public static void addTagToAudioAttributes(@NonNull AudioAttributes.Builder builder, 534 @NonNull String tag) { 535 builder.addTag(tag); 536 } 537 538 /** 539 * Gets a separated string of tags associated to given {@link AudioAttributes} 540 * 541 * @param attributes {@link AudioAttributes} to be considered 542 * @return the tags of the given {@link AudioAttributes} as a 543 * {@link #AUDIO_ATTRIBUTE_TAG_SEPARATOR} separated string. 544 */ 545 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 546 @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0) getFormattedTags(@onNull AudioAttributes attributes)547 public static String getFormattedTags(@NonNull AudioAttributes attributes) { 548 Preconditions.checkNotNull(attributes, "Audio Attributes must not be null"); 549 return TextUtils.join(AUDIO_ATTRIBUTE_TAG_SEPARATOR, attributes.getTags()); 550 } 551 552 private static final Map<String, Integer> XSD_STRING_TO_CONTENT_TYPE = Map.of( 553 "AUDIO_CONTENT_TYPE_UNKNOWN", AudioAttributes.CONTENT_TYPE_UNKNOWN, 554 "AUDIO_CONTENT_TYPE_SPEECH", AudioAttributes.CONTENT_TYPE_SPEECH, 555 "AUDIO_CONTENT_TYPE_MUSIC", AudioAttributes.CONTENT_TYPE_MUSIC, 556 "AUDIO_CONTENT_TYPE_MOVIE", AudioAttributes.CONTENT_TYPE_MOVIE, 557 "AUDIO_CONTENT_TYPE_SONIFICATION", AudioAttributes.CONTENT_TYPE_SONIFICATION, 558 "AUDIO_CONTENT_TYPE_ULTRASOUND", AudioAttributes.CONTENT_TYPE_ULTRASOUND 559 ); 560 561 /** 562 * Converts a literal representation of tags into {@link AudioAttributes.ContentType} value. 563 * 564 * @param xsdString string to be converted into {@link AudioAttributes.ContentType} 565 * @return {@link AudioAttributes.ContentType} representation of xsd content type string if 566 * found, {@code AudioAttributes.CONTENT_TYPE_UNKNOWN} otherwise. 567 */ 568 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 569 @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0) xsdStringToContentType(String xsdString)570 public static int xsdStringToContentType(String xsdString) { 571 if (XSD_STRING_TO_CONTENT_TYPE.containsKey(xsdString)) { 572 return XSD_STRING_TO_CONTENT_TYPE.get(xsdString); 573 } 574 return AudioAttributes.CONTENT_TYPE_UNKNOWN; 575 } 576 577 /** 578 * Gets the {@link android.media.AudioVolumeGroup} id associated with given 579 * {@link AudioProductStrategy} and {@link AudioAttributes} 580 * 581 * @param strategy {@link AudioProductStrategy} to be considered 582 * @param attributes {@link AudioAttributes} to be considered 583 * @return the id of the {@link android.media.AudioVolumeGroup} supporting the given 584 * {@link AudioAttributes} and {@link AudioProductStrategy} if found, 585 * {@link android.media.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise. 586 */ 587 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 588 @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0) getVolumeGroupIdForAudioAttributes( @onNull AudioProductStrategy strategy, @NonNull AudioAttributes attributes)589 public static int getVolumeGroupIdForAudioAttributes( 590 @NonNull AudioProductStrategy strategy, @NonNull AudioAttributes attributes) { 591 Preconditions.checkNotNull(attributes, "Audio Attributes must not be null"); 592 Preconditions.checkNotNull(strategy, "Audio Product Strategy must not be null"); 593 return strategy.getVolumeGroupIdForAudioAttributes(attributes); 594 } 595 596 /** 597 * Gets the last audible volume for a given {@link android.media.AudioVolumeGroup} id. 598 * <p>The last audible index is the current index if not muted, or index applied before mute if 599 * muted. If muted by volume 0, the last audible index is 0. See 600 * {@link AudioManager#getLastAudibleVolumeForVolumeGroup} for details. 601 * 602 * @param audioManager {@link AudioManager} instance to be used for the request 603 * @param amGroupId id of the {@link android.media.AudioVolumeGroup} to consider 604 * @return the last audible volume of the {@link android.media.AudioVolumeGroup} 605 * referred by its id if found, {@code 0} otherwise. 606 */ 607 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 608 @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0) getLastAudibleVolumeGroupVolume(@onNull AudioManager audioManager, int amGroupId)609 public static int getLastAudibleVolumeGroupVolume(@NonNull AudioManager audioManager, 610 int amGroupId) { 611 Objects.requireNonNull(audioManager, "Audio manager can not be null"); 612 return audioManager.getLastAudibleVolumeForVolumeGroup(amGroupId); 613 } 614 615 /** 616 * Checks if the given {@link android.media.AudioVolumeGroup} is muted or not. 617 * <p>See {@link AudioManager#isVolumeGroupMuted} for details 618 * 619 * @param audioManager {@link AudioManager} instance to be used for the request 620 * @param amGroupId id of the {@link android.media.AudioVolumeGroup} to consider 621 * @return true if the {@link android.media.AudioVolumeGroup} referred by its id is found and 622 * muted, false otherwise. 623 */ 624 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 625 @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0) isVolumeGroupMuted(@onNull AudioManager audioManager, int amGroupId)626 public static boolean isVolumeGroupMuted(@NonNull AudioManager audioManager, int amGroupId) { 627 Objects.requireNonNull(audioManager, "Audio manager can not be null"); 628 return audioManager.isVolumeGroupMuted(amGroupId); 629 } 630 631 /** 632 * Adjusts the volume for the {@link android.media.AudioVolumeGroup} id if found. No-operation 633 * otherwise. 634 * <p>See {@link AudioManager#adjustVolumeGroupVolume} for details 635 * 636 * @param audioManager audio manager to use for managing the volume group 637 * @param amGroupId id of the {@link android.media.AudioVolumeGroup} to consider 638 * @param direction direction to adjust the volume, one of {@link AudioManager#VolumeAdjustment} 639 * @param flags one ore more flags of {@link AudioManager#Flags} 640 */ 641 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 642 @AddedIn(PlatformVersion.UPSIDE_DOWN_CAKE_0) adjustVolumeGroupVolume(@onNull AudioManager audioManager, int amGroupId, int direction, @AudioManager.Flags int flags)643 public static void adjustVolumeGroupVolume(@NonNull AudioManager audioManager, 644 int amGroupId, int direction, @AudioManager.Flags int flags) { 645 Objects.requireNonNull(audioManager, "Audio manager can not be null"); 646 audioManager.adjustVolumeGroupVolume(amGroupId, direction, flags); 647 } 648 } 649