blob: d18d7e4238e2eaf339bfb4f6a4416f7314eab91c [file] [log] [blame]
/*
* Copyright (C) 2016 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.media;
import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* The AudioPlaybackConfiguration class collects the information describing an audio playback
* session.
*/
public final class AudioPlaybackConfiguration implements Parcelable {
private static final String TAG = new String("AudioPlaybackConfiguration");
private static final boolean DEBUG = false;
/** @hide */
public static final int PLAYER_PIID_INVALID = -1;
/** @hide */
public static final int PLAYER_UPID_INVALID = -1;
/** @hide */
public static final int PLAYER_DEVICEID_INVALID = 0;
// information about the implementation
/**
* @hide
* An unknown type of player
*/
@SystemApi
public static final int PLAYER_TYPE_UNKNOWN = -1;
/**
* @hide
* Player backed by a java android.media.AudioTrack player
*/
@SystemApi
public static final int PLAYER_TYPE_JAM_AUDIOTRACK = 1;
/**
* @hide
* Player backed by a java android.media.MediaPlayer player
*/
@SystemApi
public static final int PLAYER_TYPE_JAM_MEDIAPLAYER = 2;
/**
* @hide
* Player backed by a java android.media.SoundPool player
*/
@SystemApi
public static final int PLAYER_TYPE_JAM_SOUNDPOOL = 3;
/**
* @hide
* Player backed by a C OpenSL ES AudioPlayer player with a BufferQueue source
*/
@SystemApi
public static final int PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE = 11;
/**
* @hide
* Player backed by a C OpenSL ES AudioPlayer player with a URI or FD source
*/
@SystemApi
public static final int PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD = 12;
/**
* @hide
* Player backed an AAudio player.
*/
@SystemApi
public static final int PLAYER_TYPE_AAUDIO = 13;
/**
* @hide
* Player backed a hardware source, whose state is visible in the Android audio policy manager.
* Note this type is not in System API so it will not be returned in public API calls
*/
// TODO unhide for SystemApi, update getPlayerType()
public static final int PLAYER_TYPE_HW_SOURCE = 14;
/**
* @hide
* Player is a proxy for an audio player whose audio and state doesn't go through the Android
* audio framework.
* Note this type is not in System API so it will not be returned in public API calls
*/
// TODO unhide for SystemApi, update getPlayerType()
public static final int PLAYER_TYPE_EXTERNAL_PROXY = 15;
/** @hide */
@IntDef({
PLAYER_TYPE_UNKNOWN,
PLAYER_TYPE_JAM_AUDIOTRACK,
PLAYER_TYPE_JAM_MEDIAPLAYER,
PLAYER_TYPE_JAM_SOUNDPOOL,
PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE,
PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerType {}
/**
* @hide
* An unknown player state
*/
@SystemApi
public static final int PLAYER_STATE_UNKNOWN = -1;
/**
* @hide
* The resources of the player have been released, it cannot play anymore
*/
@SystemApi
public static final int PLAYER_STATE_RELEASED = 0;
/**
* @hide
* The state of a player when it's created
*/
@SystemApi
public static final int PLAYER_STATE_IDLE = 1;
/**
* @hide
* The state of a player that is actively playing
*/
@SystemApi
public static final int PLAYER_STATE_STARTED = 2;
/**
* @hide
* The state of a player where playback is paused
*/
@SystemApi
public static final int PLAYER_STATE_PAUSED = 3;
/**
* @hide
* The state of a player where playback is stopped
*/
@SystemApi
public static final int PLAYER_STATE_STOPPED = 4;
/**
* @hide
* The state used to update device id, does not actually change the state of the player
*/
public static final int PLAYER_UPDATE_DEVICE_ID = 5;
/** @hide */
@IntDef({
PLAYER_STATE_UNKNOWN,
PLAYER_STATE_RELEASED,
PLAYER_STATE_IDLE,
PLAYER_STATE_STARTED,
PLAYER_STATE_PAUSED,
PLAYER_STATE_STOPPED,
PLAYER_UPDATE_DEVICE_ID
})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerState {}
/** @hide */
public static String playerStateToString(@PlayerState int state) {
switch (state) {
case PLAYER_STATE_UNKNOWN: return "PLAYER_STATE_UNKNOWN";
case PLAYER_STATE_RELEASED: return "PLAYER_STATE_RELEASED";
case PLAYER_STATE_IDLE: return "PLAYER_STATE_IDLE";
case PLAYER_STATE_STARTED: return "PLAYER_STATE_STARTED";
case PLAYER_STATE_PAUSED: return "PLAYER_STATE_PAUSED";
case PLAYER_STATE_STOPPED: return "PLAYER_STATE_STOPPED";
case PLAYER_UPDATE_DEVICE_ID: return "PLAYER_UPDATE_DEVICE_ID";
default:
return "invalid state " + state;
}
}
// immutable data
private final int mPlayerIId;
// not final due to anonymization step
private int mPlayerType;
private int mClientUid;
private int mClientPid;
// the IPlayer reference and death monitor
private IPlayerShell mIPlayerShell;
private int mPlayerState;
private AudioAttributes mPlayerAttr; // never null
private int mDeviceId;
private int mSessionId;
/**
* Never use without initializing parameters afterwards
*/
private AudioPlaybackConfiguration(int piid) {
mPlayerIId = piid;
mIPlayerShell = null;
}
/**
* @hide
*/
public AudioPlaybackConfiguration(PlayerBase.PlayerIdCard pic, int piid, int uid, int pid) {
if (DEBUG) {
Log.d(TAG, "new: piid=" + piid + " iplayer=" + pic.mIPlayer
+ " sessionId=" + pic.mSessionId);
}
mPlayerIId = piid;
mPlayerType = pic.mPlayerType;
mClientUid = uid;
mClientPid = pid;
mDeviceId = PLAYER_DEVICEID_INVALID;
mPlayerState = PLAYER_STATE_IDLE;
mPlayerAttr = pic.mAttributes;
if ((sPlayerDeathMonitor != null) && (pic.mIPlayer != null)) {
mIPlayerShell = new IPlayerShell(this, pic.mIPlayer);
} else {
mIPlayerShell = null;
}
mSessionId = pic.mSessionId;
}
/**
* @hide
*/
public void init() {
synchronized (this) {
if (mIPlayerShell != null) {
mIPlayerShell.monitorDeath();
}
}
}
// Note that this method is called server side, so no "privileged" information is ever sent
// to a client that is not supposed to have access to it.
/**
* @hide
* Creates a copy of the playback configuration that is stripped of any data enabling
* identification of which application it is associated with ("anonymized").
* @param toSanitize
*/
public static AudioPlaybackConfiguration anonymizedCopy(AudioPlaybackConfiguration in) {
final AudioPlaybackConfiguration anonymCopy = new AudioPlaybackConfiguration(in.mPlayerIId);
anonymCopy.mPlayerState = in.mPlayerState;
// do not reuse the full attributes: only usage, content type and public flags are allowed
AudioAttributes.Builder builder = new AudioAttributes.Builder()
.setContentType(in.mPlayerAttr.getContentType())
.setFlags(in.mPlayerAttr.getFlags())
.setAllowedCapturePolicy(
in.mPlayerAttr.getAllowedCapturePolicy() == ALLOW_CAPTURE_BY_ALL
? ALLOW_CAPTURE_BY_ALL : ALLOW_CAPTURE_BY_NONE);
if (AudioAttributes.isSystemUsage(in.mPlayerAttr.getSystemUsage())) {
builder.setSystemUsage(in.mPlayerAttr.getSystemUsage());
} else {
builder.setUsage(in.mPlayerAttr.getUsage());
}
anonymCopy.mPlayerAttr = builder.build();
anonymCopy.mDeviceId = in.mDeviceId;
// anonymized data
anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
anonymCopy.mClientUid = PLAYER_UPID_INVALID;
anonymCopy.mClientPid = PLAYER_UPID_INVALID;
anonymCopy.mIPlayerShell = null;
anonymCopy.mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
return anonymCopy;
}
/**
* Return the {@link AudioAttributes} of the corresponding player.
* @return the audio attributes of the player
*/
public AudioAttributes getAudioAttributes() {
return mPlayerAttr;
}
/**
* @hide
* Return the uid of the client application that created this player.
* @return the uid of the client
*/
@SystemApi
public int getClientUid() {
return mClientUid;
}
/**
* @hide
* Return the pid of the client application that created this player.
* @return the pid of the client
*/
@SystemApi
public int getClientPid() {
return mClientPid;
}
/**
* Returns information about the {@link AudioDeviceInfo} used for this playback.
* @return the audio playback device or null if the device is not available at the time of query
*/
public @Nullable AudioDeviceInfo getAudioDeviceInfo() {
if (mDeviceId == PLAYER_DEVICEID_INVALID) {
return null;
}
return AudioManager.getDeviceForPortId(mDeviceId, AudioManager.GET_DEVICES_OUTPUTS);
}
/**
* @hide
* Return the audio session ID associated with this player.
* See {@link AudioManager#generateAudioSessionId()}.
* @return an audio session ID
*/
@SystemApi
public @IntRange(from = 0) int getSessionId() {
return mSessionId;
}
/**
* @hide
* Return the type of player linked to this configuration.
* <br>Note that player types not exposed in the system API will be represented as
* {@link #PLAYER_TYPE_UNKNOWN}.
* @return the type of the player.
*/
@SystemApi
public @PlayerType int getPlayerType() {
switch (mPlayerType) {
case PLAYER_TYPE_HW_SOURCE:
case PLAYER_TYPE_EXTERNAL_PROXY:
return PLAYER_TYPE_UNKNOWN;
default:
return mPlayerType;
}
}
/**
* @hide
* Return the current state of the player linked to this configuration. The return value is one
* of {@link #PLAYER_STATE_IDLE}, {@link #PLAYER_STATE_PAUSED}, {@link #PLAYER_STATE_STARTED},
* {@link #PLAYER_STATE_STOPPED}, {@link #PLAYER_STATE_RELEASED} or
* {@link #PLAYER_STATE_UNKNOWN}.
* @return the state of the player.
*/
@SystemApi
public @PlayerState int getPlayerState() {
return mPlayerState;
}
/**
* @hide
* Return an identifier unique for the lifetime of the player.
* @return a player interface identifier
*/
@SystemApi
public int getPlayerInterfaceId() {
return mPlayerIId;
}
/**
* @hide
* Return a proxy for the player associated with this playback configuration
* @return a proxy player
*/
@SystemApi
public PlayerProxy getPlayerProxy() {
final IPlayerShell ips;
synchronized (this) {
ips = mIPlayerShell;
}
return ips == null ? null : new PlayerProxy(this);
}
/**
* @hide
* @return the IPlayer interface for the associated player
*/
IPlayer getIPlayer() {
final IPlayerShell ips;
synchronized (this) {
ips = mIPlayerShell;
}
return ips == null ? null : ips.getIPlayer();
}
/**
* @hide
* Handle a change of audio attributes
* @param attr
*/
public boolean handleAudioAttributesEvent(@NonNull AudioAttributes attr) {
final boolean changed = !attr.equals(mPlayerAttr);
mPlayerAttr = attr;
return changed;
}
/**
* @hide
* Handle a change of audio session Id
* @param sessionId the audio session ID
*/
public boolean handleSessionIdEvent(int sessionId) {
final boolean changed = sessionId != mSessionId;
mSessionId = sessionId;
return changed;
}
/**
* @hide
* Handle a player state change
* @param event
* @param deviceId active device id or {@Code PLAYER_DEVICEID_INVALID}
* <br>Note device id is valid for {@code PLAYER_UPDATE_DEVICE_ID} or
* <br>{@code PLAYER_STATE_STARTED} events, as the device id will be reset to none when
* <br>pausing or stopping playback. It will be set to active device when playback starts or
* <br>it will be changed when PLAYER_UPDATE_DEVICE_ID is sent. The latter can happen if the
* <br>device changes in the middle of playback.
* @return true if the state changed, false otherwise
*/
public boolean handleStateEvent(int event, int deviceId) {
boolean changed = false;
synchronized (this) {
// Do not update if it is only device id update
if (event != PLAYER_UPDATE_DEVICE_ID) {
changed = (mPlayerState != event);
mPlayerState = event;
}
if (event == PLAYER_STATE_STARTED || event == PLAYER_UPDATE_DEVICE_ID) {
changed = changed || (mDeviceId != deviceId);
mDeviceId = deviceId;
}
if (changed && (event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) {
mIPlayerShell.release();
mIPlayerShell = null;
}
}
return changed;
}
// To report IPlayer death from death recipient
/** @hide */
public interface PlayerDeathMonitor {
public void playerDeath(int piid);
}
/** @hide */
public static PlayerDeathMonitor sPlayerDeathMonitor;
private void playerDied() {
if (sPlayerDeathMonitor != null) {
sPlayerDeathMonitor.playerDeath(mPlayerIId);
}
}
/**
* @hide
* Returns true if the player is considered "active", i.e. actively playing, and thus
* in a state that should make it considered for the list public (sanitized) active playback
* configurations
* @return true if active
*/
@SystemApi
public boolean isActive() {
switch (mPlayerState) {
case PLAYER_STATE_STARTED:
return true;
case PLAYER_STATE_UNKNOWN:
case PLAYER_STATE_RELEASED:
case PLAYER_STATE_IDLE:
case PLAYER_STATE_PAUSED:
case PLAYER_STATE_STOPPED:
default:
return false;
}
}
/**
* @hide
* For AudioService dump
* @param pw
*/
public void dump(PrintWriter pw) {
pw.println(" " + this);
}
public static final @android.annotation.NonNull Parcelable.Creator<AudioPlaybackConfiguration> CREATOR
= new Parcelable.Creator<AudioPlaybackConfiguration>() {
/**
* Rebuilds an AudioPlaybackConfiguration previously stored with writeToParcel().
* @param p Parcel object to read the AudioPlaybackConfiguration from
* @return a new AudioPlaybackConfiguration created from the data in the parcel
*/
public AudioPlaybackConfiguration createFromParcel(Parcel p) {
return new AudioPlaybackConfiguration(p);
}
public AudioPlaybackConfiguration[] newArray(int size) {
return new AudioPlaybackConfiguration[size];
}
};
@Override
public int hashCode() {
return Objects.hash(mPlayerIId, mDeviceId, mPlayerType, mClientUid, mClientPid,
mSessionId);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mPlayerIId);
dest.writeInt(mDeviceId);
dest.writeInt(mPlayerType);
dest.writeInt(mClientUid);
dest.writeInt(mClientPid);
dest.writeInt(mPlayerState);
mPlayerAttr.writeToParcel(dest, 0);
final IPlayerShell ips;
synchronized (this) {
ips = mIPlayerShell;
}
dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
dest.writeInt(mSessionId);
}
private AudioPlaybackConfiguration(Parcel in) {
mPlayerIId = in.readInt();
mDeviceId = in.readInt();
mPlayerType = in.readInt();
mClientUid = in.readInt();
mClientPid = in.readInt();
mPlayerState = in.readInt();
mPlayerAttr = AudioAttributes.CREATOR.createFromParcel(in);
final IPlayer p = IPlayer.Stub.asInterface(in.readStrongBinder());
mIPlayerShell = (p == null) ? null : new IPlayerShell(null, p);
mSessionId = in.readInt();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof AudioPlaybackConfiguration)) return false;
AudioPlaybackConfiguration that = (AudioPlaybackConfiguration) o;
return ((mPlayerIId == that.mPlayerIId)
&& (mDeviceId == that.mDeviceId)
&& (mPlayerType == that.mPlayerType)
&& (mClientUid == that.mClientUid)
&& (mClientPid == that.mClientPid))
&& (mSessionId == that.mSessionId);
}
@Override
public String toString() {
return "AudioPlaybackConfiguration piid:" + mPlayerIId
+ " deviceId:" + mDeviceId
+ " type:" + toLogFriendlyPlayerType(mPlayerType)
+ " u/pid:" + mClientUid + "/" + mClientPid
+ " state:" + toLogFriendlyPlayerState(mPlayerState)
+ " attr:" + mPlayerAttr
+ " sessionId:" + mSessionId;
}
//=====================================================================
// Inner class for corresponding IPlayer and its death monitoring
static final class IPlayerShell implements IBinder.DeathRecipient {
final AudioPlaybackConfiguration mMonitor; // never null
private volatile IPlayer mIPlayer;
IPlayerShell(@NonNull AudioPlaybackConfiguration monitor, @NonNull IPlayer iplayer) {
mMonitor = monitor;
mIPlayer = iplayer;
}
synchronized void monitorDeath() {
if (mIPlayer == null) {
return;
}
try {
mIPlayer.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
if (mMonitor != null) {
Log.w(TAG, "Could not link to client death for piid=" + mMonitor.mPlayerIId, e);
} else {
Log.w(TAG, "Could not link to client death", e);
}
}
}
IPlayer getIPlayer() {
return mIPlayer;
}
public void binderDied() {
if (mMonitor != null) {
if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied for piid=" + mMonitor.mPlayerIId);}
mMonitor.playerDied();
} else if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied"); }
}
synchronized void release() {
if (mIPlayer == null) {
return;
}
mIPlayer.asBinder().unlinkToDeath(this, 0);
mIPlayer = null;
Binder.flushPendingCommands();
}
}
//=====================================================================
// Utilities
/** @hide */
public static String toLogFriendlyPlayerType(int type) {
switch (type) {
case PLAYER_TYPE_UNKNOWN: return "unknown";
case PLAYER_TYPE_JAM_AUDIOTRACK: return "android.media.AudioTrack";
case PLAYER_TYPE_JAM_MEDIAPLAYER: return "android.media.MediaPlayer";
case PLAYER_TYPE_JAM_SOUNDPOOL: return "android.media.SoundPool";
case PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE:
return "OpenSL ES AudioPlayer (Buffer Queue)";
case PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD:
return "OpenSL ES AudioPlayer (URI/FD)";
case PLAYER_TYPE_AAUDIO: return "AAudio";
case PLAYER_TYPE_HW_SOURCE: return "hardware source";
case PLAYER_TYPE_EXTERNAL_PROXY: return "external proxy";
default:
return "unknown player type " + type + " - FIXME";
}
}
/** @hide */
public static String toLogFriendlyPlayerState(int state) {
switch (state) {
case PLAYER_STATE_UNKNOWN: return "unknown";
case PLAYER_STATE_RELEASED: return "released";
case PLAYER_STATE_IDLE: return "idle";
case PLAYER_STATE_STARTED: return "started";
case PLAYER_STATE_PAUSED: return "paused";
case PLAYER_STATE_STOPPED: return "stopped";
case PLAYER_UPDATE_DEVICE_ID: return "device";
default:
return "unknown player state - FIXME";
}
}
}