Home | History | Annotate | Download | only in audio
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.audio;
     18 
     19 import android.app.AppOpsManager;
     20 import android.content.Context;
     21 import android.media.AudioAttributes;
     22 import android.media.AudioFocusInfo;
     23 import android.media.AudioManager;
     24 import android.media.AudioSystem;
     25 import android.media.IAudioFocusDispatcher;
     26 import android.media.audiopolicy.IAudioPolicyCallback;
     27 import android.os.Binder;
     28 import android.os.IBinder;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 
     32 import java.io.PrintWriter;
     33 import java.util.ArrayList;
     34 import java.util.Date;
     35 import java.util.Iterator;
     36 import java.util.Stack;
     37 import java.text.DateFormat;
     38 
     39 /**
     40  * @hide
     41  *
     42  */
     43 public class MediaFocusControl {
     44 
     45     private static final String TAG = "MediaFocusControl";
     46 
     47     private final Context mContext;
     48     private final AppOpsManager mAppOps;
     49 
     50     protected MediaFocusControl(Context cntxt) {
     51         mContext = cntxt;
     52         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
     53     }
     54 
     55     protected void dump(PrintWriter pw) {
     56         pw.println("\nMediaFocusControl dump time: "
     57                 + DateFormat.getTimeInstance().format(new Date()));
     58         dumpFocusStack(pw);
     59     }
     60 
     61 
     62     //==========================================================================================
     63     // AudioFocus
     64     //==========================================================================================
     65 
     66     private final static Object mAudioFocusLock = new Object();
     67 
     68     /**
     69      * Discard the current audio focus owner.
     70      * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
     71      * focus), remove it from the stack, and clear the remote control display.
     72      */
     73     protected void discardAudioFocusOwner() {
     74         synchronized(mAudioFocusLock) {
     75             if (!mFocusStack.empty()) {
     76                 // notify the current focus owner it lost focus after removing it from stack
     77                 final FocusRequester exFocusOwner = mFocusStack.pop();
     78                 exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
     79                 exFocusOwner.release();
     80             }
     81         }
     82     }
     83 
     84     /**
     85      * Called synchronized on mAudioFocusLock
     86      */
     87     private void notifyTopOfAudioFocusStack() {
     88         // notify the top of the stack it gained focus
     89         if (!mFocusStack.empty()) {
     90             if (canReassignAudioFocus()) {
     91                 mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
     92             }
     93         }
     94     }
     95 
     96     /**
     97      * Focus is requested, propagate the associated loss throughout the stack.
     98      * @param focusGain the new focus gain that will later be added at the top of the stack
     99      */
    100     private void propagateFocusLossFromGain_syncAf(int focusGain) {
    101         // going through the audio focus stack to signal new focus, traversing order doesn't
    102         // matter as all entries respond to the same external focus gain
    103         Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
    104         while(stackIterator.hasNext()) {
    105             stackIterator.next().handleExternalFocusGain(focusGain);
    106         }
    107     }
    108 
    109     private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
    110 
    111     /**
    112      * Helper function:
    113      * Display in the log the current entries in the audio focus stack
    114      */
    115     private void dumpFocusStack(PrintWriter pw) {
    116         pw.println("\nAudio Focus stack entries (last is top of stack):");
    117         synchronized(mAudioFocusLock) {
    118             Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
    119             while(stackIterator.hasNext()) {
    120                 stackIterator.next().dump(pw);
    121             }
    122         }
    123         pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n");
    124     }
    125 
    126     /**
    127      * Helper function:
    128      * Called synchronized on mAudioFocusLock
    129      * Remove a focus listener from the focus stack.
    130      * @param clientToRemove the focus listener
    131      * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
    132      *   focus, notify the next item in the stack it gained focus.
    133      */
    134     private void removeFocusStackEntry(String clientToRemove, boolean signal,
    135             boolean notifyFocusFollowers) {
    136         // is the current top of the focus stack abandoning focus? (because of request, not death)
    137         if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
    138         {
    139             //Log.i(TAG, "   removeFocusStackEntry() removing top of stack");
    140             FocusRequester fr = mFocusStack.pop();
    141             fr.release();
    142             if (notifyFocusFollowers) {
    143                 final AudioFocusInfo afi = fr.toAudioFocusInfo();
    144                 afi.clearLossReceived();
    145                 notifyExtPolicyFocusLoss_syncAf(afi, false);
    146             }
    147             if (signal) {
    148                 // notify the new top of the stack it gained focus
    149                 notifyTopOfAudioFocusStack();
    150             }
    151         } else {
    152             // focus is abandoned by a client that's not at the top of the stack,
    153             // no need to update focus.
    154             // (using an iterator on the stack so we can safely remove an entry after having
    155             //  evaluated it, traversal order doesn't matter here)
    156             Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
    157             while(stackIterator.hasNext()) {
    158                 FocusRequester fr = stackIterator.next();
    159                 if(fr.hasSameClient(clientToRemove)) {
    160                     Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for "
    161                             + clientToRemove);
    162                     stackIterator.remove();
    163                     // stack entry not used anymore, clear references
    164                     fr.release();
    165                 }
    166             }
    167         }
    168     }
    169 
    170     /**
    171      * Helper function:
    172      * Called synchronized on mAudioFocusLock
    173      * Remove focus listeners from the focus stack for a particular client when it has died.
    174      */
    175     private void removeFocusStackEntryOnDeath(IBinder cb) {
    176         // is the owner of the audio focus part of the client to remove?
    177         boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
    178                 mFocusStack.peek().hasSameBinder(cb);
    179         // (using an iterator on the stack so we can safely remove an entry after having
    180         //  evaluated it, traversal order doesn't matter here)
    181         Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
    182         while(stackIterator.hasNext()) {
    183             FocusRequester fr = stackIterator.next();
    184             if(fr.hasSameBinder(cb)) {
    185                 Log.i(TAG, "AudioFocus  removeFocusStackEntryOnDeath(): removing entry for " + cb);
    186                 stackIterator.remove();
    187                 // stack entry not used anymore, clear references
    188                 fr.release();
    189             }
    190         }
    191         if (isTopOfStackForClientToRemove) {
    192             // we removed an entry at the top of the stack:
    193             //  notify the new top of the stack it gained focus.
    194             notifyTopOfAudioFocusStack();
    195         }
    196     }
    197 
    198     /**
    199      * Helper function:
    200      * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
    201      * The implementation guarantees that a state where focus cannot be immediately reassigned
    202      * implies that an "locked" focus owner is at the top of the focus stack.
    203      * Modifications to the implementation that break this assumption will cause focus requests to
    204      * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
    205      */
    206     private boolean canReassignAudioFocus() {
    207         // focus requests are rejected during a phone call or when the phone is ringing
    208         // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
    209         if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
    210             return false;
    211         }
    212         return true;
    213     }
    214 
    215     private boolean isLockedFocusOwner(FocusRequester fr) {
    216         return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
    217     }
    218 
    219     /**
    220      * Helper function
    221      * Pre-conditions: focus stack is not empty, there is one or more locked focus owner
    222      *                 at the top of the focus stack
    223      * Push the focus requester onto the audio focus stack at the first position immediately
    224      * following the locked focus owners.
    225      * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
    226      *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
    227      */
    228     private int pushBelowLockedFocusOwners(FocusRequester nfr) {
    229         int lastLockedFocusOwnerIndex = mFocusStack.size();
    230         for (int index = mFocusStack.size()-1; index >= 0; index--) {
    231             if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
    232                 lastLockedFocusOwnerIndex = index;
    233             }
    234         }
    235         if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
    236             // this should not happen, but handle it and log an error
    237             Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
    238                     new Exception());
    239             // no exclusive owner, push at top of stack, focus is granted, propagate change
    240             propagateFocusLossFromGain_syncAf(nfr.getGainRequest());
    241             mFocusStack.push(nfr);
    242             return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    243         } else {
    244             mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
    245             return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
    246         }
    247     }
    248 
    249     /**
    250      * Inner class to monitor audio focus client deaths, and remove them from the audio focus
    251      * stack if necessary.
    252      */
    253     protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
    254         private IBinder mCb; // To be notified of client's death
    255 
    256         AudioFocusDeathHandler(IBinder cb) {
    257             mCb = cb;
    258         }
    259 
    260         public void binderDied() {
    261             synchronized(mAudioFocusLock) {
    262                 removeFocusStackEntryOnDeath(mCb);
    263             }
    264         }
    265     }
    266 
    267     /**
    268      * Indicates whether to notify an audio focus owner when it loses focus
    269      * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
    270      * This variable being false indicates an AudioPolicy has been registered and has signaled
    271      * it will handle audio ducking.
    272      */
    273     private boolean mNotifyFocusOwnerOnDuck = true;
    274 
    275     protected void setDuckingInExtPolicyAvailable(boolean available) {
    276         mNotifyFocusOwnerOnDuck = !available;
    277     }
    278 
    279     boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
    280 
    281     private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
    282 
    283     void addFocusFollower(IAudioPolicyCallback ff) {
    284         if (ff == null) {
    285             return;
    286         }
    287         synchronized(mAudioFocusLock) {
    288             boolean found = false;
    289             for (IAudioPolicyCallback pcb : mFocusFollowers) {
    290                 if (pcb.asBinder().equals(ff.asBinder())) {
    291                     found = true;
    292                     break;
    293                 }
    294             }
    295             if (found) {
    296                 return;
    297             } else {
    298                 mFocusFollowers.add(ff);
    299                 notifyExtPolicyCurrentFocusAsync(ff);
    300             }
    301         }
    302     }
    303 
    304     void removeFocusFollower(IAudioPolicyCallback ff) {
    305         if (ff == null) {
    306             return;
    307         }
    308         synchronized(mAudioFocusLock) {
    309             for (IAudioPolicyCallback pcb : mFocusFollowers) {
    310                 if (pcb.asBinder().equals(ff.asBinder())) {
    311                     mFocusFollowers.remove(pcb);
    312                     break;
    313                 }
    314             }
    315         }
    316     }
    317 
    318     /**
    319      * @param pcb non null
    320      */
    321     void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) {
    322         final IAudioPolicyCallback pcb2 = pcb;
    323         final Thread thread = new Thread() {
    324             @Override
    325             public void run() {
    326                 synchronized(mAudioFocusLock) {
    327                     if (mFocusStack.isEmpty()) {
    328                         return;
    329                     }
    330                     try {
    331                         pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(),
    332                                 // top of focus stack always has focus
    333                                 AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    334                     } catch (RemoteException e) {
    335                         Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
    336                                 + pcb2.asBinder(), e);
    337                     }
    338                 }
    339             }
    340         };
    341         thread.start();
    342     }
    343 
    344     /**
    345      * Called synchronized on mAudioFocusLock
    346      */
    347     void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
    348         for (IAudioPolicyCallback pcb : mFocusFollowers) {
    349             try {
    350                 // oneway
    351                 pcb.notifyAudioFocusGrant(afi, requestResult);
    352             } catch (RemoteException e) {
    353                 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
    354                         + pcb.asBinder(), e);
    355             }
    356         }
    357     }
    358 
    359     /**
    360      * Called synchronized on mAudioFocusLock
    361      */
    362     void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
    363         for (IAudioPolicyCallback pcb : mFocusFollowers) {
    364             try {
    365                 // oneway
    366                 pcb.notifyAudioFocusLoss(afi, wasDispatched);
    367             } catch (RemoteException e) {
    368                 Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback "
    369                         + pcb.asBinder(), e);
    370             }
    371         }
    372     }
    373 
    374     protected int getCurrentAudioFocus() {
    375         synchronized(mAudioFocusLock) {
    376             if (mFocusStack.empty()) {
    377                 return AudioManager.AUDIOFOCUS_NONE;
    378             } else {
    379                 return mFocusStack.peek().getGainRequest();
    380             }
    381         }
    382     }
    383 
    384     /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
    385     protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
    386             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
    387         Log.i(TAG, " AudioFocus  requestAudioFocus() from uid/pid " + Binder.getCallingUid()
    388                 + "/" + Binder.getCallingPid()
    389                 + " clientId=" + clientId
    390                 + " req=" + focusChangeHint
    391                 + " flags=0x" + Integer.toHexString(flags));
    392         // we need a valid binder callback for clients
    393         if (!cb.pingBinder()) {
    394             Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
    395             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
    396         }
    397 
    398         if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
    399                 callingPackageName) != AppOpsManager.MODE_ALLOWED) {
    400             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
    401         }
    402 
    403         synchronized(mAudioFocusLock) {
    404             boolean focusGrantDelayed = false;
    405             if (!canReassignAudioFocus()) {
    406                 if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
    407                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
    408                 } else {
    409                     // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
    410                     // granted right now, so the requester will be inserted in the focus stack
    411                     // to receive focus later
    412                     focusGrantDelayed = true;
    413                 }
    414             }
    415 
    416             // handle the potential premature death of the new holder of the focus
    417             // (premature death == death before abandoning focus)
    418             // Register for client death notification
    419             AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
    420 
    421             try {
    422                 cb.linkToDeath(afdh, 0);
    423             } catch (RemoteException e) {
    424                 // client has already died!
    425                 Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
    426                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
    427             }
    428 
    429             if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
    430                 // if focus is already owned by this client and the reason for acquiring the focus
    431                 // hasn't changed, don't do anything
    432                 final FocusRequester fr = mFocusStack.peek();
    433                 if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
    434                     // unlink death handler so it can be gc'ed.
    435                     // linkToDeath() creates a JNI global reference preventing collection.
    436                     cb.unlinkToDeath(afdh, 0);
    437                     notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
    438                             AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    439                     return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    440                 }
    441                 // the reason for the audio focus request has changed: remove the current top of
    442                 // stack and respond as if we had a new focus owner
    443                 if (!focusGrantDelayed) {
    444                     mFocusStack.pop();
    445                     // the entry that was "popped" is the same that was "peeked" above
    446                     fr.release();
    447                 }
    448             }
    449 
    450             // focus requester might already be somewhere below in the stack, remove it
    451             removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
    452 
    453             final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
    454                     clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
    455             if (focusGrantDelayed) {
    456                 // focusGrantDelayed being true implies we can't reassign focus right now
    457                 // which implies the focus stack is not empty.
    458                 final int requestResult = pushBelowLockedFocusOwners(nfr);
    459                 if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
    460                     notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
    461                 }
    462                 return requestResult;
    463             } else {
    464                 // propagate the focus change through the stack
    465                 if (!mFocusStack.empty()) {
    466                     propagateFocusLossFromGain_syncAf(focusChangeHint);
    467                 }
    468 
    469                 // push focus requester at the top of the audio focus stack
    470                 mFocusStack.push(nfr);
    471             }
    472             notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
    473                     AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    474 
    475         }//synchronized(mAudioFocusLock)
    476 
    477         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    478     }
    479 
    480     /**
    481      * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
    482      * */
    483     protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
    484         // AudioAttributes are currently ignored, to be used for zones
    485         Log.i(TAG, " AudioFocus  abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
    486                 + "/" + Binder.getCallingPid()
    487                 + " clientId=" + clientId);
    488         try {
    489             // this will take care of notifying the new focus owner if needed
    490             synchronized(mAudioFocusLock) {
    491                 removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
    492             }
    493         } catch (java.util.ConcurrentModificationException cme) {
    494             // Catching this exception here is temporary. It is here just to prevent
    495             // a crash seen when the "Silent" notification is played. This is believed to be fixed
    496             // but this try catch block is left just to be safe.
    497             Log.e(TAG, "FATAL EXCEPTION AudioFocus  abandonAudioFocus() caused " + cme);
    498             cme.printStackTrace();
    499         }
    500 
    501         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    502     }
    503 
    504 
    505     protected void unregisterAudioFocusClient(String clientId) {
    506         synchronized(mAudioFocusLock) {
    507             removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
    508         }
    509     }
    510 
    511 }
    512