Add a new class to group media/audio player features under one interface. Currently only used for audio-related AppsOps restrictions. In the future can group routing, audio focus handling by the framework... MediaPlayer inherits from PlayerBase, and overrides the methods that are needed to delegate the handling of the restrictions in a separate class. When AppOps restrictions change (as triggered through the callback in PlayerBase), reset the volume to its value intended by the application (when unmuting) or the framework (0 when enforcing the restriction). Bug 28069414 Change-Id: I2f38e4b9b3c029778836868cf78f4d15d7603247
194 lines
6.5 KiB
Java
194 lines
6.5 KiB
Java
/*
|
|
* 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);
|
|
}
|