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