From 9dc22c227cb5c01136a6aa1b52c7dfa3383c0bd7 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Thu, 5 Jan 2017 18:06:03 -0800 Subject: [PATCH] AudioPlaybackConfiguration has a player control interface An AudioPlaybackConfiguration contains an IPlayer interface for system control of a player. It is not exposed to non-system signature components. AudioService, through PlaybackActivityMonitor, is monitoring the death of the IPlayer so the matching player can get unregistered in case it meets its maker. Test: use vendor/google_toolbox/team/audio/cmds/ClPlaybackActivity Bug: 30258418 Change-Id: Ibf3bceba91882ff16bffbf1219c55a1f89ccb13f --- api/system-current.txt | 1 + media/java/android/media/AudioManager.java | 9 ++ .../android/media/AudioPlaybackConfiguration.java | 102 +++++++++++++++++++++ media/java/android/media/AudioTrack.java | 21 +++++ media/java/android/media/IPlayer.aidl | 1 + media/java/android/media/MediaPlayer.java | 17 ++++ media/java/android/media/PlayerBase.java | 54 ++++++++--- media/java/android/media/SoundPool.java | 17 ++++ .../server/audio/PlaybackActivityMonitor.java | 17 +++- 9 files changed, 224 insertions(+), 15 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 7a0f24c3c9f9..c903cac8174d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -21783,6 +21783,7 @@ package android.media { 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 1d124c5e7f91..7c603853a3ba 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -633,6 +633,15 @@ public class AudioManager { /** * @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 147c5dffca48..b38a07f15511 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -20,8 +20,10 @@ import android.annotation.IntDef; 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 @@ import java.util.Objects; 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 @@ public final class AudioPlaybackConfiguration implements Parcelable { 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 @@ public final class AudioPlaybackConfiguration implements Parcelable { */ 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 @@ public final class AudioPlaybackConfiguration implements Parcelable { anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN; anonymCopy.mClientUid = PLAYER_UPID_INVALID; anonymCopy.mClientPid = PLAYER_UPID_INVALID; + anonymCopy.mIPlayerShell = null; return anonymCopy; } @@ -250,6 +273,25 @@ public final class AudioPlaybackConfiguration implements Parcelable { /** * @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 final class AudioPlaybackConfiguration implements Parcelable { 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 @@ public final class AudioPlaybackConfiguration implements Parcelable { 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 @@ public final class AudioPlaybackConfiguration implements Parcelable { 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 @@ public final class AudioPlaybackConfiguration implements Parcelable { } //===================================================================== + // 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 464cbdb985a4..031ac0667c64 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -537,6 +537,8 @@ public class AudioTrack extends PlayerBase } else { mState = STATE_INITIALIZED; } + + baseRegisterPlayer(); } /** @@ -566,6 +568,7 @@ public class AudioTrack extends PlayerBase // other initialization... if (nativeTrackInJavaObj != 0) { + baseRegisterPlayer(); deferred_connect(nativeTrackInJavaObj); } else { mState = STATE_UNINITIALIZED; @@ -2739,6 +2742,24 @@ public class AudioTrack extends PlayerBase } //--------------------------------------------------------- + // 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 32984f98264c..ccb60f7fd843 100644 --- a/media/java/android/media/IPlayer.aidl +++ b/media/java/android/media/IPlayer.aidl @@ -24,4 +24,5 @@ interface IPlayer { 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 500556aa7256..e3a0f25d5ccd 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -645,6 +645,8 @@ public class MediaPlayer extends PlayerBase * It's easier to create it here than in C++. */ native_setup(new WeakReference(this)); + + baseRegisterPlayer(); } /* @@ -1261,6 +1263,21 @@ public class MediaPlayer extends PlayerBase 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 4eacb3880a94..981939583e3b 100644 --- a/media/java/android/media/PlayerBase.java +++ b/media/java/android/media/PlayerBase.java @@ -56,14 +56,14 @@ public abstract class PlayerBase { 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 @@ public abstract class PlayerBase { } 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 @@ public abstract class PlayerBase { 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 @@ public abstract class PlayerBase { */ 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); + } }; //===================================================================== @@ -317,10 +343,12 @@ public abstract class PlayerBase { 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 abstract class PlayerBase { 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 CREATOR @@ -357,6 +386,9 @@ public abstract class PlayerBase { 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 c985cbd010d4..4cc1f8ebe4b4 100644 --- a/media/java/android/media/SoundPool.java +++ b/media/java/android/media/SoundPool.java @@ -159,6 +159,8 @@ public class SoundPool extends PlayerBase { } mLock = new Object(); mAttributes = attributes; + + baseRegisterPlayer(); } /** @@ -399,6 +401,21 @@ public class SoundPool extends PlayerBase { 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 d6b4bee102aa..c6b2cf64c30c 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 @@ import java.util.List; /** * 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 @@ public final class PlaybackActivityMonitor { new HashMap(); PlaybackActivityMonitor() { - PlayMonitorClient.sMonitor = this; + PlayMonitorClient.sListenerDeathMonitor = this; + AudioPlaybackConfiguration.sPlayerDeathMonitor = this; } //================================================================= @@ -72,6 +74,7 @@ public final class PlaybackActivityMonitor { final AudioPlaybackConfiguration apc = new AudioPlaybackConfiguration(pic, newPiid, Binder.getCallingUid(), Binder.getCallingPid()); + apc.init(); synchronized(mPlayerLock) { mPlayers.put(newPiid, apc); } @@ -124,6 +127,12 @@ public final class PlaybackActivityMonitor { } } + // 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 @@ public final class PlaybackActivityMonitor { 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 final class PlaybackActivityMonitor { public void binderDied() { Log.w(TAG, "client died"); - sMonitor.unregisterPlaybackCallback(mDispatcherCb); + sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb); } boolean init() { -- 2.11.0