• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.AppOpsManager;
22 import android.content.Context;
23 import android.media.AudioAttributes;
24 import android.media.AudioFocusInfo;
25 import android.media.AudioManager;
26 import android.media.AudioSystem;
27 import android.media.IAudioFocusDispatcher;
28 import android.media.MediaMetrics;
29 import android.media.audiopolicy.IAudioPolicyCallback;
30 import android.os.Binder;
31 import android.os.Build;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.provider.Settings;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.GuardedBy;
38 
39 import java.io.PrintWriter;
40 import java.text.DateFormat;
41 import java.util.ArrayList;
42 import java.util.Date;
43 import java.util.HashMap;
44 import java.util.Iterator;
45 import java.util.LinkedList;
46 import java.util.List;
47 import java.util.Map.Entry;
48 import java.util.Set;
49 import java.util.Stack;
50 
51 /**
52  * @hide
53  *
54  */
55 public class MediaFocusControl implements PlayerFocusEnforcer {
56 
57     private static final String TAG = "MediaFocusControl";
58     static final boolean DEBUG = false;
59 
60     /**
61      * set to true so the framework enforces ducking itself, without communicating to apps
62      * that they lost focus for most use cases.
63      */
64     static final boolean ENFORCE_DUCKING = true;
65     /**
66      * set to true to the framework enforces ducking itself only with apps above a given SDK
67      * target level. Is ignored if ENFORCE_DUCKING is false.
68      */
69     static final boolean ENFORCE_DUCKING_FOR_NEW = true;
70     /**
71      * the SDK level (included) up to which the framework doesn't enforce ducking itself. Is ignored
72      * if ENFORCE_DUCKING_FOR_NEW is false;
73      */
74     // automatic ducking was introduced for Android O
75     static final int DUCKING_IN_APP_SDK_LEVEL = Build.VERSION_CODES.N_MR1;
76     /**
77      * set to true so the framework enforces muting media/game itself when the device is ringing
78      * or in a call.
79      */
80     static final boolean ENFORCE_MUTING_FOR_RING_OR_CALL = true;
81 
82     private final Context mContext;
83     private final AppOpsManager mAppOps;
84     private PlayerFocusEnforcer mFocusEnforcer; // never null
85     private boolean mMultiAudioFocusEnabled = false;
86 
87     private boolean mRingOrCallActive = false;
88 
89     private final Object mExtFocusChangeLock = new Object();
90     @GuardedBy("mExtFocusChangeLock")
91     private long mExtFocusChangeCounter;
92 
MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe)93     protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) {
94         mContext = cntxt;
95         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
96         mFocusEnforcer = pfe;
97         mMultiAudioFocusEnabled = Settings.System.getInt(mContext.getContentResolver(),
98                 Settings.System.MULTI_AUDIO_FOCUS_ENABLED, 0) != 0;
99     }
100 
dump(PrintWriter pw)101     protected void dump(PrintWriter pw) {
102         pw.println("\nMediaFocusControl dump time: "
103                 + DateFormat.getTimeInstance().format(new Date()));
104         dumpFocusStack(pw);
105         pw.println("\n");
106         // log
107         mEventLogger.dump(pw);
108         dumpMultiAudioFocus(pw);
109     }
110 
111     //=================================================================
112     // PlayerFocusEnforcer implementation
113     @Override
duckPlayers(@onNull FocusRequester winner, @NonNull FocusRequester loser, boolean forceDuck)114     public boolean duckPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser,
115                                boolean forceDuck) {
116         return mFocusEnforcer.duckPlayers(winner, loser, forceDuck);
117     }
118 
119     @Override
unduckPlayers(@onNull FocusRequester winner)120     public void unduckPlayers(@NonNull FocusRequester winner) {
121         mFocusEnforcer.unduckPlayers(winner);
122     }
123 
124     @Override
mutePlayersForCall(int[] usagesToMute)125     public void mutePlayersForCall(int[] usagesToMute) {
126         mFocusEnforcer.mutePlayersForCall(usagesToMute);
127     }
128 
129     @Override
unmutePlayersForCall()130     public void unmutePlayersForCall() {
131         mFocusEnforcer.unmutePlayersForCall();
132     }
133 
134     //==========================================================================================
135     // AudioFocus
136     //==========================================================================================
137 
138     private final static Object mAudioFocusLock = new Object();
139 
140     /**
141      * Arbitrary maximum size of audio focus stack to prevent apps OOM'ing this process.
142      */
143     private static final int MAX_STACK_SIZE = 100;
144 
145     private static final AudioEventLogger mEventLogger = new AudioEventLogger(50,
146             "focus commands as seen by MediaFocusControl");
147 
148     private static final String mMetricsId = MediaMetrics.Name.AUDIO_FOCUS;
149 
noFocusForSuspendedApp(@onNull String packageName, int uid)150     /*package*/ void noFocusForSuspendedApp(@NonNull String packageName, int uid) {
151         synchronized (mAudioFocusLock) {
152             final Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
153             List<String> clientsToRemove = new ArrayList<>();
154             while (stackIterator.hasNext()) {
155                 final FocusRequester focusOwner = stackIterator.next();
156                 if (focusOwner.hasSameUid(uid) && focusOwner.hasSamePackage(packageName)) {
157                     clientsToRemove.add(focusOwner.getClientId());
158                     mEventLogger.log((new AudioEventLogger.StringEvent(
159                             "focus owner:" + focusOwner.getClientId()
160                                     + " in uid:" + uid + " pack: " + packageName
161                                     + " getting AUDIOFOCUS_LOSS due to app suspension"))
162                             .printLog(TAG));
163                     // make the suspended app lose focus through its focus listener (if any)
164                     focusOwner.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
165                 }
166             }
167             for (String clientToRemove : clientsToRemove) {
168                 // update the stack but don't signal the change.
169                 removeFocusStackEntry(clientToRemove, false, true);
170             }
171         }
172     }
173 
hasAudioFocusUsers()174     /*package*/ boolean hasAudioFocusUsers() {
175         synchronized (mAudioFocusLock) {
176             return !mFocusStack.empty();
177         }
178     }
179 
180     /**
181      * Discard the current audio focus owner.
182      * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
183      * focus), remove it from the stack, and clear the remote control display.
184      */
discardAudioFocusOwner()185     protected void discardAudioFocusOwner() {
186         synchronized(mAudioFocusLock) {
187             if (!mFocusStack.empty()) {
188                 // notify the current focus owner it lost focus after removing it from stack
189                 final FocusRequester exFocusOwner = mFocusStack.pop();
190                 exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
191                         false /*forceDuck*/);
192                 exFocusOwner.release();
193             }
194         }
195     }
196 
197     @GuardedBy("mAudioFocusLock")
notifyTopOfAudioFocusStack()198     private void notifyTopOfAudioFocusStack() {
199         // notify the top of the stack it gained focus
200         if (!mFocusStack.empty()) {
201             if (canReassignAudioFocus()) {
202                 mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
203             }
204         }
205 
206         if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
207             for (FocusRequester multifr : mMultiAudioFocusList) {
208                 if (isLockedFocusOwner(multifr)) {
209                     multifr.handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
210                 }
211             }
212         }
213     }
214 
215     /**
216      * Focus is requested, propagate the associated loss throughout the stack.
217      * Will also remove entries in the stack that have just received a definitive loss of focus.
218      * @param focusGain the new focus gain that will later be added at the top of the stack
219      */
220     @GuardedBy("mAudioFocusLock")
propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr, boolean forceDuck)221     private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
222                                                    boolean forceDuck) {
223         final List<String> clientsToRemove = new LinkedList<String>();
224         // going through the audio focus stack to signal new focus, traversing order doesn't
225         // matter as all entries respond to the same external focus gain
226         if (!mFocusStack.empty()) {
227             for (FocusRequester focusLoser : mFocusStack) {
228                 final boolean isDefinitiveLoss =
229                         focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
230                 if (isDefinitiveLoss) {
231                     clientsToRemove.add(focusLoser.getClientId());
232                 }
233             }
234         }
235 
236         if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
237             for (FocusRequester multifocusLoser : mMultiAudioFocusList) {
238                 final boolean isDefinitiveLoss =
239                         multifocusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
240                 if (isDefinitiveLoss) {
241                     clientsToRemove.add(multifocusLoser.getClientId());
242                 }
243             }
244         }
245 
246         for (String clientToRemove : clientsToRemove) {
247             removeFocusStackEntry(clientToRemove, false /*signal*/,
248                     true /*notifyFocusFollowers*/);
249         }
250     }
251 
252     private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
253 
254     ArrayList<FocusRequester> mMultiAudioFocusList = new ArrayList<FocusRequester>();
255 
256     /**
257      * Helper function:
258      * Display in the log the current entries in the audio focus stack
259      */
dumpFocusStack(PrintWriter pw)260     private void dumpFocusStack(PrintWriter pw) {
261         pw.println("\nAudio Focus stack entries (last is top of stack):");
262         synchronized(mAudioFocusLock) {
263             Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
264             while(stackIterator.hasNext()) {
265                 stackIterator.next().dump(pw);
266             }
267             pw.println("\n");
268             if (mFocusPolicy == null) {
269                 pw.println("No external focus policy\n");
270             } else {
271                 pw.println("External focus policy: "+ mFocusPolicy + ", focus owners:\n");
272                 dumpExtFocusPolicyFocusOwners(pw);
273             }
274         }
275         pw.println("\n");
276         pw.println(" Notify on duck:  " + mNotifyFocusOwnerOnDuck + "\n");
277         pw.println(" In ring or call: " + mRingOrCallActive + "\n");
278     }
279 
280     /**
281      * Remove a focus listener from the focus stack.
282      * @param clientToRemove the focus listener
283      * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
284      *   focus, notify the next item in the stack it gained focus.
285      */
286     @GuardedBy("mAudioFocusLock")
removeFocusStackEntry(String clientToRemove, boolean signal, boolean notifyFocusFollowers)287     private void removeFocusStackEntry(String clientToRemove, boolean signal,
288             boolean notifyFocusFollowers) {
289         // is the current top of the focus stack abandoning focus? (because of request, not death)
290         if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
291         {
292             //Log.i(TAG, "   removeFocusStackEntry() removing top of stack");
293             FocusRequester fr = mFocusStack.pop();
294             fr.release();
295             if (notifyFocusFollowers) {
296                 final AudioFocusInfo afi = fr.toAudioFocusInfo();
297                 afi.clearLossReceived();
298                 notifyExtPolicyFocusLoss_syncAf(afi, false);
299             }
300             if (signal) {
301                 // notify the new top of the stack it gained focus
302                 notifyTopOfAudioFocusStack();
303             }
304         } else {
305             // focus is abandoned by a client that's not at the top of the stack,
306             // no need to update focus.
307             // (using an iterator on the stack so we can safely remove an entry after having
308             //  evaluated it, traversal order doesn't matter here)
309             Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
310             while(stackIterator.hasNext()) {
311                 FocusRequester fr = stackIterator.next();
312                 if(fr.hasSameClient(clientToRemove)) {
313                     Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for "
314                             + clientToRemove);
315                     stackIterator.remove();
316                     // stack entry not used anymore, clear references
317                     fr.release();
318                 }
319             }
320         }
321 
322         if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
323             Iterator<FocusRequester> listIterator = mMultiAudioFocusList.iterator();
324             while (listIterator.hasNext()) {
325                 FocusRequester fr = listIterator.next();
326                 if (fr.hasSameClient(clientToRemove)) {
327                     listIterator.remove();
328                     fr.release();
329                 }
330             }
331 
332             if (signal) {
333                 // notify the new top of the stack it gained focus
334                 notifyTopOfAudioFocusStack();
335             }
336         }
337     }
338 
339     /**
340      * Remove focus listeners from the focus stack for a particular client when it has died.
341      */
342     @GuardedBy("mAudioFocusLock")
removeFocusStackEntryOnDeath(IBinder cb)343     private void removeFocusStackEntryOnDeath(IBinder cb) {
344         // is the owner of the audio focus part of the client to remove?
345         boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
346                 mFocusStack.peek().hasSameBinder(cb);
347         // (using an iterator on the stack so we can safely remove an entry after having
348         //  evaluated it, traversal order doesn't matter here)
349         Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
350         while(stackIterator.hasNext()) {
351             FocusRequester fr = stackIterator.next();
352             if(fr.hasSameBinder(cb)) {
353                 Log.i(TAG, "AudioFocus  removeFocusStackEntryOnDeath(): removing entry for " + cb);
354                 stackIterator.remove();
355                 // stack entry not used anymore, clear references
356                 fr.release();
357             }
358         }
359         if (isTopOfStackForClientToRemove) {
360             // we removed an entry at the top of the stack:
361             //  notify the new top of the stack it gained focus.
362             notifyTopOfAudioFocusStack();
363         }
364     }
365 
366     /**
367      * Helper function for external focus policy:
368      * Remove focus listeners from the list of potential focus owners for a particular client when
369      * it has died.
370      */
371     @GuardedBy("mAudioFocusLock")
removeFocusEntryForExtPolicy(IBinder cb)372     private void removeFocusEntryForExtPolicy(IBinder cb) {
373         if (mFocusOwnersForFocusPolicy.isEmpty()) {
374             return;
375         }
376         boolean released = false;
377         final Set<Entry<String, FocusRequester>> owners = mFocusOwnersForFocusPolicy.entrySet();
378         final Iterator<Entry<String, FocusRequester>> ownerIterator = owners.iterator();
379         while (ownerIterator.hasNext()) {
380             final Entry<String, FocusRequester> owner = ownerIterator.next();
381             final FocusRequester fr = owner.getValue();
382             if (fr.hasSameBinder(cb)) {
383                 ownerIterator.remove();
384                 fr.release();
385                 notifyExtFocusPolicyFocusAbandon_syncAf(fr.toAudioFocusInfo());
386                 break;
387             }
388         }
389     }
390 
391     /**
392      * Helper function:
393      * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
394      * The implementation guarantees that a state where focus cannot be immediately reassigned
395      * implies that an "locked" focus owner is at the top of the focus stack.
396      * Modifications to the implementation that break this assumption will cause focus requests to
397      * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
398      */
canReassignAudioFocus()399     private boolean canReassignAudioFocus() {
400         // focus requests are rejected during a phone call or when the phone is ringing
401         // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
402         if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
403             return false;
404         }
405         return true;
406     }
407 
isLockedFocusOwner(FocusRequester fr)408     private boolean isLockedFocusOwner(FocusRequester fr) {
409         return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
410     }
411 
412     /**
413      * Helper function
414      * Pre-conditions: focus stack is not empty, there is one or more locked focus owner
415      *                 at the top of the focus stack
416      * Push the focus requester onto the audio focus stack at the first position immediately
417      * following the locked focus owners.
418      * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
419      *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
420      */
421     @GuardedBy("mAudioFocusLock")
pushBelowLockedFocusOwners(FocusRequester nfr)422     private int pushBelowLockedFocusOwners(FocusRequester nfr) {
423         int lastLockedFocusOwnerIndex = mFocusStack.size();
424         for (int index = mFocusStack.size()-1; index >= 0; index--) {
425             if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
426                 lastLockedFocusOwnerIndex = index;
427             }
428         }
429         if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
430             // this should not happen, but handle it and log an error
431             Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
432                     new Exception());
433             // no exclusive owner, push at top of stack, focus is granted, propagate change
434             propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr, false /*forceDuck*/);
435             mFocusStack.push(nfr);
436             return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
437         } else {
438             mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
439             return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
440         }
441     }
442 
443     /**
444      * Inner class to monitor audio focus client deaths, and remove them from the audio focus
445      * stack if necessary.
446      */
447     protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
448         private IBinder mCb; // To be notified of client's death
449 
AudioFocusDeathHandler(IBinder cb)450         AudioFocusDeathHandler(IBinder cb) {
451             mCb = cb;
452         }
453 
binderDied()454         public void binderDied() {
455             synchronized(mAudioFocusLock) {
456                 if (mFocusPolicy != null) {
457                     removeFocusEntryForExtPolicy(mCb);
458                 } else {
459                     removeFocusStackEntryOnDeath(mCb);
460                     if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
461                         Iterator<FocusRequester> listIterator = mMultiAudioFocusList.iterator();
462                         while (listIterator.hasNext()) {
463                             FocusRequester fr = listIterator.next();
464                             if (fr.hasSameBinder(mCb)) {
465                                 listIterator.remove();
466                                 fr.release();
467                             }
468                         }
469                     }
470                 }
471             }
472         }
473     }
474 
475     /**
476      * Indicates whether to notify an audio focus owner when it loses focus
477      * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
478      * This variable being false indicates an AudioPolicy has been registered and has signaled
479      * it will handle audio ducking.
480      */
481     private boolean mNotifyFocusOwnerOnDuck = true;
482 
setDuckingInExtPolicyAvailable(boolean available)483     protected void setDuckingInExtPolicyAvailable(boolean available) {
484         mNotifyFocusOwnerOnDuck = !available;
485     }
486 
mustNotifyFocusOwnerOnDuck()487     boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
488 
489     private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
490 
addFocusFollower(IAudioPolicyCallback ff)491     void addFocusFollower(IAudioPolicyCallback ff) {
492         if (ff == null) {
493             return;
494         }
495         synchronized(mAudioFocusLock) {
496             boolean found = false;
497             for (IAudioPolicyCallback pcb : mFocusFollowers) {
498                 if (pcb.asBinder().equals(ff.asBinder())) {
499                     found = true;
500                     break;
501                 }
502             }
503             if (found) {
504                 return;
505             } else {
506                 mFocusFollowers.add(ff);
507                 notifyExtPolicyCurrentFocusAsync(ff);
508             }
509         }
510     }
511 
removeFocusFollower(IAudioPolicyCallback ff)512     void removeFocusFollower(IAudioPolicyCallback ff) {
513         if (ff == null) {
514             return;
515         }
516         synchronized(mAudioFocusLock) {
517             for (IAudioPolicyCallback pcb : mFocusFollowers) {
518                 if (pcb.asBinder().equals(ff.asBinder())) {
519                     mFocusFollowers.remove(pcb);
520                     break;
521                 }
522             }
523         }
524     }
525 
526     /** The current audio focus policy */
527     @GuardedBy("mAudioFocusLock")
528     @Nullable private IAudioPolicyCallback mFocusPolicy = null;
529     /**
530      * The audio focus policy that was registered before a test focus policy was registered
531      * during a test
532      */
533     @GuardedBy("mAudioFocusLock")
534     @Nullable private IAudioPolicyCallback mPreviousFocusPolicy = null;
535 
536     // Since we don't have a stack of focus owners when using an external focus policy, we keep
537     // track of all the focus requesters in this map, with their clientId as the key. This is
538     // used both for focus dispatch and death handling
539     private HashMap<String, FocusRequester> mFocusOwnersForFocusPolicy =
540             new HashMap<String, FocusRequester>();
541 
setFocusPolicy(IAudioPolicyCallback policy, boolean isTestFocusPolicy)542     void setFocusPolicy(IAudioPolicyCallback policy, boolean isTestFocusPolicy) {
543         if (policy == null) {
544             return;
545         }
546         synchronized (mAudioFocusLock) {
547             if (isTestFocusPolicy) {
548                 mPreviousFocusPolicy = mFocusPolicy;
549             }
550             mFocusPolicy = policy;
551         }
552     }
553 
unsetFocusPolicy(IAudioPolicyCallback policy, boolean isTestFocusPolicy)554     void unsetFocusPolicy(IAudioPolicyCallback policy, boolean isTestFocusPolicy) {
555         if (policy == null) {
556             return;
557         }
558         synchronized (mAudioFocusLock) {
559             if (mFocusPolicy == policy) {
560                 if (isTestFocusPolicy) {
561                     // restore the focus policy that was there before the focus policy test started
562                     mFocusPolicy = mPreviousFocusPolicy;
563                 } else {
564                     mFocusPolicy = null;
565                 }
566             }
567         }
568     }
569 
570     /**
571      * @param pcb non null
572      */
notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb)573     void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) {
574         final IAudioPolicyCallback pcb2 = pcb;
575         final Thread thread = new Thread() {
576             @Override
577             public void run() {
578                 synchronized(mAudioFocusLock) {
579                     if (mFocusStack.isEmpty()) {
580                         return;
581                     }
582                     try {
583                         pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(),
584                                 // top of focus stack always has focus
585                                 AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
586                     } catch (RemoteException e) {
587                         Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
588                                 + pcb2.asBinder(), e);
589                     }
590                 }
591             }
592         };
593         thread.start();
594     }
595 
596     /**
597      * Called synchronized on mAudioFocusLock
598      */
notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult)599     void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
600         for (IAudioPolicyCallback pcb : mFocusFollowers) {
601             try {
602                 // oneway
603                 pcb.notifyAudioFocusGrant(afi, requestResult);
604             } catch (RemoteException e) {
605                 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
606                         + pcb.asBinder(), e);
607             }
608         }
609     }
610 
611     /**
612      * Called synchronized on mAudioFocusLock
613      */
notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched)614     void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
615         for (IAudioPolicyCallback pcb : mFocusFollowers) {
616             try {
617                 // oneway
618                 pcb.notifyAudioFocusLoss(afi, wasDispatched);
619             } catch (RemoteException e) {
620                 Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback "
621                         + pcb.asBinder(), e);
622             }
623         }
624     }
625 
626     /**
627      * Called synchronized on mAudioFocusLock.
628      * Can only be called with an external focus policy installed (mFocusPolicy != null)
629      * @param afi
630      * @param fd
631      * @param cb binder of the focus requester
632      * @return true if the external audio focus policy (if any) can handle the focus request,
633      *     and false if there was any error handling the request (e.g. error talking to policy,
634      *     focus requester is already dead)
635      */
notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, IAudioFocusDispatcher fd, @NonNull IBinder cb)636     boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi,
637             IAudioFocusDispatcher fd, @NonNull IBinder cb) {
638         if (DEBUG) {
639             Log.v(TAG, "notifyExtFocusPolicyFocusRequest client="+afi.getClientId()
640             + " dispatcher=" + fd);
641         }
642         synchronized (mExtFocusChangeLock) {
643             afi.setGen(mExtFocusChangeCounter++);
644         }
645         final FocusRequester existingFr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
646         boolean keepTrack = false;
647         if (existingFr != null) {
648             if (!existingFr.hasSameDispatcher(fd)) {
649                 existingFr.release();
650                 keepTrack = true;
651             }
652         } else {
653             keepTrack = true;
654         }
655         if (keepTrack) {
656             final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb);
657             try {
658                 cb.linkToDeath(hdlr, 0);
659             } catch (RemoteException e) {
660                 // client has already died!
661                 return false;
662             }
663             // new focus (future) focus owner to keep track of
664             mFocusOwnersForFocusPolicy.put(afi.getClientId(),
665                     new FocusRequester(afi, fd, cb, hdlr, this));
666         }
667 
668         try {
669             //oneway
670             mFocusPolicy.notifyAudioFocusRequest(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
671             return true;
672         } catch (RemoteException e) {
673             Log.e(TAG, "Can't call notifyAudioFocusRequest() on IAudioPolicyCallback "
674                     + mFocusPolicy.asBinder(), e);
675         }
676         return false;
677     }
678 
setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult)679     void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult) {
680         synchronized (mExtFocusChangeLock) {
681             if (afi.getGen() > mExtFocusChangeCounter) {
682                 return;
683             }
684         }
685         final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
686         if (fr != null) {
687             fr.dispatchFocusResultFromExtPolicy(requestResult);
688         }
689     }
690 
691     /**
692      * Called synchronized on mAudioFocusLock
693      * @param afi
694      * @return true if the external audio focus policy (if any) is handling the focus request
695      */
notifyExtFocusPolicyFocusAbandon_syncAf(AudioFocusInfo afi)696     boolean notifyExtFocusPolicyFocusAbandon_syncAf(AudioFocusInfo afi) {
697         if (mFocusPolicy == null) {
698             return false;
699         }
700         final FocusRequester fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
701         if (fr != null) {
702             fr.release();
703         }
704         try {
705             //oneway
706             mFocusPolicy.notifyAudioFocusAbandon(afi);
707         } catch (RemoteException e) {
708             Log.e(TAG, "Can't call notifyAudioFocusAbandon() on IAudioPolicyCallback "
709                     + mFocusPolicy.asBinder(), e);
710         }
711         return true;
712     }
713 
714     /** see AudioManager.dispatchFocusChange(AudioFocusInfo afi, int focusChange, AudioPolicy ap) */
dispatchFocusChange(AudioFocusInfo afi, int focusChange)715     int dispatchFocusChange(AudioFocusInfo afi, int focusChange) {
716         if (DEBUG) {
717             Log.v(TAG, "dispatchFocusChange " + focusChange + " to afi client="
718                     + afi.getClientId());
719         }
720         synchronized (mAudioFocusLock) {
721             if (mFocusPolicy == null) {
722                 if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
723                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
724             }
725             final FocusRequester fr;
726             if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
727                 fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
728             } else {
729                 fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
730             }
731             if (fr == null) {
732                 if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
733                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
734             }
735             return fr.dispatchFocusChange(focusChange);
736         }
737     }
738 
dumpExtFocusPolicyFocusOwners(PrintWriter pw)739     private void dumpExtFocusPolicyFocusOwners(PrintWriter pw) {
740         final Set<Entry<String, FocusRequester>> owners = mFocusOwnersForFocusPolicy.entrySet();
741         final Iterator<Entry<String, FocusRequester>> ownerIterator = owners.iterator();
742         while (ownerIterator.hasNext()) {
743             final Entry<String, FocusRequester> owner = ownerIterator.next();
744             final FocusRequester fr = owner.getValue();
745             fr.dump(pw);
746         }
747     }
748 
getCurrentAudioFocus()749     protected int getCurrentAudioFocus() {
750         synchronized(mAudioFocusLock) {
751             if (mFocusStack.empty()) {
752                 return AudioManager.AUDIOFOCUS_NONE;
753             } else {
754                 return mFocusStack.peek().getGainRequest();
755             }
756         }
757     }
758 
759     /**
760      * Delay after entering ringing or call mode after which the framework will mute streams
761      * that are still playing.
762      */
763     private static final int RING_CALL_MUTING_ENFORCEMENT_DELAY_MS = 100;
764 
765     /**
766      * Usages to mute when the device rings or is in a call
767      */
768     private final static int[] USAGES_TO_MUTE_IN_RING_OR_CALL =
769         { AudioAttributes.USAGE_MEDIA, AudioAttributes.USAGE_GAME };
770 
771     /**
772      * Return the volume ramp time expected before playback with the given AudioAttributes would
773      * start after gaining audio focus.
774      * @param attr attributes of the sound about to start playing
775      * @return time in ms
776      */
getFocusRampTimeMs(int focusGain, AudioAttributes attr)777     protected static int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
778         switch (attr.getUsage()) {
779             case AudioAttributes.USAGE_MEDIA:
780             case AudioAttributes.USAGE_GAME:
781                 return 1000;
782             case AudioAttributes.USAGE_ALARM:
783             case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
784             case AudioAttributes.USAGE_ASSISTANT:
785             case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
786             case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
787             case AudioAttributes.USAGE_ANNOUNCEMENT:
788                 return 700;
789             case AudioAttributes.USAGE_VOICE_COMMUNICATION:
790             case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
791             case AudioAttributes.USAGE_NOTIFICATION:
792             case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
793             case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
794             case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
795             case AudioAttributes.USAGE_NOTIFICATION_EVENT:
796             case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
797             case AudioAttributes.USAGE_VEHICLE_STATUS:
798                 return 500;
799             case AudioAttributes.USAGE_EMERGENCY:
800             case AudioAttributes.USAGE_SAFETY:
801             case AudioAttributes.USAGE_UNKNOWN:
802             default:
803                 return 0;
804         }
805     }
806 
807     /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int)
808      * @param aa
809      * @param focusChangeHint
810      * @param cb
811      * @param fd
812      * @param clientId
813      * @param callingPackageName
814      * @param flags
815      * @param sdk
816      * @param forceDuck only true if
817      *     {@link android.media.AudioFocusRequest.Builder#setFocusGain(int)} was set to true for
818      *                  accessibility.
819      * @return
820      */
requestAudioFocus(@onNull AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName, int flags, int sdk, boolean forceDuck)821     protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
822             IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
823             int flags, int sdk, boolean forceDuck) {
824         new MediaMetrics.Item(mMetricsId)
825                 .setUid(Binder.getCallingUid())
826                 .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
827                 .set(MediaMetrics.Property.CLIENT_NAME, clientId)
828                 .set(MediaMetrics.Property.EVENT, "requestAudioFocus")
829                 .set(MediaMetrics.Property.FLAGS, flags)
830                 .set(MediaMetrics.Property.FOCUS_CHANGE_HINT,
831                         AudioManager.audioFocusToString(focusChangeHint))
832                 //.set(MediaMetrics.Property.SDK, sdk)
833                 .record();
834 
835         mEventLogger.log((new AudioEventLogger.StringEvent(
836                 "requestAudioFocus() from uid/pid " + Binder.getCallingUid()
837                     + "/" + Binder.getCallingPid()
838                     + " clientId=" + clientId + " callingPack=" + callingPackageName
839                     + " req=" + focusChangeHint
840                     + " flags=0x" + Integer.toHexString(flags)
841                     + " sdk=" + sdk))
842                 .printLog(TAG));
843         // we need a valid binder callback for clients
844         if (!cb.pingBinder()) {
845             Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
846             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
847         }
848 
849         if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
850                 callingPackageName) != AppOpsManager.MODE_ALLOWED) {
851             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
852         }
853 
854         synchronized(mAudioFocusLock) {
855             if (mFocusStack.size() > MAX_STACK_SIZE) {
856                 Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
857                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
858             }
859 
860             boolean enteringRingOrCall = !mRingOrCallActive
861                     & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
862             if (enteringRingOrCall) { mRingOrCallActive = true; }
863 
864             final AudioFocusInfo afiForExtPolicy;
865             if (mFocusPolicy != null) {
866                 // construct AudioFocusInfo as it will be communicated to audio focus policy
867                 afiForExtPolicy = new AudioFocusInfo(aa, Binder.getCallingUid(),
868                         clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
869                         flags, sdk);
870             } else {
871                 afiForExtPolicy = null;
872             }
873 
874             // handle delayed focus
875             boolean focusGrantDelayed = false;
876             if (!canReassignAudioFocus()) {
877                 if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
878                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
879                 } else {
880                     // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
881                     // granted right now, so the requester will be inserted in the focus stack
882                     // to receive focus later
883                     focusGrantDelayed = true;
884                 }
885             }
886 
887             // external focus policy?
888             if (mFocusPolicy != null) {
889                 if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) {
890                     // stop handling focus request here as it is handled by external audio
891                     // focus policy (return code will be handled in AudioManager)
892                     return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
893                 } else {
894                     // an error occured, client already dead, bail early
895                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
896                 }
897             }
898 
899             // handle the potential premature death of the new holder of the focus
900             // (premature death == death before abandoning focus)
901             // Register for client death notification
902             AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
903 
904             try {
905                 cb.linkToDeath(afdh, 0);
906             } catch (RemoteException e) {
907                 // client has already died!
908                 Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
909                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
910             }
911 
912             if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
913                 // if focus is already owned by this client and the reason for acquiring the focus
914                 // hasn't changed, don't do anything
915                 final FocusRequester fr = mFocusStack.peek();
916                 if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
917                     // unlink death handler so it can be gc'ed.
918                     // linkToDeath() creates a JNI global reference preventing collection.
919                     cb.unlinkToDeath(afdh, 0);
920                     notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
921                             AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
922                     return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
923                 }
924                 // the reason for the audio focus request has changed: remove the current top of
925                 // stack and respond as if we had a new focus owner
926                 if (!focusGrantDelayed) {
927                     mFocusStack.pop();
928                     // the entry that was "popped" is the same that was "peeked" above
929                     fr.release();
930                 }
931             }
932 
933             // focus requester might already be somewhere below in the stack, remove it
934             removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
935 
936             final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
937                     clientId, afdh, callingPackageName, Binder.getCallingUid(), this, sdk);
938 
939             if (mMultiAudioFocusEnabled
940                     && (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
941                 if (enteringRingOrCall) {
942                     if (!mMultiAudioFocusList.isEmpty()) {
943                         for (FocusRequester multifr : mMultiAudioFocusList) {
944                             multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck);
945                         }
946                     }
947                 } else {
948                     boolean needAdd = true;
949                     if (!mMultiAudioFocusList.isEmpty()) {
950                         for (FocusRequester multifr : mMultiAudioFocusList) {
951                             if (multifr.getClientUid() == Binder.getCallingUid()) {
952                                 needAdd = false;
953                                 break;
954                             }
955                         }
956                     }
957                     if (needAdd) {
958                         mMultiAudioFocusList.add(nfr);
959                     }
960                     nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
961                     notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
962                             AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
963                     return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
964                 }
965             }
966 
967             if (focusGrantDelayed) {
968                 // focusGrantDelayed being true implies we can't reassign focus right now
969                 // which implies the focus stack is not empty.
970                 final int requestResult = pushBelowLockedFocusOwners(nfr);
971                 if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
972                     notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
973                 }
974                 return requestResult;
975             } else {
976                 // propagate the focus change through the stack
977                 propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
978 
979                 // push focus requester at the top of the audio focus stack
980                 mFocusStack.push(nfr);
981                 nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
982             }
983             notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
984                     AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
985 
986             if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
987                 runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
988             }
989         }//synchronized(mAudioFocusLock)
990 
991         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
992     }
993 
994     /**
995      * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
996      * */
abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa, String callingPackageName)997     protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
998             String callingPackageName) {
999         new MediaMetrics.Item(mMetricsId)
1000                 .setUid(Binder.getCallingUid())
1001                 .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
1002                 .set(MediaMetrics.Property.CLIENT_NAME, clientId)
1003                 .set(MediaMetrics.Property.EVENT, "abandonAudioFocus")
1004                 .record();
1005 
1006         // AudioAttributes are currently ignored, to be used for zones / a11y
1007         mEventLogger.log((new AudioEventLogger.StringEvent(
1008                 "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
1009                     + "/" + Binder.getCallingPid()
1010                     + " clientId=" + clientId))
1011                 .printLog(TAG));
1012         try {
1013             // this will take care of notifying the new focus owner if needed
1014             synchronized(mAudioFocusLock) {
1015                 // external focus policy?
1016                 if (mFocusPolicy != null) {
1017                     final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),
1018                             clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,
1019                             0 /*flags*/, 0 /* sdk n/a here*/);
1020                     if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {
1021                         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
1022                     }
1023                 }
1024 
1025                 boolean exitingRingOrCall = mRingOrCallActive
1026                         & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
1027                 if (exitingRingOrCall) { mRingOrCallActive = false; }
1028 
1029                 removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
1030 
1031                 if (ENFORCE_MUTING_FOR_RING_OR_CALL & exitingRingOrCall) {
1032                     runAudioCheckerForRingOrCallAsync(false/*enteringRingOrCall*/);
1033                 }
1034             }
1035         } catch (java.util.ConcurrentModificationException cme) {
1036             // Catching this exception here is temporary. It is here just to prevent
1037             // a crash seen when the "Silent" notification is played. This is believed to be fixed
1038             // but this try catch block is left just to be safe.
1039             Log.e(TAG, "FATAL EXCEPTION AudioFocus  abandonAudioFocus() caused " + cme);
1040             cme.printStackTrace();
1041         }
1042 
1043         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
1044     }
1045 
1046 
unregisterAudioFocusClient(String clientId)1047     protected void unregisterAudioFocusClient(String clientId) {
1048         synchronized(mAudioFocusLock) {
1049             removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
1050         }
1051     }
1052 
runAudioCheckerForRingOrCallAsync(final boolean enteringRingOrCall)1053     private void runAudioCheckerForRingOrCallAsync(final boolean enteringRingOrCall) {
1054         new Thread() {
1055             public void run() {
1056                 if (enteringRingOrCall) {
1057                     try {
1058                         Thread.sleep(RING_CALL_MUTING_ENFORCEMENT_DELAY_MS);
1059                     } catch (InterruptedException e) {
1060                         e.printStackTrace();
1061                     }
1062                 }
1063                 synchronized (mAudioFocusLock) {
1064                     // since the new thread starting running the state could have changed, so
1065                     // we need to check again mRingOrCallActive, not enteringRingOrCall
1066                     if (mRingOrCallActive) {
1067                         mFocusEnforcer.mutePlayersForCall(USAGES_TO_MUTE_IN_RING_OR_CALL);
1068                     } else {
1069                         mFocusEnforcer.unmutePlayersForCall();
1070                     }
1071                 }
1072             }
1073         }.start();
1074     }
1075 
updateMultiAudioFocus(boolean enabled)1076     public void updateMultiAudioFocus(boolean enabled) {
1077         Log.d(TAG, "updateMultiAudioFocus( " + enabled + " )");
1078         mMultiAudioFocusEnabled = enabled;
1079         Settings.System.putInt(mContext.getContentResolver(),
1080                 Settings.System.MULTI_AUDIO_FOCUS_ENABLED, enabled ? 1 : 0);
1081         if (!mFocusStack.isEmpty()) {
1082             final FocusRequester fr = mFocusStack.peek();
1083             fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, false);
1084         }
1085         if (!enabled) {
1086             if (!mMultiAudioFocusList.isEmpty()) {
1087                 for (FocusRequester multifr : mMultiAudioFocusList) {
1088                     multifr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, false);
1089                 }
1090                 mMultiAudioFocusList.clear();
1091             }
1092         }
1093     }
1094 
getMultiAudioFocusEnabled()1095     public boolean getMultiAudioFocusEnabled() {
1096         return mMultiAudioFocusEnabled;
1097     }
1098 
dumpMultiAudioFocus(PrintWriter pw)1099     private void dumpMultiAudioFocus(PrintWriter pw) {
1100         pw.println("Multi Audio Focus enabled :" + mMultiAudioFocusEnabled);
1101         if (!mMultiAudioFocusList.isEmpty()) {
1102             pw.println("Multi Audio Focus List:");
1103             pw.println("------------------------------");
1104             for (FocusRequester multifr : mMultiAudioFocusList) {
1105                 multifr.dump(pw);
1106             }
1107             pw.println("------------------------------");
1108         }
1109     }
1110 }
1111