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