Merge "AudioPlaybackConfiguration has a player control interface"
diff --git a/api/system-current.txt b/api/system-current.txt
index d8c981d..24f9aad 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -21848,6 +21848,7 @@
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);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 1d124c5..7c60385 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -633,6 +633,15 @@
/**
* @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);
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 147c5df..b38a07f 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -20,8 +20,10 @@
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;
@@ -36,6 +38,8 @@
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 */
@@ -147,6 +151,8 @@
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
@@ -156,18 +162,34 @@
*/
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
@@ -191,6 +213,7 @@
anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
anonymCopy.mClientUid = PLAYER_UPID_INVALID;
anonymCopy.mClientPid = PLAYER_UPID_INVALID;
+ anonymCopy.mIPlayerShell = null;
return anonymCopy;
}
@@ -250,6 +273,25 @@
/**
* @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
*/
@@ -268,9 +310,26 @@
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
@@ -338,6 +397,7 @@
dest.writeInt(mClientPid);
dest.writeInt(mPlayerState);
mPlayerAttr.writeToParcel(dest, 0);
+ dest.writeStrongInterface(mIPlayerShell == null ? null : mIPlayerShell.getIPlayer());
}
private AudioPlaybackConfiguration(Parcel in) {
@@ -347,6 +407,8 @@
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
@@ -363,6 +425,46 @@
}
//=====================================================================
+ // 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 */
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 464cbdb..031ac06 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -537,6 +537,8 @@
} else {
mState = STATE_INITIALIZED;
}
+
+ baseRegisterPlayer();
}
/**
@@ -566,6 +568,7 @@
// other initialization...
if (nativeTrackInJavaObj != 0) {
+ baseRegisterPlayer();
deferred_connect(nativeTrackInJavaObj);
} else {
mState = STATE_UNINITIALIZED;
@@ -2739,6 +2742,24 @@
}
//---------------------------------------------------------
+ // 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")
diff --git a/media/java/android/media/IPlayer.aidl b/media/java/android/media/IPlayer.aidl
index 32984f9..ccb60f7 100644
--- a/media/java/android/media/IPlayer.aidl
+++ b/media/java/android/media/IPlayer.aidl
@@ -24,4 +24,5 @@
oneway void start();
oneway void pause();
oneway void stop();
+ oneway void setVolume(float vol);
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 500556a..e3a0f25 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -645,6 +645,8 @@
* It's easier to create it here than in C++.
*/
native_setup(new WeakReference<MediaPlayer>(this));
+
+ baseRegisterPlayer();
}
/*
@@ -1261,6 +1263,21 @@
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
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 4eacb38..9819395 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -56,14 +56,14 @@
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;
@@ -78,6 +78,12 @@
}
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);
@@ -100,14 +106,16 @@
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
@@ -295,16 +303,34 @@
*/
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() {}
+ public void pause() {
+ playerPause();
+ }
+
@Override
- public void stop() {}
+ public void stop() {
+ playerStop();
+ }
+
+ @Override
+ public void setVolume(float vol) {
+ baseSetVolume(vol, vol);
+ }
};
//=====================================================================
@@ -317,10 +343,12 @@
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
@@ -337,6 +365,7 @@
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
@@ -357,6 +386,9 @@
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
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index c985cbd..4cc1f8e 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -159,6 +159,8 @@
}
mLock = new Object();
mAttributes = attributes;
+
+ baseRegisterPlayer();
}
/**
@@ -399,6 +401,21 @@
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
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index d6b4bee..c6b2cf6 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -41,7 +41,8 @@
/**
* 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;
@@ -57,7 +58,8 @@
new HashMap<Integer, AudioPlaybackConfiguration>();
PlaybackActivityMonitor() {
- PlayMonitorClient.sMonitor = this;
+ PlayMonitorClient.sListenerDeathMonitor = this;
+ AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
}
//=================================================================
@@ -72,6 +74,7 @@
final AudioPlaybackConfiguration apc =
new AudioPlaybackConfiguration(pic, newPiid,
Binder.getCallingUid(), Binder.getCallingPid());
+ apc.init();
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
}
@@ -124,6 +127,12 @@
}
}
+ // 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()));
@@ -275,7 +284,7 @@
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;
@@ -291,7 +300,7 @@
public void binderDied() {
Log.w(TAG, "client died");
- sMonitor.unregisterPlaybackCallback(mDispatcherCb);
+ sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb);
}
boolean init() {