Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8e48c693 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

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
parent 7519e166
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -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;
    }
+4 −4
Original line number Diff line number Diff line
@@ -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;
    }

+52 −11
Original line number Diff line number Diff line
@@ -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);
}
+24 −88
Original line number Diff line number Diff line
@@ -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) {
+28 −1
Original line number Diff line number Diff line
@@ -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);
Loading