Merge "Player superclass for handling AppOps features" into nyc-dev

This commit is contained in:
Eric Laurent
2016-04-15 21:05:24 +00:00
committed by Android (Google) Code Review
3 changed files with 237 additions and 79 deletions

View File

@@ -28,7 +28,6 @@ import java.util.Collection;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
@@ -41,7 +40,6 @@ import android.util.ArrayMap;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.GuardedBy; 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. * 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. * 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 // Constants
@@ -271,7 +270,6 @@ public class AudioTrack implements AudioRouting
*/ */
private int mStreamType = AudioManager.STREAM_MUSIC; 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. * 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 * Audio session ID
*/ */
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE; 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 * HW_AV_SYNC track AV Sync Header
*/ */
@@ -448,11 +442,9 @@ public class AudioTrack implements AudioRouting
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId) int mode, int sessionId)
throws IllegalArgumentException { throws IllegalArgumentException {
super(attributes);
// mState already == STATE_UNINITIALIZED // mState already == STATE_UNINITIALIZED
if (attributes == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes");
}
if (format == null) { if (format == null) {
throw new IllegalArgumentException("Illegal null AudioFormat"); throw new IllegalArgumentException("Illegal null AudioFormat");
} }
@@ -491,10 +483,6 @@ public class AudioTrack implements AudioRouting
audioBuffSizeCheck(bufferSizeInBytes); audioBuffSizeCheck(bufferSizeInBytes);
mInitializationLooper = looper; mInitializationLooper = looper;
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
mAttributes = new AudioAttributes.Builder(attributes).build();
if (sessionId < 0) { if (sessionId < 0) {
throw new IllegalArgumentException("Invalid audio session ID: "+sessionId); throw new IllegalArgumentException("Invalid audio session ID: "+sessionId);
@@ -534,9 +522,8 @@ public class AudioTrack implements AudioRouting
* OpenSLES interface is realized. * OpenSLES interface is realized.
*/ */
/*package*/ AudioTrack(long nativeTrackInJavaObj) { /*package*/ AudioTrack(long nativeTrackInJavaObj) {
super(new AudioAttributes.Builder().build());
// "final"s // "final"s
mAttributes = null;
mAppOps = null;
mNativeTrackInJavaObj = 0; mNativeTrackInJavaObj = 0;
mJniData = 0; mJniData = 0;
@@ -961,12 +948,14 @@ public class AudioTrack implements AudioRouting
} catch(IllegalStateException ise) { } catch(IllegalStateException ise) {
// don't raise an exception, we're releasing the resources. // don't raise an exception, we're releasing the resources.
} }
baseRelease();
native_release(); native_release();
mState = STATE_UNINITIALIZED; mState = STATE_UNINITIALIZED;
} }
@Override @Override
protected void finalize() { protected void finalize() {
baseRelease();
native_finalize(); native_finalize();
} }
@@ -1492,21 +1481,22 @@ public class AudioTrack implements AudioRouting
*/ */
@Deprecated @Deprecated
public int setStereoVolume(float leftGain, float rightGain) { public int setStereoVolume(float leftGain, float rightGain) {
if (isRestricted()) {
return SUCCESS;
}
if (mState == STATE_UNINITIALIZED) { if (mState == STATE_UNINITIALIZED) {
return ERROR_INVALID_OPERATION; return ERROR_INVALID_OPERATION;
} }
leftGain = clampGainOrLevel(leftGain); baseSetVolume(leftGain, rightGain);
rightGain = clampGainOrLevel(rightGain);
native_setVolume(leftGain, rightGain);
return SUCCESS; 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. * 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) { if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("play() called on uninitialized AudioTrack."); throw new IllegalStateException("play() called on uninitialized AudioTrack.");
} }
if (isRestricted()) { baseStart();
setVolume(0);
}
synchronized(mPlayStateLock) { synchronized(mPlayStateLock) {
native_start(); native_start();
mPlayState = PLAYSTATE_PLAYING; 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. * Stops playing the audio data.
* When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing * 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} * {@link #ERROR_INVALID_OPERATION}, {@link #ERROR}
*/ */
public int setAuxEffectSendLevel(float level) { public int setAuxEffectSendLevel(float level) {
if (isRestricted()) {
return SUCCESS;
}
if (mState == STATE_UNINITIALIZED) { if (mState == STATE_UNINITIALIZED) {
return ERROR_INVALID_OPERATION; return ERROR_INVALID_OPERATION;
} }
return baseSetAuxEffectSendLevel(level);
}
@Override
int playerSetAuxEffectSendLevel(float level) {
level = clampGainOrLevel(level); level = clampGainOrLevel(level);
int err = native_setAuxEffectSendLevel(level); int err = native_setAuxEffectSendLevel(level);
return err == 0 ? SUCCESS : ERROR; return err == 0 ? SUCCESS : ERROR;

View File

@@ -20,7 +20,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
@@ -34,8 +33,6 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.Process; import android.os.Process;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.provider.Settings; import android.provider.Settings;
import android.system.ErrnoException; import android.system.ErrnoException;
@@ -56,7 +53,6 @@ import android.media.SubtitleData;
import android.media.SubtitleTrack.RenderingWidget; import android.media.SubtitleTrack.RenderingWidget;
import android.media.SyncParams; import android.media.SyncParams;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import libcore.io.IoBridge; import libcore.io.IoBridge;
@@ -66,7 +62,6 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.Runnable; import java.lang.Runnable;
@@ -74,7 +69,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.BitSet; import java.util.BitSet;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.Set; import java.util.Set;
@@ -561,7 +555,8 @@ import java.lang.ref.WeakReference;
* thread by default has a Looper running). * 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 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 PowerManager.WakeLock mWakeLock = null;
private boolean mScreenOnWhilePlaying; private boolean mScreenOnWhilePlaying;
private boolean mStayAwake; private boolean mStayAwake;
private final IAppOpsService mAppOps;
private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
private int mUsage = -1; private int mUsage = -1;
private boolean mBypassInterruptionPolicy; private boolean mBypassInterruptionPolicy;
@@ -628,6 +622,7 @@ public class MediaPlayer implements SubtitleController.Listener
* result in an exception.</p> * result in an exception.</p>
*/ */
public MediaPlayer() { public MediaPlayer() {
super(new AudioAttributes.Builder().build());
Looper looper; Looper looper;
if ((looper = Looper.myLooper()) != null) { if ((looper = Looper.myLooper()) != null) {
@@ -640,8 +635,6 @@ public class MediaPlayer implements SubtitleController.Listener
mTimeProvider = new TimeProvider(this); mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>(); mOpenSubtitleSources = new Vector<InputStream>();
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
/* Native setup requires a weak reference to our object. /* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++. * 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 * @throws IllegalStateException if it is called in an invalid state
*/ */
public void start() throws IllegalStateException { public void start() throws IllegalStateException {
if (isRestricted()) { baseStart();
_setVolume(0, 0);
}
stayAwake(true); stayAwake(true);
_start(); _start();
} }
private native void _start() throws IllegalStateException; 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() { private int getAudioStreamType() {
if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
@@ -1687,6 +1664,7 @@ public class MediaPlayer implements SubtitleController.Listener
* at the same time. * at the same time.
*/ */
public void release() { public void release() {
baseRelease();
stayAwake(false); stayAwake(false);
updateSurfaceScreenOn(); updateSurfaceScreenOn();
mOnPreparedListener = null; mOnPreparedListener = null;
@@ -1756,6 +1734,8 @@ public class MediaPlayer implements SubtitleController.Listener
* @see android.media.AudioManager * @see android.media.AudioManager
*/ */
public void setAudioStreamType(int streamtype) { public void setAudioStreamType(int streamtype) {
baseUpdateAudioAttributes(
new AudioAttributes.Builder().setInternalLegacyStreamType(streamtype).build());
_setAudioStreamType(streamtype); _setAudioStreamType(streamtype);
mStreamType = streamtype; mStreamType = streamtype;
} }
@@ -1785,6 +1765,7 @@ public class MediaPlayer implements SubtitleController.Listener
final String msg = "Cannot set AudioAttributes to null"; final String msg = "Cannot set AudioAttributes to null";
throw new IllegalArgumentException(msg); throw new IllegalArgumentException(msg);
} }
baseUpdateAudioAttributes(attributes);
mUsage = attributes.getUsage(); mUsage = attributes.getUsage();
mBypassInterruptionPolicy = (attributes.getAllFlags() mBypassInterruptionPolicy = (attributes.getAllFlags()
& AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0; & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0;
@@ -1826,9 +1807,11 @@ public class MediaPlayer implements SubtitleController.Listener
* to be set independently. * to be set independently.
*/ */
public void setVolume(float leftVolume, float rightVolume) { public void setVolume(float leftVolume, float rightVolume) {
if (isRestricted()) { baseSetVolume(leftVolume, rightVolume);
return; }
}
@Override
void playerSetVolume(float leftVolume, float rightVolume) {
_setVolume(leftVolume, rightVolume); _setVolume(leftVolume, rightVolume);
} }
@@ -1898,10 +1881,13 @@ public class MediaPlayer implements SubtitleController.Listener
* @param level send level scalar * @param level send level scalar
*/ */
public void setAuxEffectSendLevel(float level) { public void setAuxEffectSendLevel(float level) {
if (isRestricted()) { baseSetAuxEffectSendLevel(level);
return; }
}
@Override
int playerSetAuxEffectSendLevel(float level) {
_setAuxEffectSendLevel(level); _setAuxEffectSendLevel(level);
return AudioSystem.SUCCESS;
} }
private native void _setAuxEffectSendLevel(float level); 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); private native final int native_setRetransmitEndpoint(String addrString, int port);
@Override @Override
protected void finalize() { native_finalize(); } protected void finalize() {
baseRelease();
native_finalize();
}
/* Do not change these values without updating their counterparts /* Do not change these values without updating their counterparts
* in include/media/mediaplayer.h! * in include/media/mediaplayer.h!

View File

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