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 
MediaFocusControl(Context cntxt)50     protected MediaFocusControl(Context cntxt) {
51         mContext = cntxt;
52         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
53     }
54 
dump(PrintWriter pw)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      */
discardAudioFocusOwner()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      */
notifyTopOfAudioFocusStack()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      */
propagateFocusLossFromGain_syncAf(int focusGain)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      */
dumpFocusStack(PrintWriter pw)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      */
removeFocusStackEntry(String clientToRemove, boolean signal, boolean notifyFocusFollowers)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      */
removeFocusStackEntryForClient(IBinder cb)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      */
canReassignAudioFocus()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 
isLockedFocusOwner(FocusRequester fr)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      */
pushBelowLockedFocusOwners(FocusRequester nfr)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 
AudioFocusDeathHandler(IBinder cb)254         AudioFocusDeathHandler(IBinder cb) {
255             mCb = cb;
256         }
257 
binderDied()258         public void binderDied() {
259             synchronized(mAudioFocusLock) {
260                 Log.w(TAG, "  AudioFocus   audio focus client died");
261                 removeFocusStackEntryForClient(mCb);
262             }
263         }
264 
getBinder()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 
setDuckingInExtPolicyAvailable(boolean available)278     protected void setDuckingInExtPolicyAvailable(boolean available) {
279         mNotifyFocusOwnerOnDuck = !available;
280     }
281 
mustNotifyFocusOwnerOnDuck()282     boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
283 
284     private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
285 
addFocusFollower(IAudioPolicyCallback ff)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 
removeFocusFollower(IAudioPolicyCallback ff)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      */
notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb)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      */
notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult)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      */
notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched)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 
getCurrentAudioFocus()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) */
requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags)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      * */
abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa)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 
unregisterAudioFocusClient(String clientId)507     protected void unregisterAudioFocusClient(String clientId) {
508         synchronized(mAudioFocusLock) {
509             removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
510         }
511     }
512 
513 }
514