From 3c86a343dfca1b9e2e28c240dc894f60709e392c Mon Sep 17 00:00:00 2001
From: Jean-Michel Trivi
Date: Fri, 8 Apr 2016 20:47:02 -0700
Subject: [PATCH] Player superclass for handling AppOps features
Add a new class to group media/audio player features under one
interface. Currently only used for audio-related AppsOps
restrictions. In the future can group routing, audio focus
handling by the framework...
MediaPlayer inherits from PlayerBase, and overrides the methods
that are needed to delegate the handling of the restrictions
in a separate class.
When AppOps restrictions change (as triggered through the
callback in PlayerBase), reset the volume to its value intended
by the application (when unmuting) or the framework (0 when
enforcing the restriction).
Bug 28069414
Change-Id: I2f38e4b9b3c029778836868cf78f4d15d7603247
---
media/java/android/media/AudioTrack.java | 66 +++-----
media/java/android/media/MediaPlayer.java | 57 +++----
media/java/android/media/PlayerBase.java | 193 ++++++++++++++++++++++
3 files changed, 237 insertions(+), 79 deletions(-)
create mode 100644 media/java/android/media/PlayerBase.java
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 9d360db384c8c..2fa3a7233f543 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -28,7 +28,6 @@ import java.util.Collection;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityThread;
-import android.app.AppOpsManager;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
@@ -41,7 +40,6 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsService;
/**
* The AudioTrack class manages and plays a single audio resource for Java applications.
@@ -78,7 +76,8 @@ import com.android.internal.app.IAppOpsService;
*
* AudioTrack is not final and thus permits subclasses, but such use is not recommended.
*/
-public class AudioTrack implements AudioRouting
+public class AudioTrack extends PlayerBase
+ implements AudioRouting
{
//---------------------------------------------------------
// Constants
@@ -271,7 +270,6 @@ public class AudioTrack implements AudioRouting
*/
private int mStreamType = AudioManager.STREAM_MUSIC;
- private final AudioAttributes mAttributes;
/**
* The way audio is consumed by the audio sink, one of MODE_STATIC or MODE_STREAM.
*/
@@ -297,10 +295,6 @@ public class AudioTrack implements AudioRouting
* Audio session ID
*/
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
- /**
- * Reference to the app-ops service.
- */
- private final IAppOpsService mAppOps;
/**
* HW_AV_SYNC track AV Sync Header
*/
@@ -448,11 +442,9 @@ public class AudioTrack implements AudioRouting
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId)
throws IllegalArgumentException {
+ super(attributes);
// mState already == STATE_UNINITIALIZED
- if (attributes == null) {
- throw new IllegalArgumentException("Illegal null AudioAttributes");
- }
if (format == null) {
throw new IllegalArgumentException("Illegal null AudioFormat");
}
@@ -491,10 +483,6 @@ public class AudioTrack implements AudioRouting
audioBuffSizeCheck(bufferSizeInBytes);
mInitializationLooper = looper;
- IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
- mAppOps = IAppOpsService.Stub.asInterface(b);
-
- mAttributes = new AudioAttributes.Builder(attributes).build();
if (sessionId < 0) {
throw new IllegalArgumentException("Invalid audio session ID: "+sessionId);
@@ -534,9 +522,8 @@ public class AudioTrack implements AudioRouting
* OpenSLES interface is realized.
*/
/*package*/ AudioTrack(long nativeTrackInJavaObj) {
+ super(new AudioAttributes.Builder().build());
// "final"s
- mAttributes = null;
- mAppOps = null;
mNativeTrackInJavaObj = 0;
mJniData = 0;
@@ -961,12 +948,14 @@ public class AudioTrack implements AudioRouting
} catch(IllegalStateException ise) {
// don't raise an exception, we're releasing the resources.
}
+ baseRelease();
native_release();
mState = STATE_UNINITIALIZED;
}
@Override
protected void finalize() {
+ baseRelease();
native_finalize();
}
@@ -1492,21 +1481,22 @@ public class AudioTrack implements AudioRouting
*/
@Deprecated
public int setStereoVolume(float leftGain, float rightGain) {
- if (isRestricted()) {
- return SUCCESS;
- }
if (mState == STATE_UNINITIALIZED) {
return ERROR_INVALID_OPERATION;
}
- leftGain = clampGainOrLevel(leftGain);
- rightGain = clampGainOrLevel(rightGain);
-
- native_setVolume(leftGain, rightGain);
-
+ baseSetVolume(leftGain, rightGain);
return SUCCESS;
}
+ @Override
+ void playerSetVolume(float leftVolume, float rightVolume) {
+ leftVolume = clampGainOrLevel(leftVolume);
+ rightVolume = clampGainOrLevel(rightVolume);
+
+ native_setVolume(leftVolume, rightVolume);
+ }
+
/**
* Sets the specified output gain value on all channels of this track.
@@ -1728,29 +1718,13 @@ public class AudioTrack implements AudioRouting
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("play() called on uninitialized AudioTrack.");
}
- if (isRestricted()) {
- setVolume(0);
- }
+ baseStart();
synchronized(mPlayStateLock) {
native_start();
mPlayState = PLAYSTATE_PLAYING;
}
}
- private boolean isRestricted() {
- if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
- return false;
- }
- try {
- final int usage = AudioAttributes.usageForLegacyStreamType(mStreamType);
- final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, usage,
- Process.myUid(), ActivityThread.currentPackageName());
- return mode != AppOpsManager.MODE_ALLOWED;
- } catch (RemoteException e) {
- return false;
- }
- }
-
/**
* Stops playing the audio data.
* When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing
@@ -2375,12 +2349,14 @@ public class AudioTrack implements AudioRouting
* {@link #ERROR_INVALID_OPERATION}, {@link #ERROR}
*/
public int setAuxEffectSendLevel(float level) {
- if (isRestricted()) {
- return SUCCESS;
- }
if (mState == STATE_UNINITIALIZED) {
return ERROR_INVALID_OPERATION;
}
+ return baseSetAuxEffectSendLevel(level);
+ }
+
+ @Override
+ int playerSetAuxEffectSendLevel(float level) {
level = clampGainOrLevel(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 55d5f42fb78c0..cee7d60a9f368 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -20,7 +20,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
-import android.app.AppOpsManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
@@ -34,8 +33,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.system.ErrnoException;
@@ -56,7 +53,6 @@ import android.media.SubtitleData;
import android.media.SubtitleTrack.RenderingWidget;
import android.media.SyncParams;
-import com.android.internal.app.IAppOpsService;
import com.android.internal.util.Preconditions;
import libcore.io.IoBridge;
@@ -66,7 +62,6 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.Runnable;
@@ -74,7 +69,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetSocketAddress;
import java.util.BitSet;
-import java.util.HashSet;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
@@ -561,7 +555,8 @@ import java.lang.ref.WeakReference;
* thread by default has a Looper running).
*
*/
-public class MediaPlayer implements SubtitleController.Listener
+public class MediaPlayer extends PlayerBase
+ implements SubtitleController.Listener
{
/**
Constant to retrieve only the new metadata since the last
@@ -615,7 +610,6 @@ public class MediaPlayer implements SubtitleController.Listener
private PowerManager.WakeLock mWakeLock = null;
private boolean mScreenOnWhilePlaying;
private boolean mStayAwake;
- private final IAppOpsService mAppOps;
private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
private int mUsage = -1;
private boolean mBypassInterruptionPolicy;
@@ -628,6 +622,7 @@ public class MediaPlayer implements SubtitleController.Listener
* result in an exception.
*/
public MediaPlayer() {
+ super(new AudioAttributes.Builder().build());
Looper looper;
if ((looper = Looper.myLooper()) != null) {
@@ -640,8 +635,6 @@ public class MediaPlayer implements SubtitleController.Listener
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector();
- IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
- mAppOps = IAppOpsService.Stub.asInterface(b);
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
@@ -1211,29 +1204,13 @@ public class MediaPlayer implements SubtitleController.Listener
* @throws IllegalStateException if it is called in an invalid state
*/
public void start() throws IllegalStateException {
- if (isRestricted()) {
- _setVolume(0, 0);
- }
+ baseStart();
stayAwake(true);
_start();
}
private native void _start() throws IllegalStateException;
- private boolean isRestricted() {
- if (mBypassInterruptionPolicy) {
- return false;
- }
- try {
- final int usage = mUsage != -1 ? mUsage
- : AudioAttributes.usageForLegacyStreamType(getAudioStreamType());
- final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, usage,
- Process.myUid(), ActivityThread.currentPackageName());
- return mode != AppOpsManager.MODE_ALLOWED;
- } catch (RemoteException e) {
- return false;
- }
- }
private int getAudioStreamType() {
if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
@@ -1687,6 +1664,7 @@ public class MediaPlayer implements SubtitleController.Listener
* at the same time.
*/
public void release() {
+ baseRelease();
stayAwake(false);
updateSurfaceScreenOn();
mOnPreparedListener = null;
@@ -1756,6 +1734,8 @@ public class MediaPlayer implements SubtitleController.Listener
* @see android.media.AudioManager
*/
public void setAudioStreamType(int streamtype) {
+ baseUpdateAudioAttributes(
+ new AudioAttributes.Builder().setInternalLegacyStreamType(streamtype).build());
_setAudioStreamType(streamtype);
mStreamType = streamtype;
}
@@ -1785,6 +1765,7 @@ public class MediaPlayer implements SubtitleController.Listener
final String msg = "Cannot set AudioAttributes to null";
throw new IllegalArgumentException(msg);
}
+ baseUpdateAudioAttributes(attributes);
mUsage = attributes.getUsage();
mBypassInterruptionPolicy = (attributes.getAllFlags()
& AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0;
@@ -1826,9 +1807,11 @@ public class MediaPlayer implements SubtitleController.Listener
* to be set independently.
*/
public void setVolume(float leftVolume, float rightVolume) {
- if (isRestricted()) {
- return;
- }
+ baseSetVolume(leftVolume, rightVolume);
+ }
+
+ @Override
+ void playerSetVolume(float leftVolume, float rightVolume) {
_setVolume(leftVolume, rightVolume);
}
@@ -1898,10 +1881,13 @@ public class MediaPlayer implements SubtitleController.Listener
* @param level send level scalar
*/
public void setAuxEffectSendLevel(float level) {
- if (isRestricted()) {
- return;
- }
+ baseSetAuxEffectSendLevel(level);
+ }
+
+ @Override
+ int playerSetAuxEffectSendLevel(float level) {
_setAuxEffectSendLevel(level);
+ return AudioSystem.SUCCESS;
}
private native void _setAuxEffectSendLevel(float level);
@@ -2795,7 +2781,10 @@ public class MediaPlayer implements SubtitleController.Listener
private native final int native_setRetransmitEndpoint(String addrString, int port);
@Override
- protected void finalize() { native_finalize(); }
+ protected void finalize() {
+ baseRelease();
+ native_finalize();
+ }
/* Do not change these values without updating their counterparts
* in include/media/mediaplayer.h!
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
new file mode 100644
index 0000000000000..0f7dc9a819c7f
--- /dev/null
+++ b/media/java/android/media/PlayerBase.java
@@ -0,0 +1,193 @@
+/*
+ * 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 java.lang.IllegalArgumentException;
+
+import android.annotation.NonNull;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+
+/**
+ * Class to encapsulate a number of common player operations:
+ * - AppOps for OP_PLAY_AUDIO
+ * - more to come (routing, transport control)
+ * @hide
+ */
+public abstract class PlayerBase {
+
+ // parameters of the player that affect AppOps
+ protected AudioAttributes mAttributes;
+ protected float mLeftVolume = 1.0f;
+ protected float mRightVolume = 1.0f;
+ protected float mAuxEffectSendLevel = 0.0f;
+
+ // for AppOps
+ private final IAppOpsService mAppOps;
+ private final IAppOpsCallback mAppOpsCallback;
+ 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
+ */
+ PlayerBase(@NonNull AudioAttributes attr) {
+ if (attr == null) {
+ throw new IllegalArgumentException("Illegal null AudioAttributes");
+ }
+ mAttributes = attr;
+ IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+ mAppOps = IAppOpsService.Stub.asInterface(b);
+ // initialize mHasAppOpsPlayAudio
+ updateAppOpsPlayAudio_sync();
+ // 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 (mAppOpsLock) {
+ if (op == AppOpsManager.OP_PLAY_AUDIO) {
+ updateAppOpsPlayAudio_sync();
+ }
+ }
+ }
+ };
+ try {
+ mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
+ ActivityThread.currentPackageName(), mAppOpsCallback);
+ } catch (RemoteException e) {
+ mHasAppOpsPlayAudio = false;
+ }
+ }
+
+
+ /**
+ * To be called whenever the audio attributes of the player change
+ * @param attr non-null audio attributes
+ */
+ void baseUpdateAudioAttributes(@NonNull AudioAttributes attr) {
+ if (attr == null) {
+ throw new IllegalArgumentException("Illegal null AudioAttributes");
+ }
+ synchronized (mAppOpsLock) {
+ mAttributes = attr;
+ updateAppOpsPlayAudio_sync();
+ }
+ }
+
+ void baseStart() {
+ synchronized (mAppOpsLock) {
+ if (isRestricted_sync()) {
+ playerSetVolume(0, 0);
+ }
+ }
+ }
+
+ void baseSetVolume(float leftVolume, float rightVolume) {
+ synchronized (mAppOpsLock) {
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ if (isRestricted_sync()) {
+ return;
+ }
+ }
+ playerSetVolume(leftVolume, rightVolume);
+ }
+
+ int baseSetAuxEffectSendLevel(float level) {
+ synchronized (mAppOpsLock) {
+ mAuxEffectSendLevel = level;
+ if (isRestricted_sync()) {
+ return AudioSystem.SUCCESS;
+ }
+ }
+ return playerSetAuxEffectSendLevel(level);
+ }
+
+ /**
+ * To be called from a subclass release or finalize method.
+ * Releases AppOps related resources.
+ */
+ void baseRelease() {
+ try {
+ mAppOps.stopWatchingMode(mAppOpsCallback);
+ } catch (RemoteException e) {
+ // nothing to do here, the object is supposed to be released anyway
+ }
+ }
+
+ /**
+ * To be called whenever a condition that might affect audibility of this player is updated.
+ * Must be called synchronized on mAppOpsLock.
+ */
+ void updateAppOpsPlayAudio_sync() {
+ boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
+ 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;
+ }
+
+ // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
+ // volume used by the player
+ try {
+ if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
+ if (mHasAppOpsPlayAudio) {
+ playerSetVolume(mLeftVolume, mRightVolume);
+ playerSetAuxEffectSendLevel(mAuxEffectSendLevel);
+ } else {
+ playerSetVolume(0.0f, 0.0f);
+ playerSetAuxEffectSendLevel(0.0f);
+ }
+ }
+ } catch (Exception e) {
+ // failing silently, player might not be in right state
+ }
+ }
+
+
+ /**
+ * 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
+ * to call this method should be removed, and this method could become private.
+ * FIXME can this method be private so subclasses don't have to worry about when to check
+ * the restrictions.
+ * @return
+ */
+ boolean isRestricted_sync() {
+ if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
+ return false;
+ }
+ return !mHasAppOpsPlayAudio;
+ }
+
+ // Abstract methods a subclass needs to implement
+ abstract void playerSetVolume(float leftVolume, float rightVolume);
+ abstract int playerSetAuxEffectSendLevel(float level);
+}