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
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.</p>
|
||||
*/
|
||||
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<SoundPool>(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) {
|
||||
|
||||
@@ -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<SoundChannel*>::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);
|
||||
|
||||
@@ -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>& 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user