Notification of playback activity

AudioService keeps track of status of implementations of PlayerBase.
AudioService's PlaybackActivityMonitor maintains a list of
  playback configurations for each PlayerBase, and a list
  of clients that want to receive updates about the playback.
Playback activity clients can query the playback configuration
  of the system through AudioManager, or register a callback
  for updates. For clients with MODIFY_AUDIO_ROUTING permission
  (system), the playback configurations contain more information
  about each player (player type, uid, pid, state), and can see
  all players, not just the "active" ones. The act of stripping
  off data about the players that is not supposed to be seen
  by non-system clients, is referred to as "anonymization". It
  is implemented in system server, so no system data is ever
  sent to playback activity clients without system permission.
More information about the AudioPlaybackConfiguration is
  available in the SystemApi (uid, pid, player type, player state).

Test: run cts -m CtsMediaTestCases -t android.media.cts.AudioPlaybackConfigurationTest
Bug: 30955183

Change-Id: I85997594c0378216419f5f0fdaa0714996fd3573
diff --git a/Android.mk b/Android.mk
index 88be12f..427b888 100644
--- a/Android.mk
+++ b/Android.mk
@@ -402,6 +402,8 @@
 	media/java/android/media/IMediaRouterService.aidl \
 	media/java/android/media/IMediaScannerListener.aidl \
 	media/java/android/media/IMediaScannerService.aidl \
+	media/java/android/media/IPlaybackConfigDispatcher.aidl \
+	media/java/android/media/IPlayer.aidl \
 	media/java/android/media/IRecordingConfigDispatcher.aidl \
 	media/java/android/media/IRemoteDisplayCallback.aidl \
 	media/java/android/media/IRemoteDisplayProvider.aidl \
diff --git a/api/current.txt b/api/current.txt
index f27cf03..4351cba 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20084,6 +20084,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
     method public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
@@ -20107,6 +20108,7 @@
     method public void playSoundEffect(int);
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+    method public void registerAudioPlaybackCallback(android.media.AudioManager.AudioPlaybackCallback, android.os.Handler);
     method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
@@ -20131,6 +20133,7 @@
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+    method public void unregisterAudioPlaybackCallback(android.media.AudioManager.AudioPlaybackCallback);
     method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
@@ -20229,6 +20232,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioPlaybackCallback {
+    ctor public AudioManager.AudioPlaybackCallback();
+    method public void onPlaybackConfigChanged(java.util.List<android.media.AudioPlaybackConfiguration>);
+  }
+
   public static abstract class AudioManager.AudioRecordingCallback {
     ctor public AudioManager.AudioRecordingCallback();
     method public void onRecordingConfigChanged(java.util.List<android.media.AudioRecordingConfiguration>);
@@ -20238,6 +20246,13 @@
     method public abstract void onAudioFocusChange(int);
   }
 
+  public final class AudioPlaybackConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
+  }
+
   public class AudioRecord implements android.media.AudioRouting {
     ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
diff --git a/api/system-current.txt b/api/system-current.txt
index 8f91d85..f0303a0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -21590,6 +21590,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
     method public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
@@ -21614,6 +21615,7 @@
     method public void playSoundEffect(int);
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+    method public void registerAudioPlaybackCallback(android.media.AudioManager.AudioPlaybackCallback, android.os.Handler);
     method public int registerAudioPolicy(android.media.audiopolicy.AudioPolicy);
     method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
@@ -21641,6 +21643,7 @@
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+    method public void unregisterAudioPlaybackCallback(android.media.AudioManager.AudioPlaybackCallback);
     method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy);
     method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
@@ -21743,6 +21746,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioPlaybackCallback {
+    ctor public AudioManager.AudioPlaybackCallback();
+    method public void onPlaybackConfigChanged(java.util.List<android.media.AudioPlaybackConfiguration>);
+  }
+
   public static abstract class AudioManager.AudioRecordingCallback {
     ctor public AudioManager.AudioRecordingCallback();
     method public void onRecordingConfigChanged(java.util.List<android.media.AudioRecordingConfiguration>);
@@ -21752,6 +21760,28 @@
     method public abstract void onAudioFocusChange(int);
   }
 
+  public final class AudioPlaybackConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public int getClientPid();
+    method public int getClientUid();
+    method public int getPlayerState();
+    method public int getPlayerType();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
+    field public static final int PLAYER_STATE_IDLE = 1; // 0x1
+    field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
+    field public static final int PLAYER_STATE_RELEASED = 0; // 0x0
+    field public static final int PLAYER_STATE_STARTED = 2; // 0x2
+    field public static final int PLAYER_STATE_STOPPED = 4; // 0x4
+    field public static final int PLAYER_STATE_UNKNOWN = -1; // 0xffffffff
+    field public static final int PLAYER_TYPE_JAM_AUDIOTRACK = 1; // 0x1
+    field public static final int PLAYER_TYPE_JAM_MEDIAPLAYER = 2; // 0x2
+    field public static final int PLAYER_TYPE_JAM_SOUNDPOOL = 3; // 0x3
+    field public static final int PLAYER_TYPE_SLES_AUDIOPLAYER = 11; // 0xb
+    field public static final int PLAYER_TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
   public class AudioRecord implements android.media.AudioRouting {
     ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
diff --git a/api/test-current.txt b/api/test-current.txt
index 4f1f147..0ecf4bd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -20171,6 +20171,7 @@
     method public void adjustVolume(int, int);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
+    method public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
     method public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
     method public android.media.AudioDeviceInfo[] getDevices(int);
     method public int getMode();
@@ -20194,6 +20195,7 @@
     method public void playSoundEffect(int);
     method public void playSoundEffect(int, float);
     method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+    method public void registerAudioPlaybackCallback(android.media.AudioManager.AudioPlaybackCallback, android.os.Handler);
     method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
     method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
@@ -20218,6 +20220,7 @@
     method public void stopBluetoothSco();
     method public void unloadSoundEffects();
     method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+    method public void unregisterAudioPlaybackCallback(android.media.AudioManager.AudioPlaybackCallback);
     method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
     method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
     method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
@@ -20316,6 +20319,11 @@
     field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
   }
 
