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                     fr.release();
    164                 }
    165             }
    166         }
    167     }
    168 
    169     /**
    170      * Helper function:
    171      * Called synchronized on mAudioFocusLock
    172      * Remove focus listeners from the focus stack for a particular client when it has died.
    173      */
    174     private void removeFocusStackEntryForClient(IBinder cb) {
    175         // is the owner of the audio focus part of the client to remove?
    176         boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
    177                 mFocusStack.peek().hasSameBinder(cb);
    178         // (using an iterator on the stack so we can safely remove an entry after having
    179         //  evaluated it, traversal order doesn't matter here)
    180         Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
    181         while(stackIterator.hasNext()) {
    182             FocusRequester fr = stackIterator.next();
    183             if(fr.hasSameBinder(cb)) {
    184                 Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for " + cb);
    185                 stackIterator.remove();
    186                 // the client just died, no need to unlink to its death
    187             }
    188         }
    189         if (isTopOfStackForClientToRemove) {
    190             // we removed an entry at the top of the stack:
    191             //  notify the new top of the stack it gained focus.
    192             notifyTopOfAudioFocusStack();
    193         }
    194     }
    195 
    196     /**
    197      * Helper function:
    198      * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
    199      * The implementation guarantees that a state where focus cannot be immediately reassigned
    200      * implies that an "locked" focus owner is at the top of the focus stack.
    201      * Modifications to the implementation that break this assumption will cause focus requests to
    202      * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
    203      */
    204     private boolean canReassignAudioFocus() {
    205         // focus requests are rejected during a phone call or when the phone is ringing
    206         // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
    207         if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
    208             return false;
    209         }
    210         return true;
    211     }
    212 
    213     private boolean isLockedFocusOwner(FocusRequester fr) {
    214         return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
    215     }
    216 
    217     /**
    218      * Helper function
    219      * Pre-conditions: focus stack is not empty, there is one or more locked focus owner
    220      *                 at the top of the focus stack
    221      * Push the focus requester onto the audio focus stack at the first position immediately
    222      * following the locked focus owners.
    223      * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
    224      *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
    225      */
    226     private int pushBelowLockedFocusOwners(FocusRequester nfr) {
    227         int lastLockedFocusOwnerIndex = mFocusStack.size();
    228         for (int index = mFocusStack.size()-1; index >= 0; index--) {
    229             if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
    230                 lastLockedFocusOwnerIndex = index;
    231             }
    232         }
    233         if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
    234             // this should not happen, but handle it and log an error
    235             Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
    236                     new Exception());
    237             // no exclusive owner, push at top of stack, focus is granted, propagate change
    238             propagateFocusLossFromGain_syncAf(nfr.getGainRequest());
    239             mFocusStack.push(nfr);
    240             return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    241         } else {
    242             mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
    243             return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
    244         }
    245     }
    246 
    247     /**
    248      * Inner class to monitor audio focus client deaths, and remove them from the audio focus
    249      * stack if necessary.
    250      */
    251     protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
    252         private IBinder mCb; // To be notified of client's death
    253 
    254         AudioFocusDeathHandler(IBinder cb) {
    255             mCb = cb;
    256         }
    257 
    258         public void binderDied() {
    259             synchronized(mAudioFocusLock) {
    260                 Log.w(TAG, "  AudioFocus   audio focus client died");
    261                 removeFocusStackEntryForClient(mCb);
    262             }
    263         }
    264 
    265         public IBinder getBinder() {
    266             return mCb;
    267         }
    268     }
    269 
    270     /**
    271      * Indicates whether to notify an audio focus owner when it loses focus
    272      * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
    273      * This variable being false indicates an AudioPolicy has been registered and has signaled
    274      * it will handle audio ducking.
    275      */
    276     private boolean mNotifyFocusOwnerOnDuck = true;
    277 
    278     protected void setDuckingInExtPolicyAvailable(boolean available) {
    279         mNotifyFocusOwnerOnDuck = !available;
    280     }
    281 
    282     boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
    283 
    284     private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
    285 
    286     void addFocusFollower(IAudioPolicyCallback ff) {
    287         if (ff == null) {
    288             return;
    289         }
    290         synchronized(mAudioFocusLock) {
    291             boolean found = false;
    292             for (IAudioPolicyCallback pcb : mFocusFollowers) {
    293                 if (pcb.asBinder().equals(ff.asBinder())) {
    294                     found = true;
    295                     break;
    296                 }
    297             }
    298             if (found) {
    299                 return;
    300             } else {
    301                 mFocusFollowers.add(ff);
    302                 notifyExtPolicyCurrentFocusAsync(ff);
    303             }
    304         }
    305     }
    306 
    307     void removeFocusFollower(IAudioPolicyCallback ff) {
    308         if (ff == null) {
    309             return;
    310         }
    311         synchronized(mAudioFocusLock) {
    312             for (IAudioPolicyCallback pcb : mFocusFollowers) {
    313                 if (pcb.asBinder().equals(ff.asBinder())) {
    314                     mFocusFollowers.remove(pcb);
    315                     break;
    316                 }
    317             }
    318         }
    319     }
    320 
    321     /**
    322      * @param pcb non null
    323      */
    324     void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) {
    325         final IAudioPolicyCallback pcb2 = pcb;
    326         final Thread thread = new Thread() {
    327             @Override
    328             public void run() {
    329                 synchronized(mAudioFocusLock) {
    330                     if (mFocusStack.isEmpty()) {
    331                         return;
    332                     }
    333                     try {
    334                         pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(),
    335                                 // top of focus stack always has focus
    336                                 AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    337                     } catch (RemoteException e) {
    338                         Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
    339                                 + pcb2.asBinder(), e);
    340                     }
    341                 }
    342             }
    343         };
    344         thread.start();
    345     }
    346 
    347     /**
    348      * Called synchronized on mAudioFocusLock
    349      */
    350     void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
    351         for (IAudioPolicyCallback pcb : mFocusFollowers) {
    352             try {
    353                 // oneway
    354                 pcb.notifyAudioFocusGrant(afi, requestResult);
    355             } catch (RemoteException e) {
    356                 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
    357                         + pcb.asBinder(), e);
    358             }
    359         }
    360     }
    361 
    362     /**
    363      * Called synchronized on mAudioFocusLock
    364      */
    365     void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
    366         for (IAudioPolicyCallback pcb : mFocusFollowers) {
    367             try {
    368                 // oneway
    369                 pcb.notifyAudioFocusLoss(afi, wasDispatched);
    370             } catch (RemoteException e) {
    371                 Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback "
    372                         + pcb.asBinder(), e);
    373             }
    374         }
    375     }
    376 
    377     protected int getCurrentAudioFocus() {
    378         synchronized(mAudioFocusLock) {
    379             if (mFocusStack.empty()) {
    380                 return AudioManager.AUDIOFOCUS_NONE;
    381             } else {
    382                 return mFocusStack.peek().getGainRequest();
    383             }
    384         }
    385     }
    386 
    387     /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
    388     protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
    389             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
    390         Log.i(TAG, " AudioFocus  requestAudioFocus() from uid/pid " + Binder.getCallingUid()
    391                 + "/" + Binder.getCallingPid()
    392                 + " clientId=" + clientId
    393                 + " req=" + focusChangeHint
    394                 + " flags=0x" + Integer.toHexString(flags));
    395         // we need a valid binder callback for clients
    396         if (!cb.pingBinder()) {
    397             Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
    398             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
    399         }
    400 
    401         if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
    402                 callingPackageName) != AppOpsManager.MODE_ALLOWED) {
    403             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
    404         }
    405 
    406         synchronized(mAudioFocusLock) {
    407             boolean focusGrantDelayed = false;
    408             if (!canReassignAudioFocus()) {
    409                 if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
    410                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
    411                 } else {
    412                     // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
    413                     // granted right now, so the requester will be inserted in the focus stack
    414                     // to receive focus later
    415                     focusGrantDelayed = true;
    416                 }
    417             }
    418 
    419             // handle the potential premature death of the new holder of the focus
    420             // (premature death == death before abandoning focus)
    421             // Register for client death notification
    422             AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
    423             try {
    424                 cb.linkToDeath(afdh, 0);
    425             } catch (RemoteException e) {
    426                 // client has already died!
    427                 Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
    428                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
    429             }
    430 
    431             if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
    432                 // if focus is already owned by this client and the reason for acquiring the focus
    433                 // hasn't changed, don't do anything
    434                 final FocusRequester fr = mFocusStack.peek();
    435                 if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
    436                     // unlink death handler so it can be gc'ed.
    437                     // linkToDeath() creates a JNI global reference preventing collection.
    438                     cb.unlinkToDeath(afdh, 0);
    439                     notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
    440                             AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    441                     return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    442                 }
    443                 // the reason for the audio focus request has changed: remove the current top of
    444                 // stack and respond as if we had a new focus owner
    445                 if (!focusGrantDelayed) {
    446                     mFocusStack.pop();
    447                     // the entry that was "popped" is the same that was "peeked" above
    448                     fr.release();
    449                 }
    450             }
    451 
    452             // focus requester might already be somewhere below in the stack, remove it
    453             removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
    454 
    455             final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
    456                     clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
    457             if (focusGrantDelayed) {
    458                 // focusGrantDelayed being true implies we can't reassign focus right now
    459                 // which implies the focus stack is not empty.
    460                 final int requestResult = pushBelowLockedFocusOwners(nfr);
    461                 if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
    462                     notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
    463                 }
    464                 return requestResult;
    465             } else {
    466                 // propagate the focus change through the stack
    467                 if (!mFocusStack.empty()) {
    468                     propagateFocusLossFromGain_syncAf(focusChangeHint);
    469                 }
    470 
    471                 // push focus requester at the top of the audio focus stack
    472                 mFocusStack.push(nfr);
    473             }
    474             notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
    475                     AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    476 
    477         }//synchronized(mAudioFocusLock)
    478 
    479         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    480     }
    481 
    482     /**
    483      * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
    484      * */
    485     protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
    486         // AudioAttributes are currently ignored, to be used for zones
    487         Log.i(TAG, " AudioFocus  abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
    488                 + "/" + Binder.getCallingPid()
    489                 + " clientId=" + clientId);
    490         try {
    491             // this will take care of notifying the new focus owner if needed
    492             synchronized(mAudioFocusLock) {
    493                 removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
    494             }
    495         } catch (java.util.ConcurrentModificationException cme) {
    496             // Catching this exception here is temporary. It is here just to prevent
    497             // a crash seen when the "Silent" notification is played. This is believed to be fixed
    498             // but this try catch block is left just to be safe.
    499             Log.e(TAG, "FATAL EXCEPTION AudioFocus  abandonAudioFocus() caused " + cme);
    500             cme.printStackTrace();
    501         }
    502 
    503         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    504     }
    505 
    506 
    507     protected void unregisterAudioFocusClient(String clientId) {
    508         synchronized(mAudioFocusLock) {
    509             removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
    510         }
    511     }
    512 
    513 }
    514