• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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