1 /*
2  * Copyright (C) 2015 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 package com.android.car.audio;
17 
18 import android.content.pm.PackageManager;
19 import android.media.AudioAttributes;
20 import android.media.AudioFocusInfo;
21 import android.media.AudioManager;
22 import android.media.audiopolicy.AudioPolicy;
23 import android.util.LocalLog;
24 import android.util.Log;
25 
26 import java.io.PrintWriter;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 
31 
32 public class CarAudioFocus extends AudioPolicy.AudioPolicyFocusListener {
33 
34     private static final String TAG = "CarAudioFocus";
35 
36     private static final int FOCUS_EVENT_LOGGER_QUEUE_SIZE = 25;
37 
38     private final AudioManager mAudioManager;
39     private final PackageManager mPackageManager;
40     private AudioPolicy mAudioPolicy; // Dynamically assigned just after construction
41 
42     private final LocalLog mFocusEventLogger;
43 
44     private final FocusInteraction mFocusInteraction;
45 
46     private final boolean mEnabledDelayedFocusRequest;
47     private AudioFocusInfo mDelayedRequest;
48 
49 
50     // We keep track of all the focus requesters in this map, with their clientId as the key.
51     // This is used both for focus dispatch and death handling
52     // Note that the clientId reflects the AudioManager instance and listener object (if any)
53     // so that one app can have more than one unique clientId by setting up distinct listeners.
54     // Because the listener gets only LOSS/GAIN messages, this is important for an app to do if
55     // it expects to request focus concurrently for different USAGEs so it knows which USAGE
56     // gained or lost focus at any given moment.  If the SAME listener is used for requests of
57     // different USAGE while the earlier request is still in the focus stack (whether holding
58     // focus or pending), the new request will be REJECTED so as to avoid any confusion about
59     // the meaning of subsequent GAIN/LOSS events (which would continue to apply to the focus
60     // request that was already active or pending).
61     private final HashMap<String, FocusEntry> mFocusHolders = new HashMap<>();
62     private final HashMap<String, FocusEntry> mFocusLosers = new HashMap<>();
63 
64     private final Object mLock = new Object();
65 
66 
CarAudioFocus(AudioManager audioManager, PackageManager packageManager, FocusInteraction focusInteraction, boolean enableDelayedFocusRequest)67     CarAudioFocus(AudioManager audioManager, PackageManager packageManager,
68             FocusInteraction focusInteraction, boolean enableDelayedFocusRequest) {
69         mAudioManager = audioManager;
70         mPackageManager = packageManager;
71         mFocusEventLogger = new LocalLog(FOCUS_EVENT_LOGGER_QUEUE_SIZE);
72         mFocusInteraction = focusInteraction;
73         mEnabledDelayedFocusRequest = enableDelayedFocusRequest;
74     }
75 
76 
77     // This has to happen after the construction to avoid a chicken and egg problem when setting up
78     // the AudioPolicy which must depend on this object.
setOwningPolicy(AudioPolicy parentPolicy)79     public void setOwningPolicy(AudioPolicy parentPolicy) {
80         mAudioPolicy = parentPolicy;
81     }
82 
83 
84     // This sends a focus loss message to the targeted requester.
sendFocusLossLocked(AudioFocusInfo loser, int lossType)85     private void sendFocusLossLocked(AudioFocusInfo loser, int lossType) {
86         int result = mAudioManager.dispatchAudioFocusChange(loser, lossType,
87                 mAudioPolicy);
88         if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
89             // TODO:  Is this actually an error, or is it okay for an entry in the focus stack
90             // to NOT have a listener?  If that's the case, should we even keep it in the focus
91             // stack?
92             Log.e(TAG, "Failure to signal loss of audio focus with error: " + result);
93         }
94 
95         logFocusEvent("sendFocusLoss for client " + loser.getClientId()
96                 + " with loss type " + focusEventToString(lossType)
97                 + " resulted in " + focusRequestResponseToString(result));
98     }
99 
100 
101     /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
102     // Note that we replicate most, but not all of the behaviors of the default MediaFocusControl
103     // engine as of Android P.
104     // Besides the interaction matrix which allows concurrent focus for multiple requestors, which
105     // is the reason for this module, we also treat repeated requests from the same clientId
106     // slightly differently.
107     // If a focus request for the same listener (clientId) is received while that listener is
108     // already in the focus stack, we REJECT it outright unless it is for the same USAGE.
109     // If it is for the same USAGE, we replace the old request with the new one.
110     // The default audio framework's behavior is to remove the previous entry in the stack (no-op
111     // if the requester is already holding focus).
evaluateFocusRequestLocked(AudioFocusInfo afi)112     private int evaluateFocusRequestLocked(AudioFocusInfo afi) {
113         Log.i(TAG, "Evaluating " + focusEventToString(afi.getGainRequest())
114                 + " request for client " + afi.getClientId()
115                 + " with usage " + afi.getAttributes().usageToString());
116 
117         // Is this a request for premanant focus?
118         // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -- Means Notifications should be denied
119         // AUDIOFOCUS_GAIN_TRANSIENT -- Means current focus holders should get transient loss
120         // AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -- Means other can duck (no loss message from us)
121         // NOTE:  We expect that in practice it will be permanent for all media requests and
122         //        transient for everything else, but that isn't currently an enforced requirement.
123         final boolean permanent =
124                 (afi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN);
125         final boolean allowDucking =
126                 (afi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
127 
128         boolean delayFocusForCurrentRequest = false;
129 
130         final int requestedContext = CarAudioContext.getContextForUsage(
131                 afi.getAttributes().getSystemUsage());
132 
133         // If we happen to find entries that this new request should replace, we'll store them here.
134         // This happens when a client makes a second AF request on the same listener.
135         // After we've granted audio focus to our current request, we'll abandon these requests.
136         FocusEntry replacedCurrentEntry = null;
137         FocusEntry replacedBlockedEntry = null;
138 
139         boolean allowDelayedFocus = mEnabledDelayedFocusRequest && canReceiveDelayedFocus(afi);
140 
141         // We don't allow sharing listeners (client IDs) between two concurrent requests
142         // (because the app would have no way to know to which request a later event applied)
143         if (mDelayedRequest != null && afi.getClientId().equals(mDelayedRequest.getClientId())) {
144             int delayedRequestedContext = CarAudioContext.getContextForUsage(
145                     mDelayedRequest.getAttributes().getSystemUsage());
146             // If it is for a different context then reject
147             if (delayedRequestedContext != requestedContext) {
148                 // Trivially reject a request for a different USAGE
149                 Log.e(TAG, String.format(
150                         "Client %s has already delayed requested focus for %s "
151                                 + "- cannot request focus for %s on same listener.",
152                         mDelayedRequest.getClientId(),
153                         mDelayedRequest.getAttributes().usageToString(),
154                         afi.getAttributes().usageToString()));
155                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
156             }
157         }
158 
159         // Scan all active and pending focus requests.  If any should cause rejection of
160         // this new request, then we're done.  Keep a list of those against whom we're exclusive
161         // so we can update the relationships if/when we are sure we won't get rejected.
162         Log.i(TAG, "Scanning focus holders...");
163         final ArrayList<FocusEntry> losers = new ArrayList<FocusEntry>();
164         for (FocusEntry entry : mFocusHolders.values()) {
165             Log.d(TAG, "Evaluating focus holder: " + entry.getClientId());
166 
167             // If this request is for Notifications and a current focus holder has specified
168             // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request.
169             // This matches the hardwired behavior in the default audio policy engine which apps
170             // might expect (The interaction matrix doesn't have any provision for dealing with
171             // override flags like this).
172             if ((requestedContext == CarAudioContext.NOTIFICATION)
173                     && (entry.getAudioFocusInfo().getGainRequest()
174                     == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
175                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
176             }
177 
178             // We don't allow sharing listeners (client IDs) between two concurrent requests
179             // (because the app would have no way to know to which request a later event applied)
180             if (afi.getClientId().equals(entry.getAudioFocusInfo().getClientId())) {
181                 if (entry.getAudioContext() == requestedContext) {
182                     // This is a request from a current focus holder.
183                     // Abandon the previous request (without sending a LOSS notification to it),
184                     // and don't check the interaction matrix for it.
185                     Log.i(TAG, "Replacing accepted request from same client");
186                     replacedCurrentEntry = entry;
187                     continue;
188                 } else {
189                     // Trivially reject a request for a different USAGE
190                     Log.e(TAG, String.format(
191                             "Client %s has already requested focus for %s - cannot request focus "
192                                     + "for %s on same listener.",
193                             entry.getClientId(),
194                             entry.getAudioFocusInfo().getAttributes().usageToString(),
195                             afi.getAttributes().usageToString()));
196                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
197                 }
198             }
199 
200             @AudioManager.FocusRequestResult int interactionResult = mFocusInteraction
201                     .evaluateRequest(requestedContext, entry, losers, allowDucking,
202                             allowDelayedFocus);
203             if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
204                 return interactionResult;
205             }
206             if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
207                 delayFocusForCurrentRequest = true;
208             }
209         }
210         Log.i(TAG, "Scanning those who've already lost focus...");
211         final ArrayList<FocusEntry> blocked = new ArrayList<FocusEntry>();
212         for (FocusEntry entry : mFocusLosers.values()) {
213             Log.i(TAG, entry.getAudioFocusInfo().getClientId());
214 
215             // If this request is for Notifications and a pending focus holder has specified
216             // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request
217             if ((requestedContext == CarAudioContext.NOTIFICATION)
218                     && (entry.getAudioFocusInfo().getGainRequest()
219                     == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
220                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
221             }
222 
223             // We don't allow sharing listeners (client IDs) between two concurrent requests
224             // (because the app would have no way to know to which request a later event applied)
225             if (afi.getClientId().equals(entry.getAudioFocusInfo().getClientId())) {
226                 if (entry.getAudioContext() == requestedContext) {
227                     // This is a repeat of a request that is currently blocked.
228                     // Evaluate it as if it were a new request, but note that we should remove
229                     // the old pending request, and move it.
230                     // We do not want to evaluate the new request against itself.
231                     Log.i(TAG, "Replacing pending request from same client");
232                     replacedBlockedEntry = entry;
233                     continue;
234                 } else {
235                     // Trivially reject a request for a different USAGE
236                     Log.e(TAG, String.format(
237                             "Client %s has already requested focus for %s - cannot request focus "
238                                     + "for %s on same listener.",
239                             entry.getClientId(),
240                             entry.getAudioFocusInfo().getAttributes().usageToString(),
241                             afi.getAttributes().usageToString()));
242                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
243                 }
244             }
245 
246             @AudioManager.FocusRequestResult int interactionResult = mFocusInteraction
247                     .evaluateRequest(requestedContext, entry, blocked, allowDucking,
248                             allowDelayedFocus);
249             if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
250                 return interactionResult;
251             }
252             if (interactionResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
253                 delayFocusForCurrentRequest = true;
254             }
255         }
256 
257 
258         // Now that we've decided we'll grant focus, construct our new FocusEntry
259         FocusEntry newEntry = new FocusEntry(afi, requestedContext, mPackageManager);
260 
261         // These entries have permanently lost focus as a result of this request, so they
262         // should be removed from all blocker lists.
263         ArrayList<FocusEntry> permanentlyLost = new ArrayList<>();
264 
265         if (replacedCurrentEntry != null) {
266             mFocusHolders.remove(replacedCurrentEntry.getClientId());
267             permanentlyLost.add(replacedCurrentEntry);
268         }
269         if (replacedBlockedEntry != null) {
270             mFocusLosers.remove(replacedBlockedEntry.getClientId());
271             permanentlyLost.add(replacedBlockedEntry);
272         }
273 
274 
275         // Now that we're sure we'll accept this request, update any requests which we would
276         // block but are already out of focus but waiting to come back
277         for (FocusEntry entry : blocked) {
278             // If we're out of focus it must be because somebody is blocking us
279             assert !entry.isUnblocked();
280 
281             if (permanent) {
282                 // This entry has now lost focus forever
283                 sendFocusLossLocked(entry.getAudioFocusInfo(), AudioManager.AUDIOFOCUS_LOSS);
284                 entry.setDucked(false);
285                 final FocusEntry deadEntry = mFocusLosers.remove(
286                         entry.getAudioFocusInfo().getClientId());
287                 assert deadEntry != null;
288                 permanentlyLost.add(entry);
289             } else {
290                 if (!allowDucking && entry.isDucked()) {
291                     // This entry was previously allowed to duck, but can no longer do so.
292                     Log.i(TAG, "Converting duckable loss to non-duckable for "
293                             + entry.getClientId());
294                     sendFocusLossLocked(entry.getAudioFocusInfo(),
295                             AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
296                     entry.setDucked(false);
297                 }
298                 // Note that this new request is yet one more reason we can't (yet) have focus
299                 entry.addBlocker(newEntry);
300             }
301         }
302 
303         // Notify and update any requests which are now losing focus as a result of the new request
304         for (FocusEntry entry : losers) {
305             // If we have focus (but are about to loose it), nobody should be blocking us yet
306             assert entry.isUnblocked();
307 
308             int lossType;
309             if (permanent) {
310                 lossType = AudioManager.AUDIOFOCUS_LOSS;
311             } else if (allowDucking && entry.receivesDuckEvents()) {
312                 lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
313                 entry.setDucked(true);
314             } else {
315                 lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
316             }
317             sendFocusLossLocked(entry.getAudioFocusInfo(), lossType);
318 
319             // The entry no longer holds focus, so take it out of the holders list
320             mFocusHolders.remove(entry.getAudioFocusInfo().getClientId());
321 
322             if (permanent) {
323                 permanentlyLost.add(entry);
324             } else {
325                 // Add ourselves to the list of requests waiting to get focus back and
326                 // note why we lost focus so we can tell when it's time to get it back
327                 mFocusLosers.put(entry.getAudioFocusInfo().getClientId(), entry);
328                 entry.addBlocker(newEntry);
329             }
330         }
331 
332         // Now that all new blockers have been added, clear out any other requests that have been
333         // permanently lost as a result of this request. Treat them as abandoned - if they're on
334         // any blocker lists, remove them. If any focus requests become unblocked as a result,
335         // re-grant them. (This can happen when a GAIN_TRANSIENT_MAY_DUCK request replaces a
336         // GAIN_TRANSIENT request from the same listener.)
337         for (FocusEntry entry : permanentlyLost) {
338             Log.d(TAG, "Cleaning up entry " + entry.getClientId());
339             removeBlockerAndRestoreUnblockedWaitersLocked(entry);
340         }
341 
342         // Finally, add the request we're granting to the focus holders' list
343         if (delayFocusForCurrentRequest) {
344             swapDelayedAudioFocusRequestLocked(afi);
345             return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
346         }
347 
348         mFocusHolders.put(afi.getClientId(), newEntry);
349 
350         Log.i(TAG, "AUDIOFOCUS_REQUEST_GRANTED");
351         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
352     }
353 
354     @Override
onAudioFocusRequest(AudioFocusInfo afi, int requestResult)355     public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
356         int response;
357         AudioPolicy policy;
358         AudioFocusInfo replacedDelayedAudioFocusInfo = null;
359         synchronized (mLock) {
360             policy = mAudioPolicy;
361             response = evaluateFocusRequestLocked(afi);
362         }
363 
364         // Post our reply for delivery to the original focus requester
365         mAudioManager.setFocusRequestResult(afi, response, policy);
366         logFocusEvent("onAudioFocusRequest for client " + afi.getClientId()
367                 + " with gain type " + focusEventToString(afi.getGainRequest())
368                 + " resulted in " + focusRequestResponseToString(response));
369     }
370 
swapDelayedAudioFocusRequestLocked(AudioFocusInfo afi)371     private void swapDelayedAudioFocusRequestLocked(AudioFocusInfo afi) {
372         // If we are swapping to a different client then send the focus loss signal
373         if (mDelayedRequest != null
374                 && !afi.getClientId().equals(mDelayedRequest.getClientId())) {
375             sendFocusLossLocked(mDelayedRequest, AudioManager.AUDIOFOCUS_LOSS);
376         }
377         mDelayedRequest = afi;
378     }
379 
canReceiveDelayedFocus(AudioFocusInfo afi)380     private boolean canReceiveDelayedFocus(AudioFocusInfo afi) {
381         if (afi.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN) {
382             return false;
383         }
384         return (afi.getFlags() & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK)
385             == AudioManager.AUDIOFOCUS_FLAG_DELAY_OK;
386     }
387 
388     /**
389      * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
390      * Note that we'll get this call for a focus holder that dies while in the focus stack, so
391      * we don't need to watch for death notifications directly.
392      * */
393     @Override
onAudioFocusAbandon(AudioFocusInfo afi)394     public void onAudioFocusAbandon(AudioFocusInfo afi) {
395         logFocusEvent("onAudioFocusAbandon for client " + afi.getClientId());
396         synchronized (mLock) {
397             FocusEntry deadEntry = removeFocusEntryLocked(afi);
398 
399             if (deadEntry != null) {
400                 removeBlockerAndRestoreUnblockedWaitersLocked(deadEntry);
401             } else {
402                 removeDelayedAudioFocusRequestLocked(afi);
403             }
404         }
405     }
406 
removeDelayedAudioFocusRequestLocked(AudioFocusInfo afi)407     private void removeDelayedAudioFocusRequestLocked(AudioFocusInfo afi) {
408         if (mDelayedRequest != null && afi.getClientId().equals(mDelayedRequest.getClientId())) {
409             mDelayedRequest = null;
410         }
411     }
412 
413     /**
414      * Remove Focus entry from focus holder or losers entry lists
415      * @param afi Audio Focus Info to remove
416      * @return Removed Focus Entry
417      */
removeFocusEntryLocked(AudioFocusInfo afi)418     private FocusEntry removeFocusEntryLocked(AudioFocusInfo afi) {
419         Log.i(TAG, "removeFocusEntry " + afi.getClientId());
420 
421         // Remove this entry from our active or pending list
422         FocusEntry deadEntry = mFocusHolders.remove(afi.getClientId());
423         if (deadEntry == null) {
424             deadEntry = mFocusLosers.remove(afi.getClientId());
425             if (deadEntry == null) {
426                 // Caller is providing an unrecognzied clientId!?
427                 Log.w(TAG, "Audio focus abandoned by unrecognized client id: " + afi.getClientId());
428                 // This probably means an app double released focused for some reason.  One
429                 // harmless possibility is a race between an app being told it lost focus and the
430                 // app voluntarily abandoning focus.  More likely the app is just sloppy.  :)
431                 // The more nefarious possibility is that the clientId is actually corrupted
432                 // somehow, in which case we might have a real focus entry that we're going to fail
433                 // to remove. If that were to happen, I'd expect either the app to swallow it
434                 // silently, or else take unexpected action (eg: resume playing spontaneously), or
435                 // else to see "Failure to signal ..." gain/loss error messages in the log from
436                 // this module when a focus change tries to take action on a truly zombie entry.
437             }
438         }
439         return deadEntry;
440     }
441 
removeBlockerAndRestoreUnblockedWaitersLocked(FocusEntry deadEntry)442     private void removeBlockerAndRestoreUnblockedWaitersLocked(FocusEntry deadEntry) {
443         attemptToGainFocusForDelayedAudioFocusRequest();
444         removeBlockerAndRestoreUnblockedFocusLosersLocked(deadEntry);
445     }
446 
attemptToGainFocusForDelayedAudioFocusRequest()447     private void attemptToGainFocusForDelayedAudioFocusRequest() {
448         if (!mEnabledDelayedFocusRequest || mDelayedRequest == null) {
449             return;
450         }
451         int delayedFocusRequestResults = evaluateFocusRequestLocked(mDelayedRequest);
452         if (delayedFocusRequestResults == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
453             FocusEntry focusEntry = mFocusHolders.get(mDelayedRequest.getClientId());
454             mDelayedRequest = null;
455             if (dispatchFocusGainedLocked(focusEntry.getAudioFocusInfo())
456                     == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
457                 Log.e(TAG,
458                         "Failure to signal gain of audio focus gain for "
459                                 + "delayed focus clientId " + focusEntry.getClientId());
460                 mFocusHolders.remove(focusEntry.getClientId());
461                 removeBlockerFromBlockedFocusLosersLocked(focusEntry);
462                 sendFocusLossLocked(focusEntry.getAudioFocusInfo(),
463                         AudioManager.AUDIOFOCUS_LOSS);
464                 logFocusEvent("Did not gained delayed audio focus for "
465                         + focusEntry.getClientId());
466             }
467         }
468     }
469 
470     /**
471      * Removes the dead entry from blocked waiters but does not send focus gain signal
472      */
removeBlockerFromBlockedFocusLosersLocked(FocusEntry deadEntry)473     private void removeBlockerFromBlockedFocusLosersLocked(FocusEntry deadEntry) {
474         // Remove this entry from the blocking list of any pending requests
475         Iterator<FocusEntry> it = mFocusLosers.values().iterator();
476         while (it.hasNext()) {
477             FocusEntry entry = it.next();
478             // Remove the retiring entry from all blocker lists
479             entry.removeBlocker(deadEntry);
480         }
481     }
482 
483     /**
484      * Removes the dead entry from blocked waiters and sends focus gain signal
485      */
removeBlockerAndRestoreUnblockedFocusLosersLocked(FocusEntry deadEntry)486     private void removeBlockerAndRestoreUnblockedFocusLosersLocked(FocusEntry deadEntry) {
487         // Remove this entry from the blocking list of any pending requests
488         Iterator<FocusEntry> it = mFocusLosers.values().iterator();
489         while (it.hasNext()) {
490             FocusEntry entry = it.next();
491 
492             // Remove the retiring entry from all blocker lists
493             entry.removeBlocker(deadEntry);
494 
495             // Any entry whose blocking list becomes empty should regain focus
496             if (entry.isUnblocked()) {
497                 Log.i(TAG, "Restoring unblocked entry " + entry.getClientId());
498                 // Pull this entry out of the focus losers list
499                 it.remove();
500 
501                 // Add it back into the focus holders list
502                 mFocusHolders.put(entry.getClientId(), entry);
503 
504                 dispatchFocusGainedLocked(entry.getAudioFocusInfo());
505             }
506         }
507     }
508 
509     /**
510      * Dispatch focus gain
511      * @param afi Audio focus info
512      * @return AudioManager.AUDIOFOCUS_REQUEST_GRANTED if focus is dispatched successfully
513      */
dispatchFocusGainedLocked(AudioFocusInfo afi)514     private int dispatchFocusGainedLocked(AudioFocusInfo afi) {
515         // Send the focus (re)gain notification
516         int result = mAudioManager.dispatchAudioFocusChange(
517                 afi,
518                 afi.getGainRequest(),
519                 mAudioPolicy);
520         if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
521             // TODO:  Is this actually an error, or is it okay for an entry in the focus
522             // stack to NOT have a listener?  If that's the case, should we even keep
523             // it in the focus stack?
524             Log.e(TAG, "Failure to signal gain of audio focus with error: " + result);
525         }
526 
527         logFocusEvent("dispatchFocusGainedLocked for client " + afi.getClientId()
528                         + " with gain type " + focusEventToString(afi.getGainRequest())
529                         + " resulted in " + focusRequestResponseToString(result));
530         return result;
531     }
532 
533     /**
534      * Query the current list of focus loser for uid
535      * @param uid uid to query current focus loser
536      * @return list of current focus losers for uid
537      */
getAudioFocusLosersForUid(int uid)538     ArrayList<AudioFocusInfo> getAudioFocusLosersForUid(int uid) {
539         return getAudioFocusListForUid(uid, mFocusLosers);
540     }
541 
542     /**
543      * Query the current list of focus holders for uid
544      * @param uid uid to query current focus holders
545      * @return list of current focus holders that for uid
546      */
getAudioFocusHoldersForUid(int uid)547     ArrayList<AudioFocusInfo> getAudioFocusHoldersForUid(int uid) {
548         return getAudioFocusListForUid(uid, mFocusHolders);
549     }
550 
551     /**
552      * Query input list for matching uid
553      * @param uid uid to match in map
554      * @param mapToQuery map to query for uid info
555      * @return list of audio focus info that match uid
556      */
getAudioFocusListForUid(int uid, HashMap<String, FocusEntry> mapToQuery)557     private ArrayList<AudioFocusInfo> getAudioFocusListForUid(int uid,
558             HashMap<String, FocusEntry> mapToQuery) {
559         ArrayList<AudioFocusInfo> matchingInfoList = new ArrayList<>();
560         synchronized (mLock) {
561             for (String clientId : mapToQuery.keySet()) {
562                 AudioFocusInfo afi = mapToQuery.get(clientId).getAudioFocusInfo();
563                 if (afi.getClientUid() == uid) {
564                     matchingInfoList.add(afi);
565                 }
566             }
567         }
568         return matchingInfoList;
569     }
570 
571     /**
572      * Remove the audio focus info, if entry is still active
573      * dispatch lose focus transient to listeners
574      * @param afi Audio Focus info to remove
575      */
removeAudioFocusInfoAndTransientlyLoseFocus(AudioFocusInfo afi)576     void removeAudioFocusInfoAndTransientlyLoseFocus(AudioFocusInfo afi) {
577         synchronized (mLock) {
578             FocusEntry deadEntry = removeFocusEntryLocked(afi);
579             if (deadEntry != null) {
580                 sendFocusLossLocked(deadEntry.getAudioFocusInfo(),
581                         AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
582                 removeBlockerAndRestoreUnblockedWaitersLocked(deadEntry);
583             }
584         }
585     }
586 
587     /**
588      * Reevaluate focus request and regain focus
589      * @param afi audio focus info to reevaluate
590      * @return AudioManager.AUDIOFOCUS_REQUEST_GRANTED if focus is granted
591      */
reevaluateAndRegainAudioFocus(AudioFocusInfo afi)592     int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) {
593         int results;
594         synchronized (mLock) {
595             results = evaluateFocusRequestLocked(afi);
596             if (results == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
597                 results = dispatchFocusGainedLocked(afi);
598             }
599         }
600 
601         return results;
602     }
603 
604     /**
605      * dumps the current state of the CarAudioFocus object
606      * @param indent indent to add to each line in the current stream
607      * @param writer stream to write to
608      */
dump(String indent, PrintWriter writer)609     public void dump(String indent, PrintWriter writer) {
610         synchronized (mLock) {
611             writer.printf("%s*CarAudioFocus*\n", indent);
612             String innerIndent = indent + "\t";
613             String focusIndent = innerIndent + "\t";
614             mFocusInteraction.dump(innerIndent, writer);
615 
616             writer.printf("%sCurrent Focus Holders:\n", innerIndent);
617             for (String clientId : mFocusHolders.keySet()) {
618                 mFocusHolders.get(clientId).dump(focusIndent, writer);
619             }
620 
621             writer.printf("%sTransient Focus Losers:\n", innerIndent);
622             for (String clientId : mFocusLosers.keySet()) {
623                 mFocusLosers.get(clientId).dump(focusIndent, writer);
624             }
625 
626             writer.printf("%sQueued Delayed Focus: %s\n", innerIndent,
627                     mDelayedRequest == null ? "None" : mDelayedRequest.getClientId());
628 
629             writer.printf("%sFocus Events:\n", innerIndent);
630             mFocusEventLogger.dump(innerIndent + "\t", writer);
631         }
632     }
633 
focusEventToString(int focusEvent)634     private static String focusEventToString(int focusEvent) {
635         switch (focusEvent) {
636             case AudioManager.AUDIOFOCUS_GAIN:
637                 return "GAIN";
638             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
639                 return "GAIN_TRANSIENT";
640             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
641                 return "GAIN_TRANSIENT_EXCLUSIVE";
642             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
643                 return "GAIN_TRANSIENT_MAY_DUCK";
644             case AudioManager.AUDIOFOCUS_LOSS:
645                 return "LOSS";
646             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
647                 return "LOSS_TRANSIENT";
648             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
649                 return "LOSS_TRANSIENT_CAN_DUCK";
650             default:
651                 return "unknown event " + focusEvent;
652         }
653     }
654 
focusRequestResponseToString(int response)655     private static String focusRequestResponseToString(int response) {
656         if (response == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
657             return "REQUEST_GRANTED";
658         } else if (response == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
659             return "REQUEST_FAILED";
660         }
661         return "REQUEST_DELAYED";
662     }
663 
logFocusEvent(String log)664     private void logFocusEvent(String log) {
665         mFocusEventLogger.log(log);
666         Log.i(TAG, log);
667     }
668 
669     /**
670      * Returns the focus interaction for this car focus instance.
671      */
getFocusInteraction()672     public FocusInteraction getFocusInteraction() {
673         return mFocusInteraction;
674     }
675 }
676