summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--api/current.txt15
-rw-r--r--api/system-current.txt30
-rw-r--r--api/test-current.txt15
-rw-r--r--core/jni/android_media_AudioSystem.cpp7
-rw-r--r--media/java/android/media/AudioManager.java208
-rw-r--r--media/java/android/media/AudioPlaybackConfiguration.aidl18
-rw-r--r--media/java/android/media/AudioPlaybackConfiguration.java382
-rw-r--r--media/java/android/media/AudioSystem.java6
-rw-r--r--media/java/android/media/AudioTrack.java10
-rw-r--r--media/java/android/media/IAudioService.aidl17
-rw-r--r--media/java/android/media/IPlaybackConfigDispatcher.aidl30
-rw-r--r--media/java/android/media/IPlayer.aidl27
-rw-r--r--media/java/android/media/MediaPlayer.java18
-rw-r--r--media/java/android/media/PlayerBase.aidl18
-rw-r--r--media/java/android/media/PlayerBase.java150
-rw-r--r--media/java/android/media/SoundPool.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java41
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java277
19 files changed, 1258 insertions, 15 deletions
diff --git a/Android.mk b/Android.mk
index 88be12f23a77..427b88848d46 100644
--- a/Android.mk
+++ b/Android.mk
@@ -402,6 +402,8 @@ LOCAL_SRC_FILES += \
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 f27cf03187ad..4351cbafd420 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20084,6 +20084,7 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 8f91d85d79dc..f0303a0d3b73 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -21590,6 +21590,7 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 4f1f147724b2..0ecf4bd5dbc8 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -20171,6 +20171,7 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 @@ package android.media {
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 497600212095..664c7ea02181 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -326,6 +326,12 @@ android_media_AudioSystem_newAudioSessionId(JNIEnv *env, jobject thiz)
}
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 @@ static const JNINativeMethod gMethods[] = {
{"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 65eadb6e59a7..23bb6f94a1c3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -67,7 +67,8 @@ public class AudioManager {
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 @@ public class AudioManager {
*/
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 @@ public class AudioManager {
@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 @@ public class AudioManager {
+ 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 @@ public class AudioManager {
}
}
+ //====================================================================
+ // 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 000000000000..122fad059abe
--- /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 000000000000..3382cd95163c
--- /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 28c7253f8c76..179289952e65 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -161,6 +161,12 @@ public class AudioSystem
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 16b331560d2a..464cbdb985a4 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -471,7 +471,7 @@ public class AudioTrack extends PlayerBase
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 @@ public class AudioTrack extends PlayerBase
* 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 @@ public class AudioTrack extends PlayerBase
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 @@ public class AudioTrack extends PlayerBase
// stop playing
synchronized(mPlayStateLock) {
native_stop();
+ baseStop();
mPlayState = PLAYSTATE_STOPPED;
mAvSyncHeader = null;
mAvSyncBytesRemaining = 0;
@@ -1791,11 +1793,11 @@ public class AudioTrack extends PlayerBase
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 c7931fcd297b..151b4d58fd65 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -20,13 +20,16 @@ import android.app.PendingIntent;
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 @@ interface IAudioService {
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 000000000000..3cb52161d9bf
--- /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 000000000000..32984f98264c
--- /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 78da59c35754..77a0fbde6eb1 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -626,7 +626,8 @@ public class MediaPlayer extends PlayerBase
* 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 class MediaPlayer extends PlayerBase
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
+ baseStop();
}
private native void _stop() throws IllegalStateException;
@@ -1254,6 +1256,7 @@ public class MediaPlayer extends PlayerBase
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
+ basePause();
}
private native void _pause() throws IllegalStateException;
@@ -2973,6 +2976,7 @@ public class MediaPlayer extends PlayerBase
case MEDIA_PLAYBACK_COMPLETE:
{
+ mOnCompletionInternalListener.onCompletion(mMediaPlayer);
OnCompletionListener onCompletionListener = mOnCompletionListener;
if (onCompletionListener != null)
onCompletionListener.onCompletion(mMediaPlayer);
@@ -3037,6 +3041,7 @@ public class MediaPlayer extends PlayerBase
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 @@ public class MediaPlayer extends PlayerBase
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 000000000000..4ae2125a0dff
--- /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 42f6b830126d..49c89a35a0cb 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 android.util.Log;
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 @@ import com.android.internal.app.IAppOpsService;
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 @@ public abstract class PlayerBase {
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 @@ public abstract class PlayerBase {
} 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 @@ public abstract class PlayerBase {
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 @@ public abstract class PlayerBase {
}
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 @@ public abstract class PlayerBase {
}
}
+ 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 @@ public abstract class PlayerBase {
* 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 @@ public abstract class PlayerBase {
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 @@ public abstract class PlayerBase {
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 838767ce137e..c985cbd010d4 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -151,7 +151,7 @@ public class SoundPool extends PlayerBase {
}
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 67f361441af7..788a28cfac25 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.AudioFormat;
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.SoundPool;
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 @@ public class AudioService extends IAudioService.Stub
pw.print(" mVolumePolicy="); pw.println(mVolumePolicy);
dumpAudioPolicies(pw);
+
+ mPlaybackMonitor.dump(pw);
}
private static String safeMediaVolumeStateToString(Integer state) {
@@ -6443,6 +6448,42 @@ public class AudioService extends IAudioService.Stub
}
//======================
+ // 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 000000000000..b99e4e93a95a
--- /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);
+ }
+ }
+}