/* * Copyright (C) 2013 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 com.android.server.audio; import android.annotation.NonNull; import android.media.AudioAttributes; import android.media.AudioFocusInfo; import android.media.AudioManager; import android.media.IAudioFocusDispatcher; import android.os.IBinder; import android.util.Log; import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler; import java.io.PrintWriter; /** * @hide * Class to handle all the information about a user of audio focus. The lifecycle of each * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus * stack to its release. */ public class FocusRequester { // on purpose not using this classe's name, as it will only be used from MediaFocusControl private static final String TAG = "MediaFocusControl"; private static final boolean DEBUG = false; private AudioFocusDeathHandler mDeathHandler; private final IAudioFocusDispatcher mFocusDispatcher; // may be null private final IBinder mSourceRef; private final String mClientId; private final String mPackageName; private final int mCallingUid; private final MediaFocusControl mFocusController; // never null /** * the audio focus gain request that caused the addition of this object in the focus stack. */ private final int mFocusGainRequest; /** * the flags associated with the gain request that qualify the type of grant (e.g. accepting * delay vs grant must be immediate) */ private final int mGrantFlags; /** * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if * it never lost focus. */ private int mFocusLossReceived; /** * the audio attributes associated with the focus request */ private final AudioAttributes mAttributes; /** * Class constructor * @param aa * @param focusRequest * @param grantFlags * @param afl * @param source * @param id * @param hdlr * @param pn * @param uid * @param ctlr cannot be null */ FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags, IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr, String pn, int uid, @NonNull MediaFocusControl ctlr) { mAttributes = aa; mFocusDispatcher = afl; mSourceRef = source; mClientId = id; mDeathHandler = hdlr; mPackageName = pn; mCallingUid = uid; mFocusGainRequest = focusRequest; mGrantFlags = grantFlags; mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; mFocusController = ctlr; } boolean hasSameClient(String otherClient) { try { return mClientId.compareTo(otherClient) == 0; } catch (NullPointerException e) { return false; } } boolean isLockedFocusOwner() { return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0); } boolean hasSameBinder(IBinder ib) { return (mSourceRef != null) && mSourceRef.equals(ib); } boolean hasSamePackage(String pack) { try { return mPackageName.compareTo(pack) == 0; } catch (NullPointerException e) { return false; } } boolean hasSameUid(int uid) { return mCallingUid == uid; } String getClientId() { return mClientId; } int getGainRequest() { return mFocusGainRequest; } int getGrantFlags() { return mGrantFlags; } AudioAttributes getAudioAttributes() { return mAttributes; } private static String focusChangeToString(int focus) { switch(focus) { case AudioManager.AUDIOFOCUS_NONE: return "none"; case AudioManager.AUDIOFOCUS_GAIN: return "GAIN"; case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: return "GAIN_TRANSIENT"; case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: return "GAIN_TRANSIENT_MAY_DUCK"; case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: return "GAIN_TRANSIENT_EXCLUSIVE"; case AudioManager.AUDIOFOCUS_LOSS: return "LOSS"; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: return "LOSS_TRANSIENT"; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: return "LOSS_TRANSIENT_CAN_DUCK"; default: return "[invalid focus change" + focus + "]"; } } private String focusGainToString() { return focusChangeToString(mFocusGainRequest); } private String focusLossToString() { return focusChangeToString(mFocusLossReceived); } private static String flagsToString(int flags) { String msg = new String(); if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) { msg += "DELAY_OK"; } if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0) { if (!msg.isEmpty()) { msg += "|"; } msg += "LOCK"; } if ((flags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) { if (!msg.isEmpty()) { msg += "|"; } msg += "PAUSES_ON_DUCKABLE_LOSS"; } return msg; } void dump(PrintWriter pw) { pw.println(" source:" + mSourceRef + " -- pack: " + mPackageName + " -- client: " + mClientId + " -- gain: " + focusGainToString() + " -- flags: " + flagsToString(mGrantFlags) + " -- loss: " + focusLossToString() + " -- uid: " + mCallingUid + " -- attr: " + mAttributes); } void release() { try { if (mSourceRef != null && mDeathHandler != null) { mSourceRef.unlinkToDeath(mDeathHandler, 0); mDeathHandler = null; } } catch (java.util.NoSuchElementException e) { Log.e(TAG, "FocusRequester.release() hit ", e); } } @Override protected void finalize() throws Throwable { release(); super.finalize(); } /** * For a given audio focus gain request, return the audio focus loss type that will result * from it, taking into account any previous focus loss. * @param gainRequest * @return the audio focus loss type that matches the gain request */ private int focusLossForGainRequest(int gainRequest) { switch(gainRequest) { case AudioManager.AUDIOFOCUS_GAIN: switch(mFocusLossReceived) { case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_NONE: return AudioManager.AUDIOFOCUS_LOSS; } case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: switch(mFocusLossReceived) { case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_NONE: return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; case AudioManager.AUDIOFOCUS_LOSS: return AudioManager.AUDIOFOCUS_LOSS; } case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: switch(mFocusLossReceived) { case AudioManager.AUDIOFOCUS_NONE: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; case AudioManager.AUDIOFOCUS_LOSS: return AudioManager.AUDIOFOCUS_LOSS; } default: Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest); return AudioManager.AUDIOFOCUS_NONE; } } /** * Called synchronized on MediaFocusControl.mAudioFocusLock */ void handleExternalFocusGain(int focusGain) { int focusLoss = focusLossForGainRequest(focusGain); handleFocusLoss(focusLoss); } /** * Called synchronized on MediaFocusControl.mAudioFocusLock */ void handleFocusGain(int focusGain) { try { mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(), AudioManager.AUDIOFOCUS_REQUEST_GRANTED); if (mFocusDispatcher != null) { if (DEBUG) { Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to " + mClientId); } mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId); } } catch (android.os.RemoteException e) { Log.e(TAG, "Failure to signal gain of audio focus due to: ", e); } } /** * Called synchronized on MediaFocusControl.mAudioFocusLock */ void handleFocusLoss(int focusLoss) { try { if (focusLoss != mFocusLossReceived) { mFocusLossReceived = focusLoss; // before dispatching a focus loss, check if the following conditions are met: // 1/ the framework is not supposed to notify the focus loser on a DUCK loss // 2/ it is a DUCK loss // 3/ the focus loser isn't flagged as pausing in a DUCK loss // if they are, do not notify the focus loser if (!mFocusController.mustNotifyFocusOwnerOnDuck() && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK && (mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) { if (DEBUG) { Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived) + " to " + mClientId + ", to be handled externally"); } mFocusController.notifyExtPolicyFocusLoss_syncAf( toAudioFocusInfo(), false /* wasDispatched */); return; } if (mFocusDispatcher != null) { if (DEBUG) { Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to " + mClientId); } mFocusController.notifyExtPolicyFocusLoss_syncAf( toAudioFocusInfo(), true /* wasDispatched */); mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId); } } } catch (android.os.RemoteException e) { Log.e(TAG, "Failure to signal loss of audio focus due to:", e); } } AudioFocusInfo toAudioFocusInfo() { return new AudioFocusInfo(mAttributes, mClientId, mPackageName, mFocusGainRequest, mFocusLossReceived, mGrantFlags); } }