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