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