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