method public android.media.AudioAttributes getAudioAttributes();
method public int getClientPid();
method public int getClientUid();
+ method public int getPlayerInterfaceId();
method public int getPlayerState();
method public int getPlayerType();
method public void writeToParcel(android.os.Parcel, int);
/**
* @hide
+ * For test purposes only, will throw NPE with some methods that require a Context.
+ */
+ public AudioManager() {
+ mUseVolumeKeySounds = true;
+ mUseFixedVolume = false;
+ }
+
+ /**
+ * @hide
*/
public AudioManager(Context context) {
setContext(context);
import android.annotation.NonNull;
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;
public final class AudioPlaybackConfiguration implements Parcelable {
private final static String TAG = new String("AudioPlaybackConfiguration");
+ private final static boolean DEBUG = false;
+
/** @hide */
public final static int PLAYER_PIID_INVALID = -1;
/** @hide */
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 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); }
mPlayerIId = piid;
mPlayerType = pic.mPlayerType;
mClientUid = uid;
mClientPid = pid;
mPlayerState = PLAYER_STATE_IDLE;
mPlayerAttr = pic.mAttributes;
+ if ((sPlayerDeathMonitor != null) && (pic.mIPlayer != null)) {
+ mIPlayerShell = new IPlayerShell(this, pic.mIPlayer);
+ } else {
+ mIPlayerShell = null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void init() {
+ if (mIPlayerShell != null) {
+ mIPlayerShell.monitorDeath();
+ }
}
// Note that this method is called server side, so no "privileged" information is ever sent
anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
anonymCopy.mClientUid = PLAYER_UPID_INVALID;
anonymCopy.mClientPid = PLAYER_UPID_INVALID;
+ anonymCopy.mIPlayerShell = null;
return anonymCopy;
}
/**
* @hide
+ * Return an identifier unique for the lifetime of the player.
+ * @return a player interface identifier
+ */
+ @SystemApi
+ public int getPlayerInterfaceId() {
+ return mPlayerIId;
+ }
+
+ /**
+ * @hide
+ * FIXME return a player proxy instead, make systemApi
+ * @return
+ */
+ public IPlayer getPlayerProxy() {
+ return mIPlayerShell == null ? null : mIPlayerShell.getIPlayer();
+ }
+
+ /**
+ * @hide
* Handle a change of audio attributes
* @param attr
*/
public boolean handleStateEvent(int event) {
final boolean changed = (mPlayerState != event);
mPlayerState = event;
+ if ((event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) {
+ mIPlayerShell.release();
+ }
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
dest.writeInt(mClientPid);
dest.writeInt(mPlayerState);
mPlayerAttr.writeToParcel(dest, 0);
+ dest.writeStrongInterface(mIPlayerShell == null ? null : mIPlayerShell.getIPlayer());
}
private AudioPlaybackConfiguration(Parcel in) {
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);
}
@Override
}
//=====================================================================
+ // Inner class for corresponding IPlayer and its death monitoring
+ final static class IPlayerShell implements IBinder.DeathRecipient {
+
+ final AudioPlaybackConfiguration mMonitor; // never null
+ private IPlayer mIPlayer;
+
+ IPlayerShell(@NonNull AudioPlaybackConfiguration monitor, @NonNull IPlayer iplayer) {
+ mMonitor = monitor;
+ mIPlayer = iplayer;
+ }
+
+ void monitorDeath() {
+ 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"); }
+ }
+
+ void release() {
+ mIPlayer.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+
+ //=====================================================================
// Utilities
/** @hide */
} else {
mState = STATE_INITIALIZED;
}
+
+ baseRegisterPlayer();
}
/**
// other initialization...
if (nativeTrackInJavaObj != 0) {
+ baseRegisterPlayer();
deferred_connect(nativeTrackInJavaObj);
} else {
mState = STATE_UNINITIALIZED;
}
//---------------------------------------------------------
+ // Methods for IPlayer interface
+ //--------------------
+ @Override
+ void playerStart() {
+ play();
+ }
+
+ @Override
+ void playerPause() {
+ pause();
+ }
+
+ @Override
+ void playerStop() {
+ stop();
+ }
+
+ //---------------------------------------------------------
// Java methods called from the native side
//--------------------
@SuppressWarnings("unused")
oneway void start();
oneway void pause();
oneway void stop();
+ oneway void setVolume(float vol);
}
* It's easier to create it here than in C++.
*/
native_setup(new WeakReference<MediaPlayer>(this));
+
+ baseRegisterPlayer();
}
/*
private native void _pause() throws IllegalStateException;
+ @Override
+ void playerStart() {
+ start();
+ }
+
+ @Override
+ void playerPause() {
+ pause();
+ }
+
+ @Override
+ void playerStop() {
+ stop();
+ }
+
/**
* Set the low-level power management behavior for this MediaPlayer. This
* can be used when the MediaPlayer is not playing through a SurfaceHolder
protected float mAuxEffectSendLevel = 0.0f;
// for AppOps
- private final IAppOpsService mAppOps;
- private final IAppOpsCallback mAppOpsCallback;
+ private IAppOpsService mAppOps;
+ private IAppOpsCallback mAppOpsCallback;
private boolean mHasAppOpsPlayAudio = true;
private final Object mAppOpsLock = new Object();
private final int mImplType;
// uniquely identifies the Player Interface throughout the system (P I Id)
- private final int mPlayerIId;
+ private int mPlayerIId;
private int mState;
}
mAttributes = attr;
mImplType = implType;
+ };
+
+ /**
+ * Call from derived class when instantiation / initialization is successful
+ */
+ protected void baseRegisterPlayer() {
int newPiid = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
mHasAppOpsPlayAudio = false;
}
try {
- newPiid = getService().trackPlayer(new PlayerIdCard(mImplType, mAttributes));
+ if (mIPlayer == null) {
+ throw new IllegalStateException("Cannot register a player with a null mIPlayer");
+ }
+ newPiid = getService().trackPlayer(new PlayerIdCard(mImplType, mAttributes, mIPlayer));
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, player will not be tracked", e);
}
mPlayerIId = newPiid;
}
-
/**
* To be called whenever the audio attributes of the player change
* @param attr non-null audio attributes
*/
abstract void playerSetVolume(boolean muting, float leftVolume, float rightVolume);
abstract int playerSetAuxEffectSendLevel(boolean muting, float level);
+ abstract void playerStart();
+ abstract void playerPause();
+ abstract void playerStop();
//=====================================================================
- // Implementation of IPlayer
- private final IPlayer mIPlayer = new IPlayer.Stub() {
+ /**
+ * Implementation of IPlayer for all subclasses of PlayerBase
+ */
+ private IPlayer mIPlayer = new IPlayer.Stub() {
@Override
- public void start() {}
+ public void start() {
+ playerStart();
+ }
+
+ @Override
+ public void pause() {
+ playerPause();
+ }
+
@Override
- public void pause() {}
+ public void stop() {
+ playerStop();
+ }
+
@Override
- public void stop() {}
+ public void setVolume(float vol) {
+ baseSetVolume(vol, vol);
+ }
};
//=====================================================================
public final static int AUDIO_ATTRIBUTES_NONE = 0;
public final static int AUDIO_ATTRIBUTES_DEFINED = 1;
public final AudioAttributes mAttributes;
+ public final IPlayer mIPlayer;
- PlayerIdCard(int type, @NonNull AudioAttributes attr) {
+ PlayerIdCard(int type, @NonNull AudioAttributes attr, @NonNull IPlayer iplayer) {
mPlayerType = type;
mAttributes = attr;
+ mIPlayer = iplayer;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mPlayerType);
mAttributes.writeToParcel(dest, 0);
+ dest.writeStrongBinder(mIPlayer == null ? null : mIPlayer.asBinder());
}
public static final Parcelable.Creator<PlayerIdCard> CREATOR
private PlayerIdCard(Parcel in) {
mPlayerType = in.readInt();
mAttributes = AudioAttributes.CREATOR.createFromParcel(in);
+ // IPlayer can be null if unmarshalling a Parcel coming from who knows where
+ final IBinder b = in.readStrongBinder();
+ mIPlayer = (b == null ? null : IPlayer.Stub.asInterface(b));
}
@Override
}
mLock = new Object();
mAttributes = attributes;
+
+ baseRegisterPlayer();
}
/**
return AudioSystem.SUCCESS;
}
+ @Override
+ void playerStart() {
+ // FIXME implement resuming any paused sound
+ }
+
+ @Override
+ void playerPause() {
+ // FIXME implement pausing any playing sound
+ }
+
+ @Override
+ void playerStop() {
+ // FIXME implement pausing any playing sound
+ }
+
/**
* Similar, except set volume of all channels to same value.
* @hide
/**
* Class to receive and dispatch updates from AudioSystem about recording configurations.
*/
-public final class PlaybackActivityMonitor {
+public final class PlaybackActivityMonitor
+ implements AudioPlaybackConfiguration.PlayerDeathMonitor {
public final static String TAG = "AudioService.PlaybackActivityMonitor";
private final static boolean DEBUG = false;
new HashMap<Integer, AudioPlaybackConfiguration>();
PlaybackActivityMonitor() {
- PlayMonitorClient.sMonitor = this;
+ PlayMonitorClient.sListenerDeathMonitor = this;
+ AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
}
//=================================================================
final AudioPlaybackConfiguration apc =
new AudioPlaybackConfiguration(pic, newPiid,
Binder.getCallingUid(), Binder.getCallingPid());
+ apc.init();
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
}
}
}
+ // Implementation of AudioPlaybackConfiguration.PlayerDeathMonitor
+ @Override
+ public void playerDeath(int piid) {
+ releasePlayer(piid, 0);
+ }
+
protected void dump(PrintWriter pw) {
pw.println("\nPlaybackActivityMonitor dump time: "
+ DateFormat.getTimeInstance().format(new Date()));
private final static class PlayMonitorClient implements IBinder.DeathRecipient {
// can afford to be static because only one PlaybackActivityMonitor ever instantiated
- static PlaybackActivityMonitor sMonitor;
+ static PlaybackActivityMonitor sListenerDeathMonitor;
final IPlaybackConfigDispatcher mDispatcherCb;
final boolean mIsPrivileged;
public void binderDied() {
Log.w(TAG, "client died");
- sMonitor.unregisterPlaybackCallback(mDispatcherCb);
+ sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb);
}
boolean init() {