| /* |
| * 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.RequiresPermission; |
| 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 com.android.internal.annotations.GuardedBy; |
| |
| 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 |
| * The state used to update port id, does not actually change the state of the player |
| */ |
| public static final int PLAYER_UPDATE_PORT_ID = 6; |
| /** |
| * @hide |
| * Used to update the mute state of a player through its port id |
| */ |
| public static final int PLAYER_UPDATE_MUTED = 7; |
| /** |
| * @hide |
| * Used to update the spatialization status and format of a player through its port id |
| */ |
| public static final int PLAYER_UPDATE_FORMAT = 8; |
| |
| /** @hide */ |
| @IntDef({ |
| PLAYER_STATE_UNKNOWN, |
| PLAYER_STATE_RELEASED, |
| PLAYER_STATE_IDLE, |
| PLAYER_STATE_STARTED, |
| PLAYER_STATE_PAUSED, |
| PLAYER_STATE_STOPPED, |
| PLAYER_UPDATE_DEVICE_ID, |
| PLAYER_UPDATE_PORT_ID, |
| PLAYER_UPDATE_MUTED, |
| PLAYER_UPDATE_FORMAT, |
| }) |
| @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"; |
| case PLAYER_UPDATE_PORT_ID: return "PLAYER_UPDATE_PORT_ID"; |
| case PLAYER_UPDATE_MUTED: return "PLAYER_UPDATE_MUTED"; |
| case PLAYER_UPDATE_FORMAT: return "PLAYER_UPDATE_FORMAT"; |
| default: |
| return "invalid state " + state; |
| } |
| } |
| |
| /** |
| * @hide |
| * Used to update the spatialization status of a player through its port ID. Must be kept in |
| * sync with frameworks/native/include/audiomanager/AudioManager.h |
| */ |
| public static final String EXTRA_PLAYER_EVENT_SPATIALIZED = |
| "android.media.extra.PLAYER_EVENT_SPATIALIZED"; |
| /** |
| * @hide |
| * Used to update the sample rate of a player through its port ID. Must be kept in sync with |
| * frameworks/native/include/audiomanager/AudioManager.h |
| */ |
| public static final String EXTRA_PLAYER_EVENT_SAMPLE_RATE = |
| "android.media.extra.PLAYER_EVENT_SAMPLE_RATE"; |
| /** |
| * @hide |
| * Used to update the channel mask of a player through its port ID. Must be kept in sync with |
| * frameworks/native/include/audiomanager/AudioManager.h |
| */ |
| public static final String EXTRA_PLAYER_EVENT_CHANNEL_MASK = |
| "android.media.extra.PLAYER_EVENT_CHANNEL_MASK"; |
| /** |
| * @hide |
| * Used to update the mute state of a player through its port ID. Must be kept in sync with |
| * frameworks/native/include/audiomanager/AudioManager.h |
| */ |
| public static final String EXTRA_PLAYER_EVENT_MUTE = |
| "android.media.extra.PLAYER_EVENT_MUTE"; |
| |
| /** |
| * @hide |
| * Flag used when muted by master volume. |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) |
| public static final int MUTED_BY_MASTER = (1 << 0); |
| /** |
| * @hide |
| * Flag used when muted by stream volume. |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) |
| public static final int MUTED_BY_STREAM_VOLUME = (1 << 1); |
| /** |
| * @hide |
| * Flag used when muted by stream mute. |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) |
| public static final int MUTED_BY_STREAM_MUTED = (1 << 2); |
| /** |
| * @hide |
| * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO. |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) |
| public static final int MUTED_BY_APP_OPS = (1 << 3); |
| /** |
| * @hide |
| * Flag used when muted by client volume. |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) |
| public static final int MUTED_BY_CLIENT_VOLUME = (1 << 4); |
| /** |
| * @hide |
| * Flag used when muted by volume shaper. |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) |
| public static final int MUTED_BY_VOLUME_SHAPER = (1 << 5); |
| |
| /** @hide */ |
| @IntDef( |
| flag = true, |
| value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED, |
| MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface PlayerMuteEvent { |
| } |
| |
| // 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 |
| |
| // lock for updateable properties |
| private final Object mUpdateablePropLock = new Object(); |
| |
| @GuardedBy("mUpdateablePropLock") |
| private int mDeviceId; |
| @GuardedBy("mUpdateablePropLock") |
| private int mSessionId; |
| @GuardedBy("mUpdateablePropLock") |
| private @NonNull FormatInfo mFormatInfo; |
| @GuardedBy("mUpdateablePropLock") |
| @PlayerMuteEvent private int mMutedState; |
| |
| /** |
| * 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; |
| mMutedState = 0; |
| 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; |
| mFormatInfo = FormatInfo.DEFAULT; |
| } |
| |
| /** |
| * @hide |
| */ |
| public void init() { |
| synchronized (this) { |
| if (mIPlayerShell != null) { |
| mIPlayerShell.monitorDeath(); |
| } |
| } |
| } |
| |
| // sets the fields that are updateable and require synchronization |
| private void setUpdateableFields(int deviceId, int sessionId, int mutedState, FormatInfo format) |
| { |
| synchronized (mUpdateablePropLock) { |
| mDeviceId = deviceId; |
| mSessionId = sessionId; |
| mMutedState = mutedState; |
| mFormatInfo = format; |
| } |
| } |
| // 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 in the instance to copy from |
| */ |
| 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(); |
| // anonymized data |
| anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN; |
| anonymCopy.mClientUid = PLAYER_UPID_INVALID; |
| anonymCopy.mClientPid = PLAYER_UPID_INVALID; |
| anonymCopy.mIPlayerShell = null; |
| anonymCopy.setUpdateableFields( |
| /*deviceId*/ PLAYER_DEVICEID_INVALID, |
| /*sessionId*/ AudioSystem.AUDIO_SESSION_ALLOCATE, |
| /*mutedState*/ 0, |
| FormatInfo.DEFAULT); |
| 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() { |
| final int deviceId; |
| synchronized (mUpdateablePropLock) { |
| deviceId = mDeviceId; |
| } |
| if (deviceId == PLAYER_DEVICEID_INVALID) { |
| return null; |
| } |
| return AudioManager.getDeviceForPortId(deviceId, 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() { |
| synchronized (mUpdateablePropLock) { |
| return mSessionId; |
| } |
| } |
| |
| /** |
| * @hide |
| * Used for determining if the current player is muted. |
| * <br>Note that if this result is true then {@link #getMutedBy} will be > 0. |
| * @return {@code true} if the player associated with this configuration has been muted (by any |
| * given MUTED_BY_* source event) or {@code false} otherwise. |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) |
| public boolean isMuted() { |
| synchronized (mUpdateablePropLock) { |
| return mMutedState != 0; |
| } |
| } |
| |
| /** |
| * @hide |
| * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags. |
| * <br>A value of 0 corresponds to an unmuted player. |
| * @return the mute state. |
| */ |
| @SystemApi |
| @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) |
| @PlayerMuteEvent public int getMutedBy() { |
| synchronized (mUpdateablePropLock) { |
| return mMutedState; |
| } |
| } |
| |
| /** |
| * @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 whether this player's output is spatialized |
| * @return true if spatialized, false if not or playback hasn't started |
| */ |
| @SystemApi |
| public boolean isSpatialized() { |
| synchronized (mUpdateablePropLock) { |
| return mFormatInfo.mIsSpatialized; |
| } |
| } |
| |
| /** |
| * @hide |
| * Return the sample rate in Hz of the content being played. |
| * @return the sample rate in Hertz, or 0 if playback hasn't started |
| */ |
| @SystemApi |
| public @IntRange(from = 0) int getSampleRate() { |
| synchronized (mUpdateablePropLock) { |
| return mFormatInfo.mSampleRate; |
| } |
| } |
| |
| /** |
| * @hide |
| * Return the player's channel mask |
| * @return the channel mask, or 0 if playback hasn't started. See {@link AudioFormat} and |
| * the definitions for the <code>CHANNEL_OUT_*</code> values used for the mask's bitfield |
| */ |
| @SystemApi |
| public int getChannelMask() { |
| synchronized (mUpdateablePropLock) { |
| return (AudioFormat.convertNativeChannelMaskToOutMask(mFormatInfo.mNativeChannelMask)); |
| } |
| } |
| |
| /** |
| * @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) { |
| synchronized (mUpdateablePropLock) { |
| final boolean changed = sessionId != mSessionId; |
| mSessionId = sessionId; |
| return changed; |
| } |
| } |
| |
| /** |
| * @hide |
| * Handle a change of the muted state |
| * @param mutedState the mute reason as a combination of {@link PlayerMuteEvent} flags |
| * @return true if the state changed, false otherwise |
| */ |
| public boolean handleMutedEvent(@PlayerMuteEvent int mutedState) { |
| synchronized (mUpdateablePropLock) { |
| final boolean changed = mMutedState != mutedState; |
| mMutedState = mutedState; |
| return changed; |
| } |
| } |
| |
| /** |
| * @hide |
| * Handle a change of playback format |
| * @param fi the latest format information |
| * @return true if the format changed, false otherwise |
| */ |
| public boolean handleFormatEvent(@NonNull FormatInfo fi) { |
| synchronized (mUpdateablePropLock) { |
| final boolean changed = !mFormatInfo.equals(fi); |
| mFormatInfo = fi; |
| 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 (mUpdateablePropLock) { |
| |
| // 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); |
| } |
| } |
| |
| private boolean isMuteAffectingActiveState() { |
| return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0 |
| || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0 |
| || (mMutedState & MUTED_BY_APP_OPS) != 0; |
| } |
| |
| /** |
| * @hide |
| * Returns true if the player is considered "active", i.e. actively playing with unmuted |
| * volume, 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 !isMuteAffectingActiveState(); |
| 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() { |
| synchronized (mUpdateablePropLock) { |
| return Objects.hash(mPlayerIId, mDeviceId, mMutedState, mPlayerType, mClientUid, |
| mClientPid, mSessionId); |
| } |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| synchronized (mUpdateablePropLock) { |
| dest.writeInt(mPlayerIId); |
| dest.writeInt(mDeviceId); |
| dest.writeInt(mMutedState); |
| 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); |
| mFormatInfo.writeToParcel(dest, 0); |
| } |
| } |
| |
| private AudioPlaybackConfiguration(Parcel in) { |
| mPlayerIId = in.readInt(); |
| mDeviceId = in.readInt(); |
| mMutedState = 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(); |
| mFormatInfo = FormatInfo.CREATOR.createFromParcel(in); |
| } |
| |
| @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) |
| && (mMutedState == that.mMutedState) |
| && (mPlayerType == that.mPlayerType) |
| && (mClientUid == that.mClientUid) |
| && (mClientPid == that.mClientPid)) |
| && (mSessionId == that.mSessionId); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder apcToString = new StringBuilder(); |
| synchronized (mUpdateablePropLock) { |
| apcToString.append("AudioPlaybackConfiguration piid:").append(mPlayerIId).append( |
| " deviceId:").append(mDeviceId).append(" type:").append( |
| toLogFriendlyPlayerType(mPlayerType)).append(" u/pid:").append( |
| mClientUid).append( |
| "/").append(mClientPid).append(" state:").append( |
| toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append( |
| mPlayerAttr).append( |
| " sessionId:").append(mSessionId).append(" mutedState:"); |
| if (mMutedState == 0) { |
| apcToString.append("none "); |
| } else { |
| if ((mMutedState & MUTED_BY_MASTER) != 0) { |
| apcToString.append("master "); |
| } |
| if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) { |
| apcToString.append("streamVolume "); |
| } |
| if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) { |
| apcToString.append("streamMute "); |
| } |
| if ((mMutedState & MUTED_BY_APP_OPS) != 0) { |
| apcToString.append("appOps "); |
| } |
| if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) { |
| apcToString.append("clientVolume "); |
| } |
| if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) { |
| apcToString.append("volumeShaper "); |
| } |
| } |
| apcToString.append(" ").append(mFormatInfo); |
| } |
| return apcToString.toString(); |
| } |
| |
| //===================================================================== |
| // 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(); |
| } |
| } |
| |
| //===================================================================== |
| |
| /** |
| * @hide |
| * Class to store sample rate, channel mask, and spatialization status |
| */ |
| public static final class FormatInfo implements Parcelable { |
| static final FormatInfo DEFAULT = new FormatInfo( |
| /*spatialized*/ false, /*channel mask*/ 0, /*sample rate*/ 0); |
| final boolean mIsSpatialized; |
| final int mNativeChannelMask; |
| final int mSampleRate; |
| |
| public FormatInfo(boolean isSpatialized, int nativeChannelMask, int sampleRate) { |
| mIsSpatialized = isSpatialized; |
| mNativeChannelMask = nativeChannelMask; |
| mSampleRate = sampleRate; |
| } |
| |
| @Override |
| public String toString() { |
| return "FormatInfo{" |
| + "isSpatialized=" + mIsSpatialized |
| + ", channelMask=0x" + Integer.toHexString(mNativeChannelMask) |
| + ", sampleRate=" + mSampleRate |
| + '}'; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof FormatInfo)) return false; |
| FormatInfo that = (FormatInfo) o; |
| return mIsSpatialized == that.mIsSpatialized |
| && mNativeChannelMask == that.mNativeChannelMask |
| && mSampleRate == that.mSampleRate; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mIsSpatialized, mNativeChannelMask, mSampleRate); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) { |
| dest.writeBoolean(mIsSpatialized); |
| dest.writeInt(mNativeChannelMask); |
| dest.writeInt(mSampleRate); |
| } |
| |
| private FormatInfo(Parcel in) { |
| this( |
| in.readBoolean(), // isSpatialized |
| in.readInt(), // channelMask |
| in.readInt() // sampleRate |
| ); |
| } |
| |
| public static final @NonNull Parcelable.Creator<FormatInfo> CREATOR = |
| new Parcelable.Creator<FormatInfo>() { |
| public FormatInfo createFromParcel(Parcel p) { |
| return new FormatInfo(p); |
| } |
| public FormatInfo[] newArray(int size) { |
| return new FormatInfo[size]; |
| } |
| }; |
| } |
| //===================================================================== |
| // 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 updated"; |
| case PLAYER_UPDATE_PORT_ID: return "port updated"; |
| case PLAYER_UPDATE_MUTED: return "muted updated"; |
| default: |
| return "unknown player state - FIXME"; |
| } |
| } |
| } |