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);
+}