OSDN Git Service

AudioPlaybackConfiguration has a player control interface
authorJean-Michel Trivi <jmtrivi@google.com>
Fri, 6 Jan 2017 02:06:03 +0000 (18:06 -0800)
committerJean-Michel Trivi <jmtrivi@google.com>
Fri, 13 Jan 2017 23:49:29 +0000 (23:49 +0000)
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
media/java/android/media/AudioManager.java
media/java/android/media/AudioPlaybackConfiguration.java
media/java/android/media/AudioTrack.java
media/java/android/media/IPlayer.aidl
media/java/android/media/MediaPlayer.java
media/java/android/media/PlayerBase.java
media/java/android/media/SoundPool.java
services/core/java/com/android/server/audio/PlaybackActivityMonitor.java

index 7a0f24c..c903cac 100644 (file)
@@ -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);
index 1d124c5..7c60385 100644 (file)
@@ -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);
index 147c5df..b38a07f 100644 (file)
@@ -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 */
index 464cbdb..031ac06 100644 (file)
@@ -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")
index 32984f9..ccb60f7 100644 (file)
@@ -24,4 +24,5 @@ interface IPlayer {
     oneway void start();
     oneway void pause();
     oneway void stop();
+    oneway void setVolume(float vol);
 }
index 500556a..e3a0f25 100644 (file)
@@ -645,6 +645,8 @@ public class MediaPlayer extends PlayerBase
          * It's easier to create it here than in C++.
          */
         native_setup(new WeakReference<MediaPlayer>(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
index 4eacb38..9819395 100644 (file)
@@ -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<PlayerIdCard> 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
index c985cbd..4cc1f8e 100644 (file)
@@ -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
index d6b4bee..c6b2cf6 100644 (file)
@@ -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<Integer, AudioPlaybackConfiguration>();
 
     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() {