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.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;

View File

@@ -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.</p>
*/
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<InputStream>();
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!

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