From 8e48c6939bd81dcbc596f0a4b11bfaea73aed00b Mon Sep 17 00:00:00 2001
From: Jean-Michel Trivi
Date: Wed, 19 Oct 2016 11:52:08 -0700
Subject: [PATCH] Refactor SoundPool for appOps handling through PlayerBase
Modified the signature of the abstract volume methods so
it is clear at the subclass level whether the volume
command is for a mute or a volume control.
Changed the implementations in the subclasses
accordingly.
Removed appOps handling inside SoundPool and made it
inherit from PlayerBase.
Moved handling of the camera sound restriction from
SoundPool to PlayerBase.
Added support in SoundPool native implementation for
muting, as each player has its own volume.
Test: play a long file with SoundPool and enter DnD mode
Bug: 30955183
Bug: 28249605
Change-Id: I0fcd7480f9a455c06aa4f7092486f5c65bc9d7db
---
media/java/android/media/AudioTrack.java | 10 +-
media/java/android/media/MediaPlayer.java | 8 +-
media/java/android/media/PlayerBase.java | 63 ++++++++--
media/java/android/media/SoundPool.java | 112 ++++--------------
media/jni/soundpool/SoundPool.cpp | 29 ++++-
media/jni/soundpool/SoundPool.h | 6 +-
.../jni/soundpool/android_media_SoundPool.cpp | 13 ++
7 files changed, 131 insertions(+), 110 deletions(-)
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index b5e3af07b7e60..43fb4b9d87a2c 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1513,9 +1513,9 @@ public class AudioTrack extends PlayerBase
}
@Override
- void playerSetVolume(float leftVolume, float rightVolume) {
- leftVolume = clampGainOrLevel(leftVolume);
- rightVolume = clampGainOrLevel(rightVolume);
+ void playerSetVolume(boolean muting, float leftVolume, float rightVolume) {
+ leftVolume = clampGainOrLevel(muting ? 0.0f : leftVolume);
+ rightVolume = clampGainOrLevel(muting ? 0.0f : rightVolume);
native_setVolume(leftVolume, rightVolume);
}
@@ -2393,8 +2393,8 @@ public class AudioTrack extends PlayerBase
}
@Override
- int playerSetAuxEffectSendLevel(float level) {
- level = clampGainOrLevel(level);
+ int playerSetAuxEffectSendLevel(boolean muting, float level) {
+ level = clampGainOrLevel(muting ? 0.0f : level);
int err = native_setAuxEffectSendLevel(level);
return err == 0 ? SUCCESS : ERROR;
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 82cf965a75fb0..36ad90b6026cd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1826,8 +1826,8 @@ public class MediaPlayer extends PlayerBase
}
@Override
- void playerSetVolume(float leftVolume, float rightVolume) {
- _setVolume(leftVolume, rightVolume);
+ void playerSetVolume(boolean muting, float leftVolume, float rightVolume) {
+ _setVolume(muting ? 0.0f : leftVolume, muting ? 0.0f : rightVolume);
}
private native void _setVolume(float leftVolume, float rightVolume);
@@ -1900,8 +1900,8 @@ public class MediaPlayer extends PlayerBase
}
@Override
- int playerSetAuxEffectSendLevel(float level) {
- _setAuxEffectSendLevel(level);
+ int playerSetAuxEffectSendLevel(boolean muting, float level) {
+ _setAuxEffectSendLevel(muting ? 0.0f : level);
return AudioSystem.SUCCESS;
}
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index b262d97cfb8e1..690a553849e32 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -39,6 +39,11 @@ import com.android.internal.app.IAppOpsService;
*/
public abstract class PlayerBase {
+ private final static String TAG = "PlayerBase";
+ private static IAudioService sService; //lazy initialization, use getService()
+ /** Debug app ops */
+ protected static final boolean DEBUG_APP_OPS = Log.isLoggable(TAG + ".AO", Log.DEBUG);
+
// parameters of the player that affect AppOps
protected AudioAttributes mAttributes;
protected float mLeftVolume = 1.0f;
@@ -51,7 +56,6 @@ public abstract class PlayerBase {
private boolean mHasAppOpsPlayAudio = true;
private final Object mAppOpsLock = new Object();
-
/**
* Constructor. Must be given audio attributes, as they are required for AppOps.
* @param attr non-null audio attributes
@@ -101,7 +105,7 @@ public abstract class PlayerBase {
void baseStart() {
synchronized (mAppOpsLock) {
if (isRestricted_sync()) {
- playerSetVolume(0, 0);
+ playerSetVolume(true/*muting*/,0, 0);
}
}
}
@@ -114,7 +118,7 @@ public abstract class PlayerBase {
return;
}
}
- playerSetVolume(leftVolume, rightVolume);
+ playerSetVolume(false/*muting*/,leftVolume, rightVolume);
}
int baseSetAuxEffectSendLevel(float level) {
@@ -124,7 +128,7 @@ public abstract class PlayerBase {
return AudioSystem.SUCCESS;
}
}
- return playerSetAuxEffectSendLevel(level);
+ return playerSetAuxEffectSendLevel(false/*muting*/, level);
}
/**
@@ -159,11 +163,18 @@ public abstract class PlayerBase {
try {
if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
if (mHasAppOpsPlayAudio) {
- playerSetVolume(mLeftVolume, mRightVolume);
- playerSetAuxEffectSendLevel(mAuxEffectSendLevel);
+ if (DEBUG_APP_OPS) {
+ Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
+ + "/" + mRightVolume);
+ }
+ playerSetVolume(false/*muting*/, mLeftVolume, mRightVolume);
+ playerSetAuxEffectSendLevel(false/*muting*/, mAuxEffectSendLevel);
} else {
- playerSetVolume(0.0f, 0.0f);
- playerSetAuxEffectSendLevel(0.0f);
+ if (DEBUG_APP_OPS) {
+ Log.v(TAG, "updateAppOpsPlayAudio: muting player");
+ }
+ playerSetVolume(true/*muting*/, 0.0f, 0.0f);
+ playerSetAuxEffectSendLevel(true/*muting*/, 0.0f);
}
}
} catch (Exception e) {
@@ -171,7 +182,6 @@ public abstract class PlayerBase {
}
}
-
/**
* To be called by the subclass whenever an operation is potentially restricted.
* As the media player-common behavior are incorporated into this class, the subclass's need
@@ -189,10 +199,41 @@ public abstract class PlayerBase {
if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
return false;
}
+ // check force audibility flag and camera restriction
+ if (((mAttributes.getAllFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED) != 0)
+ && (mAttributes.getUsage() == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)) {
+ boolean cameraSoundForced = false;
+ try {
+ cameraSoundForced = getService().isCameraSoundForced();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot access AudioService in isRestricted_sync()");
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Null AudioService in isRestricted_sync()");
+ }
+ if (cameraSoundForced) {
+ return false;
+ }
+ }
return true;
}
+ private static IAudioService getService()
+ {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ sService = IAudioService.Stub.asInterface(b);
+ return sService;
+ }
+
// Abstract methods a subclass needs to implement
- abstract void playerSetVolume(float leftVolume, float rightVolume);
- abstract int playerSetAuxEffectSendLevel(float level);
+ /**
+ * Abstract method for the subclass behavior's for volume and muting commands
+ * @param muting if true, the player is to be muted, and the volume values can be ignored
+ * @param leftVolume the left volume to use if muting is false
+ * @param rightVolume the right volume to use if muting is false
+ */
+ abstract void playerSetVolume(boolean muting, float leftVolume, float rightVolume);
+ abstract int playerSetAuxEffectSendLevel(boolean muting, float level);
}
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 9fafda48d6521..b429e22135bef 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -35,9 +35,6 @@ import android.os.ServiceManager;
import android.util.AndroidRuntimeException;
import android.util.Log;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
-
/**
* The SoundPool class manages and plays audio resources for applications.
@@ -111,7 +108,7 @@ import com.android.internal.app.IAppOpsService;
* another level, a new SoundPool is created, sounds are loaded, and play
* resumes.
*/
-public class SoundPool {
+public class SoundPool extends PlayerBase {
static { System.loadLibrary("soundpool"); }
// SoundPool messages
@@ -130,10 +127,6 @@ public class SoundPool {
private final Object mLock;
private final AudioAttributes mAttributes;
- private final IAppOpsService mAppOps;
- private final IAppOpsCallback mAppOpsCallback;
-
- private static IAudioService sService;
/**
* Constructor. Constructs a SoundPool object with the following
@@ -156,32 +149,14 @@ public class SoundPool {
}
private SoundPool(int maxStreams, AudioAttributes attributes) {
+ super(attributes);
+
// do native setup
if (native_setup(new WeakReference(this), maxStreams, attributes) != 0) {
throw new RuntimeException("Native setup failed");
}
mLock = new Object();
mAttributes = attributes;
- IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
- mAppOps = IAppOpsService.Stub.asInterface(b);
- // initialize mHasAppOpsPlayAudio
- updateAppOpsPlayAudio();
- // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
- mAppOpsCallback = new IAppOpsCallback.Stub() {
- public void opChanged(int op, int uid, String packageName) {
- synchronized (mLock) {
- if (op == AppOpsManager.OP_PLAY_AUDIO) {
- updateAppOpsPlayAudio();
- }
- }
- }
- };
- try {
- mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
- ActivityThread.currentPackageName(), mAppOpsCallback);
- } catch (RemoteException e) {
- mHasAppOpsPlayAudio = false;
- }
}
/**
@@ -192,11 +167,7 @@ public class SoundPool {
* should be set to null.
*/
public final void release() {
- try {
- mAppOps.stopWatchingMode(mAppOpsCallback);
- } catch (RemoteException e) {
- // nothing to do here, the SoundPool is being released anyway
- }
+ baseRelease();
native_release();
}
@@ -333,9 +304,7 @@ public class SoundPool {
*/
public final int play(int soundID, float leftVolume, float rightVolume,
int priority, int loop, float rate) {
- if (isRestricted()) {
- leftVolume = rightVolume = 0;
- }
+ baseStart();
return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
}
@@ -408,12 +377,26 @@ public class SoundPool {
* @param rightVolume right volume value (range = 0.0 to 1.0)
*/
public final void setVolume(int streamID, float leftVolume, float rightVolume) {
- if (isRestricted()) {
- return;
- }
+ // unlike other subclasses of PlayerBase, we are not calling
+ // baseSetVolume(leftVolume, rightVolume) as we need to keep track of each
+ // volume separately for each player, so we still send the command, but
+ // handle mute/unmute separately through playerSetVolume()
_setVolume(streamID, leftVolume, rightVolume);
}
+
+ @Override
+ void playerSetVolume(boolean muting, float leftVolume, float rightVolume) {
+ // not used here to control the player volume directly, but used to mute/unmute
+ _mute(muting);
+ }
+
+ @Override
+ int playerSetAuxEffectSendLevel(boolean muting, float level) {
+ // no aux send functionality so no-op
+ return AudioSystem.SUCCESS;
+ }
+
/**
* Similar, except set volume of all channels to same value.
* @hide
@@ -494,55 +477,6 @@ public class SoundPool {
}
}
- private static IAudioService getService()
- {
- if (sService != null) {
- return sService;
- }
- IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
- sService = IAudioService.Stub.asInterface(b);
- return sService;
- }
-
- private boolean isRestricted() {
- // check app ops
- if (mHasAppOpsPlayAudio) {
- return false;
- }
- // check bypass flag
- if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
- return false;
- }
- // check force audibility flag and camera restriction
- if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED) != 0) {
-// FIXME: should also check usage when set properly by camera app
-// && (mAttributes.getUsage() == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- boolean cameraSoundForced = false;
- try {
- cameraSoundForced = getService().isCameraSoundForced();
- } catch (RemoteException e) {
- Log.e(TAG, "Cannot access AudioService in isRestricted()");
- } catch (NullPointerException e) {
- Log.e(TAG, "Null AudioService in isRestricted()");
- }
- if (cameraSoundForced) {
- return false;
- }
- }
- return true;
- }
-
- private void updateAppOpsPlayAudio() {
- try {
- final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
- mAttributes.getUsage(),
- Process.myUid(), ActivityThread.currentPackageName());
- mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED);
- } catch (RemoteException e) {
- mHasAppOpsPlayAudio = false;
- }
- }
-
private native final int _load(FileDescriptor fd, long offset, long length, int priority);
private native final int native_setup(Object weakRef, int maxStreams,
@@ -553,6 +487,8 @@ public class SoundPool {
private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
+ private native final void _mute(boolean muting);
+
// post event from native code to message handler
@SuppressWarnings("unchecked")
private static void postEventFromNative(Object ref, int msg, int arg1, int arg2, Object obj) {
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index d2dc44045c382..87092d0a5bd1e 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -60,6 +60,7 @@ SoundPool::SoundPool(int maxChannels, const audio_attributes_t* pAttributes)
ALOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);
mQuit = false;
+ mMuted = false;
mDecodeThread = 0;
memcpy(&mAttributes, pAttributes, sizeof(audio_attributes_t));
mAllocated = 0;
@@ -366,6 +367,19 @@ void SoundPool::resume(int channelID)
}
}
+void SoundPool::mute(bool muting)
+{
+ ALOGV("mute(%d)", muting);
+ Mutex::Autolock lock(&mLock);
+ mMuted = muting;
+ if (!mChannels.empty()) {
+ for (List::iterator iter = mChannels.begin();
+ iter != mChannels.end(); ++iter) {
+ (*iter)->mute(muting);
+ }
+ }
+}
+
void SoundPool::autoResume()
{
ALOGV("autoResume()");
@@ -1032,7 +1046,7 @@ void SoundChannel::setVolume_l(float leftVolume, float rightVolume)
{
mLeftVolume = leftVolume;
mRightVolume = rightVolume;
- if (mAudioTrack != NULL)
+ if (mAudioTrack != NULL && !mMuted)
mAudioTrack->setVolume(leftVolume, rightVolume);
}
@@ -1042,6 +1056,19 @@ void SoundChannel::setVolume(float leftVolume, float rightVolume)
setVolume_l(leftVolume, rightVolume);
}
+void SoundChannel::mute(bool muting)
+{
+ Mutex::Autolock lock(&mLock);
+ mMuted = muting;
+ if (mAudioTrack != NULL) {
+ if (mMuted) {
+ mAudioTrack->setVolume(0.0f, 0.0f);
+ } else {
+ mAudioTrack->setVolume(mLeftVolume, mRightVolume);
+ }
+ }
+}
+
void SoundChannel::setLoop(int loop)
{
Mutex::Autolock lock(&mLock);
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index aff101f06748b..5c48a907c832b 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -114,13 +114,14 @@ class SoundChannel : public SoundEvent {
public:
enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING };
SoundChannel() : mState(IDLE), mNumChannels(1),
- mPos(0), mToggle(0), mAutoPaused(false) {}
+ mPos(0), mToggle(0), mAutoPaused(false), mMuted(false) {}
~SoundChannel();
void init(SoundPool* soundPool);
void play(const sp& sample, int channelID, float leftVolume, float rightVolume,
int priority, int loop, float rate);
void setVolume_l(float leftVolume, float rightVolume);
void setVolume(float leftVolume, float rightVolume);
+ void mute(bool muting);
void stop_l();
void stop();
void pause();
@@ -154,6 +155,7 @@ private:
unsigned long mToggle;
bool mAutoPaused;
int mPrevSampleID;
+ bool mMuted;
};
// application object for managing a pool of sounds
@@ -168,6 +170,7 @@ public:
int play(int sampleID, float leftVolume, float rightVolume, int priority,
int loop, float rate);
void pause(int channelID);
+ void mute(bool muting);
void autoPause();
void resume(int channelID);
void autoResume();
@@ -222,6 +225,7 @@ private:
int mNextSampleID;
int mNextChannelID;
bool mQuit;
+ bool mMuted;
// callback
Mutex mCallbackLock;
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index ab3e3408bcacf..9d0c1f84fada5 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -131,6 +131,15 @@ android_media_SoundPool_setVolume(JNIEnv *env, jobject thiz, jint channelID,
ap->setVolume(channelID, (float) leftVolume, (float) rightVolume);
}
+static void
+android_media_SoundPool_mute(JNIEnv *env, jobject thiz, jboolean muting)
+{
+ ALOGV("android_media_SoundPool_mute(%d)", muting);
+ SoundPool *ap = MusterSoundPool(env, thiz);
+ if (ap == NULL) return;
+ ap->mute(muting == JNI_TRUE);
+}
+
static void
android_media_SoundPool_setPriority(JNIEnv *env, jobject thiz, jint channelID,
jint priority)
@@ -270,6 +279,10 @@ static JNINativeMethod gMethods[] = {
"(IFF)V",
(void *)android_media_SoundPool_setVolume
},
+ { "_mute",
+ "(Z)V",
+ (void *)android_media_SoundPool_mute
+ },
{ "setPriority",
"(II)V",
(void *)android_media_SoundPool_setPriority