+  public static abstract class AudioManager.AudioPlaybackCallback {
+    ctor public AudioManager.AudioPlaybackCallback();
+    method public void onPlaybackConfigChanged(java.util.List<android.media.AudioPlaybackConfiguration>);
+  }
+
   public static abstract class AudioManager.AudioRecordingCallback {
     ctor public AudioManager.AudioRecordingCallback();
     method public void onRecordingConfigChanged(java.util.List<android.media.AudioRecordingConfiguration>);
@@ -20325,6 +20333,13 @@
     method public abstract void onAudioFocusChange(int);
   }
 
+  public final class AudioPlaybackConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
+  }
+
   public class AudioRecord implements android.media.AudioRouting {
     ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 4976002..664c7ea 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -326,6 +326,12 @@
 }
 
 static jint
+android_media_AudioSystem_newAudioPlayerId(JNIEnv *env, jobject thiz)
+{
+    return AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_PLAYER);
+}
+
+static jint
 android_media_AudioSystem_setParameters(JNIEnv *env, jobject thiz, jstring keyValuePairs)
 {
     const jchar* c_keyValuePairs = env->GetStringCritical(keyValuePairs, 0);
@@ -1755,6 +1761,7 @@
     {"isStreamActiveRemotely","(II)Z",  (void *)android_media_AudioSystem_isStreamActiveRemotely},
     {"isSourceActive",      "(I)Z",     (void *)android_media_AudioSystem_isSourceActive},
     {"newAudioSessionId",   "()I",      (void *)android_media_AudioSystem_newAudioSessionId},
+    {"newAudioPlayerId",    "()I",      (void *)android_media_AudioSystem_newAudioPlayerId},
     {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
     {"getDeviceConnectionState", "(ILjava/lang/String;)I",  (void *)android_media_AudioSystem_getDeviceConnectionState},
     {"setPhoneState",       "(I)I",     (void *)android_media_AudioSystem_setPhoneState},
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 65eadb6..23bb6f9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -67,7 +67,8 @@
     private long mVolumeKeyUpTime;
     private final boolean mUseVolumeKeySounds;
     private final boolean mUseFixedVolume;
-    private static String TAG = "AudioManager";
+    private static final String TAG = "AudioManager";
+    private static final boolean DEBUG = false;
     private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
 
     /**
@@ -2130,6 +2131,7 @@
      */
     private final static int MSSG_FOCUS_CHANGE = 0;
     private final static int MSSG_RECORDING_CONFIG_CHANGE = 1;
+    private final static int MSSG_PLAYBACK_CONFIG_CHANGE = 2;
 
     /**
      * Helper class to handle the forwarding of audio service events to the appropriate listener
@@ -2153,7 +2155,7 @@
                     @Override
                     public void handleMessage(Message msg) {
                         switch (msg.what) {
-                            case MSSG_FOCUS_CHANGE:
+                            case MSSG_FOCUS_CHANGE: {
                                 OnAudioFocusChangeListener listener = null;
                                 synchronized(mFocusListenerLock) {
                                     listener = findFocusListener((String)msg.obj);
@@ -2163,14 +2165,24 @@
                                             + msg.arg1 + ") for " + msg.obj);
                                     listener.onAudioFocusChange(msg.arg1);
                                 }
-                                break;
-                            case MSSG_RECORDING_CONFIG_CHANGE:
+                            } break;
+                            case MSSG_RECORDING_CONFIG_CHANGE: {
                                 final RecordConfigChangeCallbackData cbData =
                                         (RecordConfigChangeCallbackData) msg.obj;
                                 if (cbData.mCb != null) {
                                     cbData.mCb.onRecordingConfigChanged(cbData.mConfigs);
                                 }
-                                break;
+                            } break;
+                            case MSSG_PLAYBACK_CONFIG_CHANGE: {
+                                final PlaybackConfigChangeCallbackData cbData =
+                                        (PlaybackConfigChangeCallbackData) msg.obj;
+                                if (cbData.mCb != null) {
+                                    if (DEBUG) {
+                                        Log.d(TAG, "dispatching onPlaybackConfigChanged()");
+                                    }
+                                    cbData.mCb.onPlaybackConfigChanged(cbData.mConfigs);
+                                }
+                            } break;
                             default:
                                 Log.e(TAG, "Unknown event " + msg.what);
                         }
@@ -2740,9 +2752,193 @@
         }
     }
 
+    //====================================================================
+    // Notification of playback activity & playback configuration
+    /**
+     * Interface for receiving update notifications about the playback activity on the system.
+     * Extend this abstract class and register it with
+     * {@link AudioManager#registerAudioPlaybackCallback(AudioPlaybackCallback, Handler)}
+     * to be notified.
+     * Use {@link AudioManager#getActivePlaybackConfigurations()} to query the current
+     * configuration.
+     * @see AudioPlaybackConfiguration
+     */
+    public static abstract class AudioPlaybackCallback {
+        /**
+         * Called whenever the playback activity and configuration has changed.
+         * @param configs list containing the results of
+         *      {@link AudioManager#getActivePlaybackConfigurations()}.
+         */
+        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {}
+    }
+
+    private static class AudioPlaybackCallbackInfo {
+        final AudioPlaybackCallback mCb;
+        final Handler mHandler;
+        AudioPlaybackCallbackInfo(AudioPlaybackCallback cb, Handler handler) {
+            mCb = cb;
+            mHandler = handler;
+        }
+    }
+
+    private final static class PlaybackConfigChangeCallbackData {
+        final AudioPlaybackCallback mCb;
+        final List<AudioPlaybackConfiguration> mConfigs;
+
+        PlaybackConfigChangeCallbackData(AudioPlaybackCallback cb,
+                List<AudioPlaybackConfiguration> configs) {
+            mCb = cb;
+            mConfigs = configs;
+        }
+    }
+
+    /**
+     * Register a callback to be notified of audio playback changes through
+     * {@link AudioPlaybackCallback}
+     * @param cb non-null callback to register
+     * @param handler the {@link Handler} object for the thread on which to execute
+     * the callback. If <code>null</code>, the {@link Handler} associated with the main
+     * {@link Looper} will be used.
+     */
+    public void registerAudioPlaybackCallback(@NonNull AudioPlaybackCallback cb, Handler handler)
+    {
+        if (cb == null) {
+            throw new IllegalArgumentException("Illegal null AudioPlaybackCallback argument");
+        }
+
+        synchronized(mPlaybackCallbackLock) {
+            // lazy initialization of the list of playback callbacks
+            if (mPlaybackCallbackList == null) {
+                mPlaybackCallbackList = new ArrayList<AudioPlaybackCallbackInfo>();
+            }
+            final int oldCbCount = mPlaybackCallbackList.size();
+            if (!hasPlaybackCallback_sync(cb)) {
+                mPlaybackCallbackList.add(new AudioPlaybackCallbackInfo(cb,
+                        new ServiceEventHandlerDelegate(handler).getHandler()));
+                final int newCbCount = mPlaybackCallbackList.size();
+                if ((oldCbCount == 0) && (newCbCount > 0)) {
+                    // register binder for callbacks
+                    try {
+                        getService().registerPlaybackCallback(mPlayCb);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+            } else {
+                Log.w(TAG, "attempt to call registerAudioPlaybackCallback() on a previously"
+                        + "registered callback");
+            }
+        }
+    }
+
+    /**
+     * Unregister an audio playback callback previously registered with
+     * {@link #registerAudioPlaybackCallback(AudioPlaybackCallback, Handler)}.
+     * @param cb non-null callback to unregister
+     */
+    public void unregisterAudioPlaybackCallback(@NonNull AudioPlaybackCallback cb) {
+        if (cb == null) {
+            throw new IllegalArgumentException("Illegal null AudioPlaybackCallback argument");
+        }
+        synchronized(mPlaybackCallbackLock) {
+            if (mPlaybackCallbackList == null) {
+                Log.w(TAG, "attempt to call unregisterAudioPlaybackCallback() on a callback"
+                        + " that was never registered");
+                return;
+            }
+            final int oldCbCount = mPlaybackCallbackList.size();
+            if (removePlaybackCallback_sync(cb)) {
+                final int newCbCount = mPlaybackCallbackList.size();
+                if ((oldCbCount > 0) && (newCbCount == 0)) {
+                    // unregister binder for callbacks
+                    try {
+                        getService().unregisterPlaybackCallback(mPlayCb);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+            } else {
+                Log.w(TAG, "attempt to call unregisterAudioPlaybackCallback() on a callback"
+                        + " already unregistered or never registered");
+            }
+        }
+    }
+
+    /**
+     * Returns the current active audio playback configurations of the device
+     * @return a non-null list of playback configurations. An empty list indicates there is no
+     *     playback active when queried.
+     * @see AudioPlaybackConfiguration
+     */
+    public @NonNull List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() {
+        final IAudioService service = getService();
+        try {
+            return service.getActivePlaybackConfigurations();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * All operations on this list are sync'd on mPlaybackCallbackLock.
+     * List is lazy-initialized in
+     * {@link #registerAudioPlaybackCallback(AudioPlaybackCallback, Handler)}.
+     * List can be null.
+     */
+    private List<AudioPlaybackCallbackInfo> mPlaybackCallbackList;
+    private final Object mPlaybackCallbackLock = new Object();
+
+    /**
+     * Must be called synchronized on mPlaybackCallbackLock
+     */
+    private boolean hasPlaybackCallback_sync(@NonNull AudioPlaybackCallback cb) {
+        if (mPlaybackCallbackList != null) {
+            for (int i=0 ; i < mPlaybackCallbackList.size() ; i++) {
+                if (cb.equals(mPlaybackCallbackList.get(i).mCb)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Must be called synchronized on mPlaybackCallbackLock
+     */
+    private boolean removePlaybackCallback_sync(@NonNull AudioPlaybackCallback cb) {
+        if (mPlaybackCallbackList != null) {
+            for (int i=0 ; i < mPlaybackCallbackList.size() ; i++) {
+                if (cb.equals(mPlaybackCallbackList.get(i).mCb)) {
+                    mPlaybackCallbackList.remove(i);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private final IPlaybackConfigDispatcher mPlayCb = new IPlaybackConfigDispatcher.Stub() {
+
+        public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
+            synchronized(mPlaybackCallbackLock) {
+                if (mPlaybackCallbackList != null) {
+                    for (int i=0 ; i < mPlaybackCallbackList.size() ; i++) {
+                        final AudioPlaybackCallbackInfo arci = mPlaybackCallbackList.get(i);
+                        if (arci.mHandler != null) {
+                            final Message m = arci.mHandler.obtainMessage(
+                                    MSSG_PLAYBACK_CONFIG_CHANGE/*what*/,
+                                    new PlaybackConfigChangeCallbackData(arci.mCb, configs)/*obj*/);
+                            arci.mHandler.sendMessage(m);
+                        }
+                    }
+                }
+            }
+        }
+
+    };
 
     //====================================================================
-    // Recording configuration
+    // Notification of recording activity & recording configuration
     /**
      * Interface for receiving update notifications about the recording configuration. Extend
      * this abstract class and register it with
diff --git a/media/java/android/media/AudioPlaybackConfiguration.aidl b/media/java/android/media/AudioPlaybackConfiguration.aidl
new file mode 100644
index 0000000..122fad0
--- /dev/null
+++ b/media/java/android/media/AudioPlaybackConfiguration.aidl
@@ -0,0 +1,18 @@
+/* Copyright 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;
+
+parcelable AudioPlaybackConfiguration;
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
new file mode 100644
index 0000000..3382cd95
--- /dev/null
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -0,0 +1,382 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Binder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+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 final static String TAG = new String("AudioPlaybackConfiguration");
+
+    // information about the implementation
+    /**
+     * @hide
+     * An unknown type of player
+     */
+    @SystemApi
+    public final static int PLAYER_TYPE_UNKNOWN = -1;
+    /**
+     * @hide
+     * Player backed by a java android.media.AudioTrack player
+     */
+    @SystemApi
+    public final static int PLAYER_TYPE_JAM_AUDIOTRACK = 1;
+    /**
+     * @hide
+     * Player backed by a java android.media.MediaPlayer player
+     */
+    @SystemApi
+    public final static int PLAYER_TYPE_JAM_MEDIAPLAYER = 2;
+    /**
+     * @hide
+     * Player backed by a java android.media.SoundPool player
+     */
+    @SystemApi
+    public final static int PLAYER_TYPE_JAM_SOUNDPOOL = 3;
+    /**
+     * @hide
+     * Player backed by a C OpenSL ES AudioPlayer player
+     */
+    @SystemApi
+    public final static int PLAYER_TYPE_SLES_AUDIOPLAYER = 11;
+
+    /** @hide */
+    @IntDef({
+        PLAYER_TYPE_UNKNOWN,
+        PLAYER_TYPE_JAM_AUDIOTRACK,
+        PLAYER_TYPE_JAM_MEDIAPLAYER,
+        PLAYER_TYPE_JAM_SOUNDPOOL,
+        PLAYER_TYPE_SLES_AUDIOPLAYER
+    })
+    @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 */
+    @IntDef({
+        PLAYER_STATE_UNKNOWN,
+        PLAYER_STATE_RELEASED,
+        PLAYER_STATE_IDLE,
+        PLAYER_STATE_STARTED,
+        PLAYER_STATE_PAUSED,
+        PLAYER_STATE_STOPPED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PlayerState {}
+
+    // immutable data
+    private final int mPlayerIId;
+
+    // not final due to anonymization step
+    private int mPlayerType;
+    private int mClientUid;
+    private int mClientPid;
+
+    private int mPlayerState;
+    private AudioAttributes mPlayerAttr; // never null
+
+    /**
+     * Never use without initializing parameters afterwards
+     */
+    private AudioPlaybackConfiguration(int piid) {
+        mPlayerIId = piid;
+    }
+
+    /**
+     * @hide
+     */
+    public AudioPlaybackConfiguration(PlayerBase.PlayerIdCard pic) {
+        mPlayerIId = pic.mPIId;
+        mPlayerType = pic.mPlayerType;
+        mClientUid = pic.mClientUid;
+        mClientPid = pic.mClientPid;
+        mPlayerState = PLAYER_STATE_IDLE;
+        mPlayerAttr = pic.mAttributes;
+    }
+
+    // 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 toSanitize
+     */
+    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
+        anonymCopy.mPlayerAttr = new AudioAttributes.Builder()
+                .setUsage(in.mPlayerAttr.getUsage())
+                .setContentType(in.mPlayerAttr.getContentType())
+                .setFlags(in.mPlayerAttr.getFlags())
+                .build();
+        // anonymized data
+        anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
+        anonymCopy.mClientUid = 0;
+        anonymCopy.mClientPid = 0;
+        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;
+    }
+
+    /**
+     * @hide
+     * Return the type of player linked to this configuration. The return value is one of
+     * {@link #PLAYER_TYPE_JAM_AUDIOTRACK}, {@link #PLAYER_TYPE_JAM_MEDIAPLAYER},
+     * {@link #PLAYER_TYPE_JAM_SOUNDPOOL}, {@link #PLAYER_TYPE_SLES_AUDIOPLAYER},
+     * or {@link #PLAYER_TYPE_UNKNOWN}.
+     * @return the type of the player.
+     */
+    @SystemApi
+    public @PlayerType int getPlayerType() {
+        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
+     * 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 player state change
+     * @param event
+     * @return true if the state changed, false otherwise
+     */
+    public boolean handleStateEvent(int event) {
+        final boolean changed = (mPlayerState != event);
+        mPlayerState = event;
+        return changed;
+    }
+
+    /**
+     * @hide
+     * Returns true if the player is considered "active", i.e. actively playing, and thus
+     * in a state that should make it considered for the list public (sanitized) active playback
+     * configurations
+     * @return true if active
+     */
+    public boolean isActive() {
+        switch (mPlayerState) {
+            case PLAYER_STATE_STARTED:
+                return true;
+            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("  ID:" + mPlayerIId
+                + " -- type:" + toLogFriendlyPlayerType(mPlayerType)
+                + " -- u/pid:" + mClientUid +"/" + mClientPid
+                + " -- state:" + toLogFriendlyPlayerState(mPlayerState)
+                + " -- attr:" + mPlayerAttr);
+    }
+
+    public static final 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() {
+        return Objects.hash(mPlayerIId, mPlayerType, mClientUid, mClientPid);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mPlayerIId);
+        dest.writeInt(mPlayerType);
+        dest.writeInt(mClientUid);
+        dest.writeInt(mClientPid);
+        dest.writeInt(mPlayerState);
+        mPlayerAttr.writeToParcel(dest, 0);
+    }
+
+    private AudioPlaybackConfiguration(Parcel in) {
+        mPlayerIId = in.readInt();
+        mPlayerType = in.readInt();
+        mClientUid = in.readInt();
+        mClientPid = in.readInt();
+        mPlayerState = in.readInt();
+        mPlayerAttr = AudioAttributes.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)
+                && (mPlayerType == that.mPlayerType)
+                && (mClientUid == that.mClientUid)
+                && (mClientPid == that.mClientPid));
+    }
+
+    //=====================================================================
+    // 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: return "OpenSL ES AudioPlayer";
+            default:
+                return "unknown player 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";
+            default:
+                return "unknown player state - FIXME";
+        }
+    }
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 28c7253..17928995 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -161,6 +161,12 @@
     public static native int newAudioSessionId();
 
     /*
+     * Returns a new unused audio player ID
+     */
+    public static native int newAudioPlayerId();
+
+
+    /*
      * Sets a group generic audio configuration parameters. The use of these parameters
      * are platform dependent, see libaudio
      *
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 16b3315..464cbdb 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -471,7 +471,7 @@
     public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
             int mode, int sessionId)
                     throws IllegalArgumentException {
-        super(attributes);
+        super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
         // mState already == STATE_UNINITIALIZED
 
         if (format == null) {
@@ -551,7 +551,8 @@
      * OpenSLES interface is realized.
      */
     /*package*/ AudioTrack(long nativeTrackInJavaObj) {
-        super(new AudioAttributes.Builder().build());
+        super(new AudioAttributes.Builder().build(),
+                AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
         // "final"s
         mNativeTrackInJavaObj = 0;
         mJniData = 0;
@@ -1749,8 +1750,8 @@
         if (mState != STATE_INITIALIZED) {
             throw new IllegalStateException("play() called on uninitialized AudioTrack.");
         }
-        baseStart();
         synchronized(mPlayStateLock) {
+            baseStart();
             native_start();
             mPlayState = PLAYSTATE_PLAYING;
         }
@@ -1773,6 +1774,7 @@
         // stop playing
         synchronized(mPlayStateLock) {
             native_stop();
+            baseStop();
             mPlayState = PLAYSTATE_STOPPED;
             mAvSyncHeader = null;
             mAvSyncBytesRemaining = 0;
@@ -1791,11 +1793,11 @@
         if (mState != STATE_INITIALIZED) {
             throw new IllegalStateException("pause() called on uninitialized AudioTrack.");
         }
-        //logd("pause()");
 
         // pause playback
         synchronized(mPlayStateLock) {
             native_pause();
+            basePause();
             mPlayState = PLAYSTATE_PAUSED;
         }
     }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index c7931fc..151b4d5 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -20,13 +20,16 @@
 import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.media.AudioAttributes;
+import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.IAudioFocusDispatcher;
 import android.media.IAudioRoutesObserver;
+import android.media.IPlaybackConfigDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
 import android.media.IVolumeController;
+import android.media.PlayerBase;
 import android.media.Rating;
 import android.media.VolumePolicy;
 import android.media.audiopolicy.AudioPolicyConfig;
@@ -165,4 +168,18 @@
     oneway void unregisterRecordingCallback(in IRecordingConfigDispatcher rcdb);
 
     List<AudioRecordingConfiguration> getActiveRecordingConfigurations();
+
+    void registerPlaybackCallback(in IPlaybackConfigDispatcher pcdb);
+
+    oneway void unregisterPlaybackCallback(in IPlaybackConfigDispatcher pcdb);
+
+    List<AudioPlaybackConfiguration> getActivePlaybackConfigurations();
+
+    oneway void trackPlayer(in PlayerBase.PlayerIdCard pic);
+
+    oneway void playerAttributes(in int piid, in AudioAttributes attr);
+
+    oneway void playerEvent(in int piid, in int event);
+
+    oneway void releasePlayer(in int piid);
 }
diff --git a/media/java/android/media/IPlaybackConfigDispatcher.aidl b/media/java/android/media/IPlaybackConfigDispatcher.aidl
new file mode 100644
index 0000000..3cb5216
--- /dev/null
+++ b/media/java/android/media/IPlaybackConfigDispatcher.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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 android.media.AudioPlaybackConfiguration;
+
+/**
+ * AIDL for the PlaybackActivityMonitor in AudioService to signal audio playback updates.
+ *
+ * {@hide}
+ */
+oneway interface IPlaybackConfigDispatcher {
+
+    void dispatchPlaybackConfigChange(in List<AudioPlaybackConfiguration> configs);
+
+}
diff --git a/media/java/android/media/IPlayer.aidl b/media/java/android/media/IPlayer.aidl
new file mode 100644
index 0000000..32984f9
--- /dev/null
+++ b/media/java/android/media/IPlayer.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+
+/**
+ * @hide
+ */
+interface IPlayer {
+    oneway void start();
+    oneway void pause();
+    oneway void stop();
+}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 78da59c..77a0fbd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -626,7 +626,8 @@
      * result in an exception.</p>
      */
     public MediaPlayer() {
-        super(new AudioAttributes.Builder().build());
+        super(new AudioAttributes.Builder().build(),
+                AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);
 
         Looper looper;
         if ((looper = Looper.myLooper()) != null) {
@@ -1241,6 +1242,7 @@
     public void stop() throws IllegalStateException {
         stayAwake(false);
         _stop();
+        baseStop();
     }
 
     private native void _stop() throws IllegalStateException;
@@ -1254,6 +1256,7 @@
     public void pause() throws IllegalStateException {
         stayAwake(false);
         _pause();
+        basePause();
     }
 
     private native void _pause() throws IllegalStateException;
@@ -2973,6 +2976,7 @@
 
             case MEDIA_PLAYBACK_COMPLETE:
                 {
+                    mOnCompletionInternalListener.onCompletion(mMediaPlayer);
                     OnCompletionListener onCompletionListener = mOnCompletionListener;
                     if (onCompletionListener != null)
                         onCompletionListener.onCompletion(mMediaPlayer);
@@ -3037,6 +3041,7 @@
                     error_was_handled = onErrorListener.onError(mMediaPlayer, msg.arg1, msg.arg2);
                 }
                 {
+                    mOnCompletionInternalListener.onCompletion(mMediaPlayer);
                     OnCompletionListener onCompletionListener = mOnCompletionListener;
                     if (onCompletionListener != null && ! error_was_handled) {
                         onCompletionListener.onCompletion(mMediaPlayer);
@@ -3215,6 +3220,17 @@
     private OnCompletionListener mOnCompletionListener;
 
     /**
+     * @hide
+     * Internal completion listener to update PlayerBase of the play state. Always "registered".
+     */
+    private final OnCompletionListener mOnCompletionInternalListener = new OnCompletionListener() {
+        @Override
+        public void onCompletion(MediaPlayer mp) {
+            baseStop();
+        }
+    };
+
+    /**
      * Interface definition of a callback to be invoked indicating buffering
      * status of a media resource being streamed over the network.
      */
diff --git a/media/java/android/media/PlayerBase.aidl b/media/java/android/media/PlayerBase.aidl
new file mode 100644
index 0000000..4ae2125
--- /dev/null
+++ b/media/java/android/media/PlayerBase.aidl
@@ -0,0 +1,18 @@
+/* Copyright 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;
+
+parcelable PlayerBase.PlayerIdCard;
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 42f6b83..49c89a3 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -16,13 +16,14 @@
 
 package android.media;
 
-import java.lang.IllegalArgumentException;
-
 import android.annotation.NonNull;
 import android.app.ActivityThread;
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.os.Binder;
 import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -31,6 +32,9 @@
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 
+import java.lang.IllegalArgumentException;
+import java.util.Objects;
+
 /**
  * Class to encapsulate a number of common player operations:
  *   - AppOps for OP_PLAY_AUDIO
@@ -40,6 +44,7 @@
 public abstract class PlayerBase {
 
     private final static String TAG = "PlayerBase";
+    private final static boolean DEBUG = false;
     private static IAudioService sService; //lazy initialization, use getService()
     /** Debug app ops */
     protected static final boolean DEBUG_APP_OPS = Log.isLoggable(TAG + ".AO", Log.DEBUG);
@@ -56,15 +61,24 @@
     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 mState;
+
     /**
      * Constructor. Must be given audio attributes, as they are required for AppOps.
      * @param attr non-null audio attributes
+     * @param class non-null class of the implementation of this abstract class
      */
-    PlayerBase(@NonNull AudioAttributes attr) {
+    PlayerBase(@NonNull AudioAttributes attr, int implType) {
         if (attr == null) {
             throw new IllegalArgumentException("Illegal null AudioAttributes");
         }
         mAttributes = attr;
+        mImplType = implType;
+        mPlayerIId = AudioSystem.newAudioPlayerId();
         IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
         mAppOps = IAppOpsService.Stub.asInterface(b);
         // initialize mHasAppOpsPlayAudio
@@ -85,6 +99,11 @@
         } catch (RemoteException e) {
             mHasAppOpsPlayAudio = false;
         }
+        try {
+            getService().trackPlayer(new PlayerIdCard(mPlayerIId, mImplType, mAttributes));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to audio service, player will not be tracked", e);
+        }
     }
 
 
@@ -96,6 +115,11 @@
         if (attr == null) {
             throw new IllegalArgumentException("Illegal null AudioAttributes");
         }
+        try {
+            getService().playerAttributes(mPlayerIId, attr);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
+        }
         synchronized (mAppOpsLock) {
             mAttributes = attr;
             updateAppOpsPlayAudio_sync();
@@ -103,6 +127,12 @@
     }
 
     void baseStart() {
+        if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); }
+        try {
+            getService().playerEvent(mPlayerIId, AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
+        }
         synchronized (mAppOpsLock) {
             if (isRestricted_sync()) {
                 playerSetVolume(true/*muting*/,0, 0);
@@ -110,6 +140,24 @@
         }
     }
 
+    void basePause() {
+        if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); }
+        try {
+            getService().playerEvent(mPlayerIId, AudioPlaybackConfiguration.PLAYER_STATE_PAUSED);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to audio service, PAUSED state will not be tracked", e);
+        }
+    }
+
+    void baseStop() {
+        if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); }
+        try {
+            getService().playerEvent(mPlayerIId, AudioPlaybackConfiguration.PLAYER_STATE_STOPPED);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to audio service, STOPPED state will not be tracked", e);
+        }
+    }
+
     void baseSetVolume(float leftVolume, float rightVolume) {
         synchronized (mAppOpsLock) {
             mLeftVolume = leftVolume;
@@ -136,6 +184,15 @@
      * Releases AppOps related resources.
      */
     void baseRelease() {
+        if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId); }
+        try {
+            if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
+                getService().releasePlayer(mPlayerIId);
+                mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to audio service, the player will still be tracked", e);
+        }
         try {
             mAppOps.stopWatchingMode(mAppOpsCallback);
         } catch (RemoteException e) {
@@ -227,6 +284,7 @@
         return sService;
     }
 
+    //=====================================================================
     // Abstract methods a subclass needs to implement
     /**
      * Abstract method for the subclass behavior's for volume and muting commands
@@ -238,6 +296,92 @@
     abstract int playerSetAuxEffectSendLevel(boolean muting, float level);
 
     //=====================================================================
+    // Implementation of IPlayer
+    private final IPlayer mIPlayer = new IPlayer.Stub() {
+        @Override
+        public void start() {}
+        @Override
+        public void pause() {}
+        @Override
+        public void stop() {}
+    };
+
+    //=====================================================================
+    /**
+     * Class holding all the information about a player that needs to be known at registration time
+     */
+    public static class PlayerIdCard implements Parcelable {
+        public final int mPIId;
+        public final int mPlayerType;
+        public final int mClientUid;
+        public final int mClientPid;
+
+        public final static int AUDIO_ATTRIBUTES_NONE = 0;
+        public final static int AUDIO_ATTRIBUTES_DEFINED = 1;
+        public final AudioAttributes mAttributes;
+
+        PlayerIdCard(int piid, int type, @NonNull AudioAttributes attr) {
+            mPIId = piid;
+            mPlayerType = type;
+            mClientUid = Binder.getCallingUid();
+            mClientPid = Binder.getCallingPid();
+            mAttributes = attr;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPIId, mPlayerType);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mPIId);
+            dest.writeInt(mPlayerType);
+            dest.writeInt(mClientUid);
+            dest.writeInt(mClientPid);
+            mAttributes.writeToParcel(dest, 0);
+        }
+
+        public static final Parcelable.Creator<PlayerIdCard> CREATOR
+        = new Parcelable.Creator<PlayerIdCard>() {
+            /**
+             * Rebuilds an PlayerIdCard previously stored with writeToParcel().
+             * @param p Parcel object to read the PlayerIdCard from
+             * @return a new PlayerIdCard created from the data in the parcel
+             */
+            public PlayerIdCard createFromParcel(Parcel p) {
+                return new PlayerIdCard(p);
+            }
+            public PlayerIdCard[] newArray(int size) {
+                return new PlayerIdCard[size];
+            }
+        };
+
+        private PlayerIdCard(Parcel in) {
+            mPIId = in.readInt();
+            mPlayerType = in.readInt();
+            mClientUid = in.readInt();
+            mClientPid = in.readInt();
+            mAttributes = AudioAttributes.CREATOR.createFromParcel(in);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || !(o instanceof PlayerIdCard)) return false;
+
+            PlayerIdCard that = (PlayerIdCard) o;
+
+            return (mPIId == that.mPIId);
+        }
+    }
+
+    //=====================================================================
     // Utilities
 
     /**
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 838767c..c985cbd 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -151,7 +151,7 @@
     }
 
     private SoundPool(int maxStreams, AudioAttributes attributes) {
-        super(attributes);
+        super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL);
 
         // do native setup
         if (native_setup(new WeakReference<SoundPool>(this), maxStreams, attributes) != 0) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 67f3614..788a28c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -59,11 +59,13 @@
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioPort;
+import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.IAudioFocusDispatcher;
 import android.media.IAudioRoutesObserver;
 import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
 import android.media.IVolumeController;
@@ -72,6 +74,7 @@
 import android.media.VolumePolicy;
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
+import android.media.PlayerBase;
 import android.media.audiopolicy.AudioMix;
 import android.media.audiopolicy.AudioPolicy;
 import android.media.audiopolicy.AudioPolicyConfig;
@@ -6023,6 +6026,8 @@
         pw.print("  mVolumePolicy="); pw.println(mVolumePolicy);
 
         dumpAudioPolicies(pw);
+
+        mPlaybackMonitor.dump(pw);
     }
 
     private static String safeMediaVolumeStateToString(Integer state) {
@@ -6443,6 +6448,42 @@
     }
 
     //======================
+    // Audio policy callbacks from players for playback configuration updates
+    //======================
+    private final PlaybackActivityMonitor mPlaybackMonitor = new PlaybackActivityMonitor();
+
+    public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
+        final boolean isPrivileged = 
+                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+                        android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+        mPlaybackMonitor.registerPlaybackCallback(pcdb, isPrivileged);
+    }
+
+    public void unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
+        mPlaybackMonitor.unregisterPlaybackCallback(pcdb);
+    }
+
+    public List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() {
+        return mPlaybackMonitor.getActivePlaybackConfigurations();
+    }
+
+    public void trackPlayer(PlayerBase.PlayerIdCard pic) {
+        mPlaybackMonitor.trackPlayer(pic);
+    }
+
+    public void playerAttributes(int piid, AudioAttributes attr) {
+        mPlaybackMonitor.playerAttributes(piid, attr);
+    }
+
+    public void playerEvent(int piid, int event) {
+        mPlaybackMonitor.playerEvent(piid, event);
+    }
+
+    public void releasePlayer(int piid) {
+        mPlaybackMonitor.releasePlayer(piid);
+    }
+
+    //======================
     // Audio policy proxy
     //======================
     /**
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
new file mode 100644
index 0000000..b99e4e9
--- /dev/null
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -0,0 +1,277 @@
+/*
+ * 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 com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioSystem;
+import android.media.IPlaybackConfigDispatcher;
+import android.media.MediaRecorder;
+import android.media.PlayerBase;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Class to receive and dispatch updates from AudioSystem about recording configurations.
+ */
+public final class PlaybackActivityMonitor {
+
+    public final static String TAG = "AudioService.PlaybackActivityMonitor";
+    private final static boolean DEBUG = false;
+
+    private ArrayList<PlayMonitorClient> mClients = new ArrayList<PlayMonitorClient>();
+    // a public client is one that needs an anonymized version of the playback configurations, we
+    // keep track of whether there is at least one to know when we need to create the list of
+    // playback configurations that do not contain uid/pid/package name information.
+    private boolean mHasPublicClients = false;
+
+    private final Object mPlayerLock = new Object();
+    private HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
+            new HashMap<Integer, AudioPlaybackConfiguration>();
+
+    PlaybackActivityMonitor() {
+        PlayMonitorClient.sMonitor = this;
+    }
+
+    //=================================================================
+    // Track players and their states
+    // methods trackPlayer, playerAttributes, playerEvent, releasePlayer are all oneway calls
+    //  into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
+    //  all listeners as oneway calls.
+
+    public void trackPlayer(PlayerBase.PlayerIdCard pic) {
+        if (DEBUG) { Log.v(TAG, "trackPlayer() for piid=" + pic.mPIId); }
+        final AudioPlaybackConfiguration apc = new AudioPlaybackConfiguration(pic);
+        synchronized(mPlayerLock) {
+            mPlayers.put(pic.mPIId, apc);
+        }
+    }
+
+    public void playerAttributes(int piid, @NonNull AudioAttributes attr) {
+        final boolean change;
+        synchronized(mPlayerLock) {
+            final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
+            if (apc == null) {
+                Log.e(TAG, "Unknown player " + piid + " for audio attributes change");
+                change = false;
+            } else {
+                change = apc.handleAudioAttributesEvent(attr);
+            }
+        }
+        if (change) {
+            dispatchPlaybackChange();
+        }
+    }
+
+    public void playerEvent(int piid, int event) {
+        if (DEBUG) { Log.v(TAG, String.format("trackPlayer(piid=%d, event=%d)", piid, event)); }
+        final boolean change;
+        synchronized(mPlayerLock) {
+            final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
+            if (apc == null) {
+                Log.e(TAG, "Unknown player " + piid + " for event " + event);
+                change = false;
+            } else {
+                //TODO add generation counter to only update to the latest state
+                change = apc.handleStateEvent(event);
+            }
+        }
+        if (change) {
+            dispatchPlaybackChange();
+        }
+    }
+
+    public void releasePlayer(int piid) {
+        if (DEBUG) { Log.v(TAG, "releasePlayer() for piid=" + piid); }
+        synchronized(mPlayerLock) {
+            if (!mPlayers.containsKey(new Integer(piid))) {
+                Log.e(TAG, "Unknown player " + piid + " for release");
+            } else {
+                mPlayers.remove(new Integer(piid));
+            }
+        }
+    }
+
+    protected void dump(PrintWriter pw) {
+        pw.println("\nPlaybackActivityMonitor dump time: "
+                + DateFormat.getTimeInstance().format(new Date()));
+        synchronized(mPlayerLock) {
+            for (AudioPlaybackConfiguration conf : mPlayers.values()) {
+                conf.dump(pw);
+            }
+        }
+    }
+
+    private void dispatchPlaybackChange() {
+        synchronized (mClients) {
+            // typical use case, nobody is listening, don't do any work
+            if (mClients.isEmpty()) {
+                return;
+            }
+        }
+        if (DEBUG) { Log.v(TAG, "dispatchPlaybackChange to " + mClients.size() + " clients"); }
+        final List<AudioPlaybackConfiguration> configsSystem;
+        // list of playback configurations for "public consumption". It is only computed if there
+        // are non-system playback activity listeners.
+        final List<AudioPlaybackConfiguration> configsPublic;
+        synchronized (mPlayerLock) {
+            if (mPlayers.isEmpty()) {
+                return;
+            }
+            configsSystem = new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
+        }
+        synchronized (mClients) {
+            // was done at beginning of method, but could have changed
+            if (mClients.isEmpty()) {
+                return;
+            }
+            configsPublic = mHasPublicClients ? anonymizeForPublicConsumption(configsSystem) : null;
+            final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
+            while (clientIterator.hasNext()) {
+                final PlayMonitorClient pmc = clientIterator.next();
+                try {
+                    // do not spam the logs if there are problems communicating with this client
+                    if (pmc.mErrorCount < PlayMonitorClient.MAX_ERRORS) {
+                        if (pmc.mIsPrivileged) {
+                            pmc.mDispatcherCb.dispatchPlaybackConfigChange(configsSystem);
+                        } else {
+                            pmc.mDispatcherCb.dispatchPlaybackConfigChange(configsPublic);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    pmc.mErrorCount++;
+                    Log.e(TAG, "Error (" + pmc.mErrorCount +
+                            ") trying to dispatch playback config change to " + pmc, e);
+                }
+            }
+        }
+    }
+
+    private ArrayList<AudioPlaybackConfiguration> anonymizeForPublicConsumption(
+            List<AudioPlaybackConfiguration> sysConfigs) {
+        ArrayList<AudioPlaybackConfiguration> publicConfigs =
+                new ArrayList<AudioPlaybackConfiguration>();
+        // only add active anonymized configurations, 
+        for (AudioPlaybackConfiguration config : sysConfigs) {
+            if (config.isActive()) {
+                publicConfigs.add(AudioPlaybackConfiguration.anonymizedCopy(config));
+            }
+        }
+        return publicConfigs;
+    }
+
+    //=================================================================
+    // Track playback activity listeners
+
+    void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
+        if (pcdb == null) {
+            return;
+        }
+        synchronized(mClients) {
+            final PlayMonitorClient pmc = new PlayMonitorClient(pcdb, isPrivileged);
+            if (pmc.init()) {
+                if (!isPrivileged) {
+                    mHasPublicClients = true;
+                }
+                mClients.add(pmc);
+            }
+        }
+    }
+
+    void unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
+        if (pcdb == null) {
+            return;
+        }
+        synchronized(mClients) {
+            final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
+            boolean hasPublicClients = false;
+            // iterate over the clients to remove the dispatcher to remove, and reevaluate at
+            // the same time if we still have a public client.
+            while (clientIterator.hasNext()) {
+                PlayMonitorClient pmc = clientIterator.next();
+                if (pcdb.equals(pmc.mDispatcherCb)) {
+                    pmc.release();
+                    clientIterator.remove();
+                } else {
+                    if (!pmc.mIsPrivileged) {
+                        hasPublicClients = true;
+                    }
+                }
+            }
+            mHasPublicClients = hasPublicClients;
+        }
+    }
+
+    List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() {
+        synchronized(mPlayers) {
+            return new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
+        }
+    }
+
+
+    /**
+     * Inner class to track clients that want to be notified of playback updates
+     */
+    private final static class PlayMonitorClient implements IBinder.DeathRecipient {
+
+        // can afford to be static because only one PlaybackActivityMonitor ever instantiated
+        static PlaybackActivityMonitor sMonitor;
+
+        final IPlaybackConfigDispatcher mDispatcherCb;
+        final boolean mIsPrivileged;
+
+        int mErrorCount = 0;
+        // number of errors after which we don't update this client anymore to not spam the logs
+        static final int MAX_ERRORS = 5;
+
+        PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
+            mDispatcherCb = pcdb;
+            mIsPrivileged = isPrivileged;
+        }
+
+        public void binderDied() {
+            Log.w(TAG, "client died");
+            sMonitor.unregisterPlaybackCallback(mDispatcherCb);
+        }
+
+        boolean init() {
+            try {
+                mDispatcherCb.asBinder().linkToDeath(this, 0);
+                return true;
+            } catch (RemoteException e) {
+                Log.w(TAG, "Could not link to client death", e);
+                return false;
+            }
+        }
+
+        void release() {
+            mDispatcherCb.asBinder().unlinkToDeath(this, 0);
+        }
+    }
+}