blob: 1bd09de9b5ff6007eacc4fff9b3e477688d8d923 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v4.media;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.util.SparseIntArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
* A class to encapsulate a collection of attributes describing information about an audio stream.
*
* <p><code>AudioAttributesCompat</code> supersede the notion of stream types (see for instance
* {@link AudioManager#STREAM_MUSIC} or {@link AudioManager#STREAM_ALARM}) for defining the behavior
* of audio playback. Attributes allow an application to specify more information than is conveyed
* in a stream type by allowing the application to define:
*
* <ul>
* <li>usage: "why" you are playing a sound, what is this sound used for. This is achieved with
* the "usage" information. Examples of usage are {@link #USAGE_MEDIA} and {@link
* #USAGE_ALARM}. These two examples are the closest to stream types, but more detailed use
* cases are available. Usage information is more expressive than a stream type, and allows
* certain platforms or routing policies to use this information for more refined volume or
* routing decisions. Usage is the most important information to supply in <code>
* AudioAttributesCompat</code> and it is recommended to build any instance with this
* information supplied, see {@link AudioAttributesCompat.Builder} for exceptions.
* <li>content type: "what" you are playing. The content type expresses the general category of
* the content. This information is optional. But in case it is known (for instance {@link
* #CONTENT_TYPE_MOVIE} for a movie streaming service or {@link #CONTENT_TYPE_MUSIC} for a
* music playback application) this information might be used by the audio framework to
* selectively configure some audio post-processing blocks.
* <li>flags: "how" is playback to be affected, see the flag definitions for the specific playback
* behaviors they control.
* </ul>
*
* <p><code>AudioAttributesCompat</code> instance is built through its builder, {@link
* AudioAttributesCompat.Builder}. Also see {@link android.media.AudioAttributes} for the framework
* implementation of this class.
*/
public class AudioAttributesCompat {
private static final String TAG = "AudioAttributesCompat";
/**
* Content type value to use when the content type is unknown, or other than the ones defined.
*/
public static final int CONTENT_TYPE_UNKNOWN = AudioAttributes.CONTENT_TYPE_UNKNOWN;
/** Content type value to use when the content type is speech. */
public static final int CONTENT_TYPE_SPEECH = AudioAttributes.CONTENT_TYPE_SPEECH;
/** Content type value to use when the content type is music. */
public static final int CONTENT_TYPE_MUSIC = AudioAttributes.CONTENT_TYPE_MUSIC;
/**
* Content type value to use when the content type is a soundtrack, typically accompanying a
* movie or TV program.
*/
public static final int CONTENT_TYPE_MOVIE = AudioAttributes.CONTENT_TYPE_MOVIE;
/**
* Content type value to use when the content type is a sound used to accompany a user action,
* such as a beep or sound effect expressing a key click, or event, such as the type of a sound
* for a bonus being received in a game. These sounds are mostly synthesized or short Foley
* sounds.
*/
public static final int CONTENT_TYPE_SONIFICATION = AudioAttributes.CONTENT_TYPE_SONIFICATION;
/** Usage value to use when the usage is unknown. */
public static final int USAGE_UNKNOWN = AudioAttributes.USAGE_UNKNOWN;
/** Usage value to use when the usage is media, such as music, or movie soundtracks. */
public static final int USAGE_MEDIA = AudioAttributes.USAGE_MEDIA;
/** Usage value to use when the usage is voice communications, such as telephony or VoIP. */
public static final int USAGE_VOICE_COMMUNICATION = AudioAttributes.USAGE_VOICE_COMMUNICATION;
/**
* Usage value to use when the usage is in-call signalling, such as with a "busy" beep, or DTMF
* tones.
*/
public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING =
AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
/** Usage value to use when the usage is an alarm (e.g. wake-up alarm). */
public static final int USAGE_ALARM = AudioAttributes.USAGE_ALARM;
/**
* Usage value to use when the usage is notification. See other notification usages for more
* specialized uses.
*/
public static final int USAGE_NOTIFICATION = AudioAttributes.USAGE_NOTIFICATION;
/** Usage value to use when the usage is telephony ringtone. */
public static final int USAGE_NOTIFICATION_RINGTONE =
AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
/**
* Usage value to use when the usage is a request to enter/end a communication, such as a VoIP
* communication or video-conference.
*/
public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST =
AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
/**
* Usage value to use when the usage is notification for an "instant" communication such as a
* chat, or SMS.
*/
public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT =
AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT;
/**
* Usage value to use when the usage is notification for a non-immediate type of communication
* such as e-mail.
*/
public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED =
AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
/**
* Usage value to use when the usage is to attract the user's attention, such as a reminder or
* low battery warning.
*/
public static final int USAGE_NOTIFICATION_EVENT = AudioAttributes.USAGE_NOTIFICATION_EVENT;
/** Usage value to use when the usage is for accessibility, such as with a screen reader. */
public static final int USAGE_ASSISTANCE_ACCESSIBILITY =
AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
/** Usage value to use when the usage is driving or navigation directions. */
public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE =
AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
/** Usage value to use when the usage is sonification, such as with user interface sounds. */
public static final int USAGE_ASSISTANCE_SONIFICATION =
AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
/** Usage value to use when the usage is for game audio. */
public static final int USAGE_GAME = AudioAttributes.USAGE_GAME;
// usage not available to clients
private static final int USAGE_VIRTUAL_SOURCE = 15; // AudioAttributes.USAGE_VIRTUAL_SOURCE;
/**
* Usage value to use for audio responses to user queries, audio instructions or help
* utterances.
*/
public static final int USAGE_ASSISTANT = AudioAttributes.USAGE_ASSISTANT;
/**
* IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
* if applicable.
*/
// private API
private static final int SUPPRESSIBLE_NOTIFICATION = 1;
private static final int SUPPRESSIBLE_CALL = 2;
private static final SparseIntArray SUPPRESSIBLE_USAGES;
// used by tests
private static boolean sForceLegacyBehavior;
static {
SUPPRESSIBLE_USAGES = new SparseIntArray();
SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION, SUPPRESSIBLE_NOTIFICATION);
SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_RINGTONE, SUPPRESSIBLE_CALL);
SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_COMMUNICATION_REQUEST, SUPPRESSIBLE_CALL);
SUPPRESSIBLE_USAGES.put(
USAGE_NOTIFICATION_COMMUNICATION_INSTANT, SUPPRESSIBLE_NOTIFICATION);
SUPPRESSIBLE_USAGES.put(
USAGE_NOTIFICATION_COMMUNICATION_DELAYED, SUPPRESSIBLE_NOTIFICATION);
SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT, SUPPRESSIBLE_NOTIFICATION);
}
private static final int[] SDK_USAGES = {
USAGE_UNKNOWN,
USAGE_MEDIA,
USAGE_VOICE_COMMUNICATION,
USAGE_VOICE_COMMUNICATION_SIGNALLING,
USAGE_ALARM,
USAGE_NOTIFICATION,
USAGE_NOTIFICATION_RINGTONE,
USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
USAGE_NOTIFICATION_EVENT,
USAGE_ASSISTANCE_ACCESSIBILITY,
USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
USAGE_ASSISTANCE_SONIFICATION,
USAGE_GAME,
USAGE_ASSISTANT,
};
/** Flag defining a behavior where the audibility of the sound will be ensured by the system. */
public static final int FLAG_AUDIBILITY_ENFORCED = 0x1 << 0;
// flags for @hide API so we can create a proper flags mask
private static final int FLAG_SECURE = 0x1 << 1;
private static final int FLAG_SCO = 0x1 << 2;
private static final int FLAG_BEACON = 0x1 << 3;
/** Flag requesting the use of an output stream supporting hardware A/V synchronization. */
public static final int FLAG_HW_AV_SYNC = 0x1 << 4;
// more @hide flags
private static final int FLAG_HW_HOTWORD = 0x1 << 5;
private static final int FLAG_BYPASS_INTERRUPTION_POLICY = 0x1 << 6;
private static final int FLAG_BYPASS_MUTE = 0x1 << 7;
private static final int FLAG_LOW_LATENCY = 0x1 << 8;
private static final int FLAG_DEEP_BUFFER = 0x1 << 9;
private static final int FLAG_ALL =
(FLAG_AUDIBILITY_ENFORCED
| FLAG_SECURE
| FLAG_SCO
| FLAG_BEACON
| FLAG_HW_AV_SYNC
| FLAG_HW_HOTWORD
| FLAG_BYPASS_INTERRUPTION_POLICY
| FLAG_BYPASS_MUTE
| FLAG_LOW_LATENCY
| FLAG_DEEP_BUFFER);
private static final int FLAG_ALL_PUBLIC =
(FLAG_AUDIBILITY_ENFORCED | FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY);
int mUsage = USAGE_UNKNOWN;
int mContentType = CONTENT_TYPE_UNKNOWN;
int mFlags = 0x0;
Integer mLegacyStream;
private AudioAttributesCompatApi21.Wrapper mAudioAttributesWrapper;
private AudioAttributesCompat() {
}
/**
* Returns the stream type matching the given attributes for volume control. Use this method to
* derive the stream type needed to configure the volume control slider in an {@link
* android.app.Activity} with {@link android.app.Activity#setVolumeControlStream(int)}. <br>
* Do not use this method to set the stream type on an audio player object (e.g. {@link
* android.media.AudioTrack}, {@link android.media.MediaPlayer}) as this is deprecated;
* use <code>AudioAttributes</code> instead.
*
* @return a valid stream type for <code>Activity</code> or stream volume control that matches
* the attributes, or {@link AudioManager#USE_DEFAULT_STREAM_TYPE} if there isn't a direct
* match. Note that <code>USE_DEFAULT_STREAM_TYPE</code> is not a valid value for {@link
* AudioManager#setStreamVolume(int, int, int)}.
*/
public int getVolumeControlStream() {
if (this == null) {
throw new IllegalArgumentException("Invalid null audio attributes");
}
if (Build.VERSION.SDK_INT >= 26
&& !sForceLegacyBehavior
&& unwrap() != null) {
return ((AudioAttributes) unwrap()).getVolumeControlStream();
}
return toVolumeStreamType(true, this);
}
// public API unique to AudioAttributesCompat
/**
* If the current SDK level is 21 or higher, return the {@link AudioAttributes} object inside
* this {@link AudioAttributesCompat}. Otherwise <code>null</code>.
*
* @return the underlying {@link AudioAttributes} object or null
*/
@Nullable
public Object unwrap() {
if (mAudioAttributesWrapper != null) {
return mAudioAttributesWrapper.unwrap();
}
return null;
}
/**
* Return a stream type passed to {@link Builder#setLegacyStreamType(int)}, or -1 if no legacy
* stream is available
*
* @return the stream type {@see AudioManager}
*/
public int getLegacyStreamType() {
// case 1: developer explicitly set a legacy stream,
// so just hand that back
if (mLegacyStream != null) {
return mLegacyStream;
}
// case 2: API 21+ and we have a real AudioAttributes
// the same caveats in AudioAttributes#toLegacyStreamTyoe apply:
// only use this for volume control
if (Build.VERSION.SDK_INT >= 21) {
if (!sForceLegacyBehavior) {
return AudioAttributesCompatApi21.toLegacyStreamType(mAudioAttributesWrapper);
}
}
// case 3: developer set up AudioAttrs using new flags/usage APIs
// but we are running pre-API21, so use the heuristic below
return toVolumeStreamType(false, mFlags, mUsage);
}
/**
* Create an {@link AudioAttributesCompat} given an API 21 {@link AudioAttributes} object.
*
* @param aa an instance of {@link AudioAttributes}
* @return the new <code>AudioAttributesCompat</code>, or <code>null</code> on API &lt; 21
*/
@Nullable
public static AudioAttributesCompat wrap(@NonNull final Object aa) {
if (Build.VERSION.SDK_INT >= 21 && !sForceLegacyBehavior) {
final AudioAttributesCompat aac = new AudioAttributesCompat();
aac.mAudioAttributesWrapper =
AudioAttributesCompatApi21.Wrapper.wrap((AudioAttributes) aa);
return aac;
}
return null;
}
// The rest of this file implements an approximation to AudioAttributes using old stream types
/**
* Return the content type.
*
* @return one of the values that can be set in {@link Builder#setContentType(int)}
*/
public int getContentType() {
if (Build.VERSION.SDK_INT >= 21
&& !sForceLegacyBehavior
&& mAudioAttributesWrapper != null) {
return mAudioAttributesWrapper.unwrap().getContentType();
} else {
return mContentType;
}
}
/**
* Return the usage.
*
* @return one of the values that can be set in {@link Builder#setUsage(int)}
*/
public @AttributeUsage int getUsage() {
if (Build.VERSION.SDK_INT >= 21
&& !sForceLegacyBehavior
&& mAudioAttributesWrapper != null) {
return mAudioAttributesWrapper.unwrap().getUsage();
} else {
return mUsage;
}
}
/**
* Return the flags.
*
* @return a combined mask of all flags
*/
public int getFlags() {
if (Build.VERSION.SDK_INT >= 21
&& !sForceLegacyBehavior
&& mAudioAttributesWrapper != null) {
return mAudioAttributesWrapper.unwrap().getFlags();
} else {
int flags = mFlags;
int legacyStream = getLegacyStreamType();
if (legacyStream == AudioManagerHidden.STREAM_BLUETOOTH_SCO) {
flags |= FLAG_SCO;
} else if (legacyStream == AudioManagerHidden.STREAM_SYSTEM_ENFORCED) {
flags |= FLAG_AUDIBILITY_ENFORCED;
}
return flags & FLAG_ALL_PUBLIC;
}
}
/**
* Builder class for {@link AudioAttributesCompat} objects.
*
* <p>example:
*
* <pre class="prettyprint">
* new AudioAttributes.Builder()
* .setUsage(AudioAttributes.USAGE_MEDIA)
* .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
* .build();
* </pre>
*
* <p>By default all types of information (usage, content type, flags) conveyed by an <code>
* AudioAttributesCompat</code> instance are set to "unknown". Unknown information will be
* interpreted as a default value that is dependent on the context of use, for instance a {@link
* android.media.MediaPlayer} will use a default usage of
* {@link AudioAttributesCompat#USAGE_MEDIA}. See also {@link AudioAttributes.Builder}.
*/
public static class Builder {
private int mUsage = USAGE_UNKNOWN;
private int mContentType = CONTENT_TYPE_UNKNOWN;
private int mFlags = 0x0;
private Integer mLegacyStream;
private Object mAAObject;
/**
* Constructs a new Builder with the defaults. By default, usage and content type are
* respectively {@link AudioAttributesCompat#USAGE_UNKNOWN} and {@link
* AudioAttributesCompat#CONTENT_TYPE_UNKNOWN}, and flags are 0. It is recommended to
* configure the usage (with {@link #setUsage(int)}) or deriving attributes from a legacy
* stream type (with {@link #setLegacyStreamType(int)}) before calling {@link #build()} to
* override any default playback behavior in terms of routing and volume management.
*/
public Builder() {
}
/**
* Constructs a new Builder from a given AudioAttributes
*
* @param aa the AudioAttributesCompat object whose data will be reused in the new Builder.
*/
public Builder(AudioAttributesCompat aa) {
mUsage = aa.mUsage;
mContentType = aa.mContentType;
mFlags = aa.mFlags;
mLegacyStream = aa.mLegacyStream;
mAAObject = aa.unwrap();
}
/**
* Combines all of the attributes that have been set and return a new {@link
* AudioAttributesCompat} object.
*
* @return a new {@link AudioAttributesCompat} object
*/
public AudioAttributesCompat build() {
if (!sForceLegacyBehavior && Build.VERSION.SDK_INT >= 21) {
// API21
if (mAAObject != null) {
// best case: underlying real AudioAttributes
return wrap(mAAObject);
} else {
// set up an API21 builder with whatever we have
AudioAttributes.Builder api21Builder =
new AudioAttributes.Builder()
.setContentType(mContentType)
.setFlags(mFlags)
.setUsage(mUsage);
if (mLegacyStream != null) {
// if a legacy stream was specified, throw that in
api21Builder.setLegacyStreamType(mLegacyStream);
}
return wrap(api21Builder.build());
}
} else {
// pre-API21
final AudioAttributesCompat aac = new AudioAttributesCompat();
aac.mContentType = mContentType;
aac.mFlags = mFlags;
aac.mUsage = mUsage;
aac.mLegacyStream = mLegacyStream;
aac.mAudioAttributesWrapper = null;
return aac;
}
}
/**
* Sets the attribute describing what is the intended use of the the audio signal, such as
* alarm or ringtone.
*
* @param usage one of {@link AudioAttributesCompat#USAGE_UNKNOWN}, {@link
* AudioAttributesCompat#USAGE_MEDIA}, {@link
* AudioAttributesCompat#USAGE_VOICE_COMMUNICATION}, {@link
* AudioAttributesCompat#USAGE_VOICE_COMMUNICATION_SIGNALLING}, {@link
* AudioAttributesCompat#USAGE_ALARM},
* {@link AudioAttributesCompat#USAGE_NOTIFICATION},
* {@link AudioAttributesCompat#USAGE_NOTIFICATION_RINGTONE}, {@link
* AudioAttributesCompat#USAGE_NOTIFICATION_COMMUNICATION_REQUEST}, {@link
* AudioAttributesCompat#USAGE_NOTIFICATION_COMMUNICATION_INSTANT}, {@link
* AudioAttributesCompat#USAGE_NOTIFICATION_COMMUNICATION_DELAYED}, {@link
* AudioAttributesCompat#USAGE_NOTIFICATION_EVENT}, {@link
* AudioAttributesCompat#USAGE_ASSISTANT}, {@link
* AudioAttributesCompat#USAGE_ASSISTANCE_ACCESSIBILITY}, {@link
* AudioAttributesCompat#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE}, {@link
* AudioAttributesCompat#USAGE_ASSISTANCE_SONIFICATION}, {@link
* AudioAttributesCompat#USAGE_GAME}.
* @return the same Builder instance.
*/
public Builder setUsage(@AttributeUsage int usage) {
switch (usage) {
case USAGE_UNKNOWN:
case USAGE_MEDIA:
case USAGE_VOICE_COMMUNICATION:
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
case USAGE_ALARM:
case USAGE_NOTIFICATION:
case USAGE_NOTIFICATION_RINGTONE:
case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
case USAGE_NOTIFICATION_EVENT:
case USAGE_ASSISTANCE_ACCESSIBILITY:
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
case USAGE_ASSISTANCE_SONIFICATION:
case USAGE_GAME:
case USAGE_VIRTUAL_SOURCE:
mUsage = usage;
break;
case USAGE_ASSISTANT:
if (!sForceLegacyBehavior && Build.VERSION.SDK_INT > 25) {
mUsage = usage;
} else {
mUsage = USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
}
break;
default:
mUsage = USAGE_UNKNOWN;
}
return this;
}
/**
* Sets the attribute describing the content type of the audio signal, such as speech, or
* music.
*
* @param contentType the content type values, one of {@link
* AudioAttributesCompat#CONTENT_TYPE_MOVIE}, {@link
* AudioAttributesCompat#CONTENT_TYPE_MUSIC}, {@link
* AudioAttributesCompat#CONTENT_TYPE_SONIFICATION}, {@link
* AudioAttributesCompat#CONTENT_TYPE_SPEECH}, {@link
* AudioAttributesCompat#CONTENT_TYPE_UNKNOWN}.
* @return the same Builder instance.
*/
public Builder setContentType(@AttributeContentType int contentType) {
switch (contentType) {
case CONTENT_TYPE_UNKNOWN:
case CONTENT_TYPE_MOVIE:
case CONTENT_TYPE_MUSIC:
case CONTENT_TYPE_SONIFICATION:
case CONTENT_TYPE_SPEECH:
mContentType = contentType;
break;
default:
mUsage = CONTENT_TYPE_UNKNOWN;
}
return this;
}
/**
* Sets the combination of flags.
*
* <p>This is a bitwise OR with the existing flags.
*
* @param flags a combination of {@link AudioAttributesCompat#FLAG_AUDIBILITY_ENFORCED},
* {@link AudioAttributesCompat#FLAG_HW_AV_SYNC}.
* @return the same Builder instance.
*/
public Builder setFlags(int flags) {
flags &= AudioAttributesCompat.FLAG_ALL;
mFlags |= flags;
return this;
}
/**
* Create an {@link AudioAttributesCompat} that best approximates the specified {@link
* AudioManager} stream type constant.
*
* @param streamType one of <code>AudioManager.STREAM_*</code>
* @return this same Builder
*/
public Builder setLegacyStreamType(int streamType) {
if (streamType == AudioManagerHidden.STREAM_ACCESSIBILITY) {
throw new IllegalArgumentException(
"STREAM_ACCESSIBILITY is not a legacy stream "
+ "type that was used for audio playback");
}
mLegacyStream = streamType;
mUsage = usageForStreamType(streamType);
return this;
}
}
@Override
public int hashCode() {
if (Build.VERSION.SDK_INT >= 21
&& !sForceLegacyBehavior
&& mAudioAttributesWrapper != null) {
return mAudioAttributesWrapper.unwrap().hashCode();
}
return Arrays.hashCode(new Object[] {mContentType, mFlags, mUsage, mLegacyStream});
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("AudioAttributesCompat:");
if (unwrap() != null) {
sb.append(" audioattributes=").append(unwrap());
} else {
if (mLegacyStream != null) {
sb.append(" stream=").append(mLegacyStream);
sb.append(" derived");
}
sb.append(" usage=")
.append(usageToString())
.append(" content=")
.append(mContentType)
.append(" flags=0x")
.append(Integer.toHexString(mFlags).toUpperCase());
}
return sb.toString();
}
String usageToString() {
return usageToString(mUsage);
}
static String usageToString(int usage) {
switch (usage) {
case USAGE_UNKNOWN:
return new String("USAGE_UNKNOWN");
case USAGE_MEDIA:
return new String("USAGE_MEDIA");
case USAGE_VOICE_COMMUNICATION:
return new String("USAGE_VOICE_COMMUNICATION");
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
return new String("USAGE_VOICE_COMMUNICATION_SIGNALLING");
case USAGE_ALARM:
return new String("USAGE_ALARM");
case USAGE_NOTIFICATION:
return new String("USAGE_NOTIFICATION");
case USAGE_NOTIFICATION_RINGTONE:
return new String("USAGE_NOTIFICATION_RINGTONE");
case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
return new String("USAGE_NOTIFICATION_COMMUNICATION_REQUEST");
case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
return new String("USAGE_NOTIFICATION_COMMUNICATION_INSTANT");
case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
return new String("USAGE_NOTIFICATION_COMMUNICATION_DELAYED");
case USAGE_NOTIFICATION_EVENT:
return new String("USAGE_NOTIFICATION_EVENT");
case USAGE_ASSISTANCE_ACCESSIBILITY:
return new String("USAGE_ASSISTANCE_ACCESSIBILITY");
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
return new String("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE");
case USAGE_ASSISTANCE_SONIFICATION:
return new String("USAGE_ASSISTANCE_SONIFICATION");
case USAGE_GAME:
return new String("USAGE_GAME");
case USAGE_ASSISTANT:
return new String("USAGE_ASSISTANT");
default:
return new String("unknown usage " + usage);
}
}
private abstract static class AudioManagerHidden {
public static final int STREAM_BLUETOOTH_SCO = 6;
public static final int STREAM_SYSTEM_ENFORCED = 7;
public static final int STREAM_TTS = 9;
public static final int STREAM_ACCESSIBILITY = 10;
}
private static int usageForStreamType(int streamType) {
switch (streamType) {
case AudioManager.STREAM_VOICE_CALL:
return USAGE_VOICE_COMMUNICATION;
case AudioManagerHidden.STREAM_SYSTEM_ENFORCED:
case AudioManager.STREAM_SYSTEM:
return USAGE_ASSISTANCE_SONIFICATION;
case AudioManager.STREAM_RING:
return USAGE_NOTIFICATION_RINGTONE;
case AudioManager.STREAM_MUSIC:
return USAGE_MEDIA;
case AudioManager.STREAM_ALARM:
return USAGE_ALARM;
case AudioManager.STREAM_NOTIFICATION:
return USAGE_NOTIFICATION;
case AudioManagerHidden.STREAM_BLUETOOTH_SCO:
return USAGE_VOICE_COMMUNICATION;
case AudioManager.STREAM_DTMF:
return USAGE_VOICE_COMMUNICATION_SIGNALLING;
case AudioManagerHidden.STREAM_ACCESSIBILITY:
return USAGE_ASSISTANCE_ACCESSIBILITY;
case AudioManagerHidden.STREAM_TTS:
default:
return USAGE_UNKNOWN;
}
}
/**
* Prevent AudioAttributes from being used even on platforms that support it.
*
* @hide For testing only.
*/
@RestrictTo(LIBRARY_GROUP)
public static void setForceLegacyBehavior(boolean force) {
sForceLegacyBehavior = force;
}
static int toVolumeStreamType(boolean fromGetVolumeControlStream, AudioAttributesCompat aa) {
return toVolumeStreamType(fromGetVolumeControlStream, aa.getFlags(), aa.getUsage());
}
static int toVolumeStreamType(
boolean fromGetVolumeControlStream, int flags, @AttributeUsage int usage) {
// flags to stream type mapping
if ((flags & FLAG_AUDIBILITY_ENFORCED) == FLAG_AUDIBILITY_ENFORCED) {
return fromGetVolumeControlStream
? AudioManager.STREAM_SYSTEM
: AudioManagerHidden.STREAM_SYSTEM_ENFORCED;
}
if ((flags & FLAG_SCO) == FLAG_SCO) {
return fromGetVolumeControlStream
? AudioManager.STREAM_VOICE_CALL
: AudioManagerHidden.STREAM_BLUETOOTH_SCO;
}
// usage to stream type mapping
switch (usage) {
case USAGE_MEDIA:
case USAGE_GAME:
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
case USAGE_ASSISTANT:
return AudioManager.STREAM_MUSIC;
case USAGE_ASSISTANCE_SONIFICATION:
return AudioManager.STREAM_SYSTEM;
case USAGE_VOICE_COMMUNICATION:
return AudioManager.STREAM_VOICE_CALL;
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
return fromGetVolumeControlStream
? AudioManager.STREAM_VOICE_CALL
: AudioManager.STREAM_DTMF;
case USAGE_ALARM:
return AudioManager.STREAM_ALARM;
case USAGE_NOTIFICATION_RINGTONE:
return AudioManager.STREAM_RING;
case USAGE_NOTIFICATION:
case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
case USAGE_NOTIFICATION_EVENT:
return AudioManager.STREAM_NOTIFICATION;
case USAGE_ASSISTANCE_ACCESSIBILITY:
return AudioManagerHidden.STREAM_ACCESSIBILITY;
case USAGE_UNKNOWN:
return fromGetVolumeControlStream
? AudioManager.USE_DEFAULT_STREAM_TYPE
: AudioManager.STREAM_MUSIC;
default:
if (fromGetVolumeControlStream) {
throw new IllegalArgumentException(
"Unknown usage value " + usage + " in audio attributes");
} else {
return AudioManager.STREAM_MUSIC;
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AudioAttributesCompat that = (AudioAttributesCompat) o;
if (Build.VERSION.SDK_INT >= 21
&& !sForceLegacyBehavior
&& mAudioAttributesWrapper != null) {
return mAudioAttributesWrapper.unwrap().equals(that.unwrap());
}
return ((mContentType == that.getContentType())
&& (mFlags == that.getFlags())
&& (mUsage == that.getUsage())
&& (mLegacyStream != null ? mLegacyStream.equals(that.mLegacyStream)
: that.mLegacyStream == null)); // query the slot directly, don't guess
}
/** @hide */
@IntDef({
USAGE_UNKNOWN,
USAGE_MEDIA,
USAGE_VOICE_COMMUNICATION,
USAGE_VOICE_COMMUNICATION_SIGNALLING,
USAGE_ALARM,
USAGE_NOTIFICATION,
USAGE_NOTIFICATION_RINGTONE,
USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
USAGE_NOTIFICATION_EVENT,
USAGE_ASSISTANCE_ACCESSIBILITY,
USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
USAGE_ASSISTANCE_SONIFICATION,
USAGE_GAME,
USAGE_ASSISTANT,
})
@RestrictTo(LIBRARY_GROUP)
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {
}
/** @hide */
@IntDef({
CONTENT_TYPE_UNKNOWN,
CONTENT_TYPE_SPEECH,
CONTENT_TYPE_MUSIC,
CONTENT_TYPE_MOVIE,
CONTENT_TYPE_SONIFICATION
})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(LIBRARY_GROUP)
public @interface AttributeContentType {
}
}