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