Merge "Player superclass for handling AppOps features" into nyc-dev
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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!
|
||||
|
||||
193
media/java/android/media/PlayerBase.java
Normal file
193
media/java/android/media/PlayerBase.java
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user