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