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 android.car.builtin.media.AudioManagerHelper.isCallFocusRequestClientId; 19 import static android.car.builtin.media.AudioManagerHelper.usageToString; 20 import static android.car.oem.CarAudioFeaturesInfo.AUDIO_FEATURE_FADE_MANAGER_CONFIGS; 21 import static android.media.AudioManager.AUDIOFOCUS_FLAG_DELAY_OK; 22 import static android.media.AudioManager.AUDIOFOCUS_GAIN; 23 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT; 24 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE; 25 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK; 26 import static android.media.AudioManager.AUDIOFOCUS_LOSS; 27 import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; 28 import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; 29 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED; 30 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED; 31 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 32 33 import static com.android.car.audio.CarAudioContext.isCriticalAudioAudioAttribute; 34 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 35 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 36 37 import android.annotation.Nullable; 38 import android.annotation.UserIdInt; 39 import android.car.builtin.os.TraceHelper; 40 import android.car.builtin.util.Slogf; 41 import android.car.builtin.util.TimingsTraceLog; 42 import android.car.media.CarVolumeGroupInfo; 43 import android.car.oem.AudioFocusEntry; 44 import android.car.oem.CarAudioFadeConfiguration; 45 import android.car.oem.CarAudioFeaturesInfo; 46 import android.car.oem.OemCarAudioFocusEvaluationRequest; 47 import android.car.oem.OemCarAudioFocusResult; 48 import android.content.pm.PackageManager; 49 import android.media.AudioAttributes; 50 import android.media.AudioFocusInfo; 51 import android.media.AudioManager; 52 import android.media.FadeManagerConfiguration; 53 import android.media.audiopolicy.AudioPolicy; 54 import android.os.UserHandle; 55 import android.util.ArrayMap; 56 import android.util.Log; 57 import android.util.proto.ProtoOutputStream; 58 59 import com.android.car.CarLocalServices; 60 import com.android.car.CarLog; 61 import com.android.car.audio.CarAudioDumpProto.CarAudioZoneFocusProto; 62 import com.android.car.audio.CarAudioDumpProto.CarAudioZoneFocusProto.CarAudioFocusProto; 63 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 64 import com.android.car.internal.util.IndentingPrintWriter; 65 import com.android.car.internal.util.LocalLog; 66 import com.android.car.oem.CarOemProxyService; 67 import com.android.internal.annotations.GuardedBy; 68 69 import java.util.ArrayList; 70 import java.util.Iterator; 71 import java.util.List; 72 import java.util.Map; 73 import java.util.Objects; 74 75 class CarAudioFocus extends AudioPolicy.AudioPolicyFocusListener { 76 77 private static final String TAG = CarLog.tagFor(CarAudioFocus.class); 78 79 private static final int FOCUS_EVENT_LOGGER_QUEUE_SIZE = 25; 80 81 private final AudioManagerWrapper mAudioManager; 82 private final PackageManager mPackageManager; 83 private final CarVolumeInfoWrapper mCarVolumeInfoWrapper; 84 @Nullable 85 private final CarAudioFeaturesInfo mAudioFeaturesInfo; 86 private AudioPolicy mAudioPolicy; // Dynamically assigned just after construction 87 88 private final LocalLog mFocusEventLogger; 89 90 private final FocusInteraction mFocusInteraction; 91 92 private final CarAudioContext mCarAudioContext; 93 94 private final CarAudioZone mCarAudioZone; 95 96 97 private AudioFocusInfo mDelayedRequest; 98 99 // We keep track of all the focus requesters in this map, with their clientId as the key. 100 // This is used both for focus dispatch and death handling 101 // Note that the clientId reflects the AudioManager instance and listener object (if any) 102 // so that one app can have more than one unique clientId by setting up distinct listeners. 103 // Because the listener gets only LOSS/GAIN messages, this is important for an app to do if 104 // it expects to request focus concurrently for different USAGEs so it knows which USAGE 105 // gained or lost focus at any given moment. If the SAME listener is used for requests of 106 // different USAGE while the earlier request is still in the focus stack (whether holding 107 // focus or pending), the new request will be REJECTED so as to avoid any confusion about 108 // the meaning of subsequent GAIN/LOSS events (which would continue to apply to the focus 109 // request that was already active or pending). 110 @GuardedBy("mLock") 111 private final ArrayMap<String, FocusEntry> mFocusHolders = new ArrayMap<>(); 112 @GuardedBy("mLock") 113 private final ArrayMap<String, FocusEntry> mFocusLosers = new ArrayMap<>(); 114 115 private final Object mLock = new Object(); 116 117 @GuardedBy("mLock") 118 private boolean mIsFocusRestricted; 119 CarAudioFocus(AudioManagerWrapper audioManager, PackageManager packageManager, FocusInteraction focusInteraction, CarAudioZone carAudioZone, CarVolumeInfoWrapper volumeInfoWrapper, @Nullable CarAudioFeaturesInfo features)120 CarAudioFocus(AudioManagerWrapper audioManager, PackageManager packageManager, 121 FocusInteraction focusInteraction, CarAudioZone carAudioZone, 122 CarVolumeInfoWrapper volumeInfoWrapper, @Nullable CarAudioFeaturesInfo features) { 123 mAudioManager = Objects.requireNonNull(audioManager, "Audio manager can not be null"); 124 mPackageManager = Objects.requireNonNull(packageManager, "Package manager can not null"); 125 mFocusEventLogger = new LocalLog(FOCUS_EVENT_LOGGER_QUEUE_SIZE); 126 mFocusInteraction = Objects.requireNonNull(focusInteraction, 127 "Focus interactions can not be null"); 128 mCarAudioZone = Objects.requireNonNull(carAudioZone, "Car audio zone can not be null"); 129 mCarAudioContext = Objects.requireNonNull(mCarAudioZone.getCarAudioContext(), 130 "Car audio context can not be null"); 131 mCarVolumeInfoWrapper = Objects.requireNonNull(volumeInfoWrapper, 132 "Car volume info can not be null"); 133 mAudioFeaturesInfo = features; 134 } 135 136 // This has to happen after the construction to avoid a chicken and egg problem when setting up 137 // the AudioPolicy which must depend on this object. setOwningPolicy(AudioPolicy parentPolicy)138 public void setOwningPolicy(AudioPolicy parentPolicy) { 139 mAudioPolicy = parentPolicy; 140 } 141 setRestrictFocus(boolean isFocusRestricted)142 void setRestrictFocus(boolean isFocusRestricted) { 143 logFocusEvent("setRestrictFocus: is focus restricted " + isFocusRestricted); 144 synchronized (mLock) { 145 mIsFocusRestricted = isFocusRestricted; 146 if (mIsFocusRestricted) { 147 abandonNonCriticalFocusLocked(); 148 } 149 } 150 } 151 152 @GuardedBy("mLock") abandonNonCriticalFocusLocked()153 private void abandonNonCriticalFocusLocked() { 154 if (mDelayedRequest != null) { 155 if (!isCriticalAudioAudioAttribute(mDelayedRequest.getAttributes())) { 156 logFocusEvent( 157 "abandonNonCriticalFocusLocked abandoning non critical delayed request " 158 + mDelayedRequest); 159 sendFocusLossLocked(mDelayedRequest, AUDIOFOCUS_LOSS, /* winner= */ null, 160 /* shouldFade= */ false, /* transientFadeManagerConfig= */ null); 161 mDelayedRequest = null; 162 } else { 163 logFocusEvent("abandonNonCriticalFocusLocked keeping critical delayed request " 164 + mDelayedRequest); 165 } 166 } 167 168 abandonNonCriticalEntriesLocked(mFocusLosers); 169 abandonNonCriticalEntriesLocked(mFocusHolders); 170 } 171 172 @GuardedBy("mLock") abandonNonCriticalEntriesLocked(Map<String, FocusEntry> entries)173 private void abandonNonCriticalEntriesLocked(Map<String, FocusEntry> entries) { 174 List<String> clientsToRemove = new ArrayList<>(); 175 for (FocusEntry holderEntry : entries.values()) { 176 if (isCriticalAudioAudioAttribute(holderEntry.getAudioFocusInfo().getAttributes())) { 177 Slogf.i(TAG, "abandonNonCriticalEntriesLocked keeping critical focus " 178 + holderEntry); 179 continue; 180 } 181 182 sendFocusLossLocked(holderEntry.getAudioFocusInfo(), AUDIOFOCUS_LOSS, 183 /* winner= */ null, /* shouldFade= */ false, 184 /* transientFadeManagerConfig= */ null); 185 clientsToRemove.add(holderEntry.getAudioFocusInfo().getClientId()); 186 } 187 188 for (int i = 0; i < clientsToRemove.size(); i++) { 189 String clientId = clientsToRemove.get(i); 190 FocusEntry removedEntry = entries.remove(clientId); 191 removeBlockerAndRestoreUnblockedWaitersLocked(removedEntry); 192 } 193 } 194 195 // This sends a focus loss message to the targeted requester. 196 @GuardedBy("mLock") sendFocusLossLocked(AudioFocusInfo loser, int lossType, AudioFocusInfo winner, boolean shouldFade, FadeManagerConfiguration transientFadeManagerConfig)197 private void sendFocusLossLocked(AudioFocusInfo loser, int lossType, AudioFocusInfo winner, 198 boolean shouldFade, FadeManagerConfiguration transientFadeManagerConfig) { 199 int result; 200 if (isFadeManagerSupported() && shouldFade) { 201 List<AudioFocusInfo> otherActiveAfis = getAudioFocusInfos(mFocusHolders); 202 // remove the losing clients audio focus info from the list 203 otherActiveAfis.remove(loser); 204 // if not yet added (or not present already), add the winning clients audio focus info 205 // to the list 206 if (winner != null && !otherActiveAfis.contains(winner)) { 207 otherActiveAfis.add(winner); 208 } 209 result = mAudioManager.dispatchAudioFocusChangeWithFade(loser, lossType, mAudioPolicy, 210 otherActiveAfis, transientFadeManagerConfig); 211 } else { 212 result = mAudioManager.dispatchAudioFocusChange(loser, lossType, mAudioPolicy); 213 } 214 215 if (result == AUDIOFOCUS_REQUEST_FAILED) { 216 // TODO: Is this actually an error, or is it okay for an entry in the focus stack 217 // to NOT have a listener? If that's the case, should we even keep it in the focus 218 // stack? 219 Slogf.e(TAG, "Failure to signal loss of audio focus with error: " + result); 220 } 221 222 logFocusEvent("sendFocusLoss for client " + loser.getClientId() 223 + " with loss type " + focusEventToString(lossType) 224 + " resulted in " + focusRequestResponseToString(result)); 225 } 226 isFadeManagerSupported()227 private boolean isFadeManagerSupported() { 228 return mAudioFeaturesInfo != null && mAudioFeaturesInfo.isAudioFeatureEnabled( 229 AUDIO_FEATURE_FADE_MANAGER_CONFIGS); 230 } 231 232 /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */ 233 // Note that we replicate most, but not all of the behaviors of the default MediaFocusControl 234 // engine as of Android P. 235 // Besides the interaction matrix which allows concurrent focus for multiple requestors, which 236 // is the reason for this module, we also treat repeated requests from the same clientId 237 // slightly differently. 238 // If a focus request for the same listener (clientId) is received while that listener is 239 // already in the focus stack, we REJECT it outright unless it is for the same USAGE. 240 // If it is for the same USAGE, we replace the old request with the new one. 241 // The default audio framework's behavior is to remove the previous entry in the stack (no-op 242 // if the requester is already holding focus). 243 @GuardedBy("mLock") evaluateFocusRequestLocked(AudioFocusInfo afi)244 private int evaluateFocusRequestLocked(AudioFocusInfo afi) { 245 Slogf.i(TAG, "Evaluating " + focusEventToString(afi.getGainRequest()) 246 + " request for client " + afi.getClientId() 247 + " with usage " + usageToString(afi.getAttributes().getUsage())); 248 249 if (mIsFocusRestricted) { 250 if (!isCriticalAudioAudioAttribute(afi.getAttributes())) { 251 return AUDIOFOCUS_REQUEST_FAILED; 252 } 253 } 254 255 // Is this a request for permanent focus? 256 // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -- Means Notifications should be denied 257 // AUDIOFOCUS_GAIN_TRANSIENT -- Means current focus holders should get transient loss 258 // AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -- Means other can duck (no loss message from us) 259 // NOTE: We expect that in practice it will be permanent for all media requests and 260 // transient for everything else, but that isn't currently an enforced requirement. 261 boolean permanent = (afi.getGainRequest() == AUDIOFOCUS_GAIN); 262 boolean allowDucking = (afi.getGainRequest() == AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); 263 264 int requestedContext = mCarAudioContext.getContextForAttributes(afi.getAttributes()); 265 266 // We don't allow sharing listeners (client IDs) between two concurrent requests 267 // (because the app would have no way to know to which request a later event applied) 268 if (mDelayedRequest != null && afi.getClientId().equals(mDelayedRequest.getClientId())) { 269 int delayedRequestedContext = mCarAudioContext.getContextForAttributes( 270 mDelayedRequest.getAttributes()); 271 // If it is for a different context then reject 272 if (delayedRequestedContext != requestedContext) { 273 // Trivially reject a request for a different USAGE 274 Slogf.e(TAG, "Client %s has already delayed requested focus for %s - cannot request" 275 + " focus for %s on same listener.", mDelayedRequest.getClientId(), 276 usageToString(mDelayedRequest.getAttributes().getUsage()), 277 usageToString(afi.getAttributes().getUsage())); 278 return AUDIOFOCUS_REQUEST_FAILED; 279 } 280 } 281 282 // These entries have permanently lost focus as a result of this request, so they 283 // should be removed from all blocker lists. 284 ArrayList<FocusEntry> permanentlyLost = new ArrayList<>(); 285 FocusEntry replacedCurrentEntry = null; 286 287 for (int index = 0; index < mFocusHolders.size(); index++) { 288 FocusEntry entry = mFocusHolders.valueAt(index); 289 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 290 Slogf.d(TAG, "Evaluating focus holder %s for duplicates", entry.getClientId()); 291 } 292 293 // If this request is for Notifications and a current focus holder has specified 294 // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request. 295 // This matches the hardwired behavior in the default audio policy engine which apps 296 // might expect (The interaction matrix doesn't have any provision for dealing with 297 // override flags like this). 298 if (CarAudioContext.isNotificationAudioAttribute(afi.getAttributes()) 299 && (entry.getAudioFocusInfo().getGainRequest() 300 == AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) { 301 return AUDIOFOCUS_REQUEST_FAILED; 302 } 303 304 // We don't allow sharing listeners (client IDs) between two concurrent requests 305 // (because the app would have no way to know to which request a later event applied) 306 if (afi.getClientId().equals(entry.getAudioFocusInfo().getClientId())) { 307 if ((entry.getAudioContext() == requestedContext) 308 || canSwapCallOrRingerClientRequest(afi.getClientId(), 309 entry.getAudioFocusInfo().getAttributes(), afi.getAttributes())) { 310 // This is a request from a current focus holder. 311 // Abandon the previous request (without sending a LOSS notification to it), 312 // and don't check the interaction matrix for it. 313 Slogf.i(TAG, "Replacing accepted request from same client: %s", afi); 314 replacedCurrentEntry = entry; 315 continue; 316 } else { 317 // Trivially reject a request for a different USAGE 318 Slogf.e(TAG, "Client %s has already requested focus for %s - cannot request " 319 + "focus for %s on same listener.", entry.getClientId(), 320 usageToString(entry.getAudioFocusInfo().getAttributes().getUsage()), 321 usageToString(afi.getAttributes().getUsage())); 322 return AUDIOFOCUS_REQUEST_FAILED; 323 } 324 } 325 } 326 327 328 for (int index = 0; index < mFocusLosers.size(); index++) { 329 FocusEntry entry = mFocusLosers.valueAt(index); 330 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 331 Slogf.d(TAG, "Evaluating focus loser %s for duplicates", entry.getClientId()); 332 } 333 334 // If this request is for Notifications and a pending focus holder has specified 335 // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request 336 if ((CarAudioContext.isNotificationAudioAttribute(afi.getAttributes())) 337 && (entry.getAudioFocusInfo().getGainRequest() 338 == AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) { 339 return AUDIOFOCUS_REQUEST_FAILED; 340 } 341 342 // We don't allow sharing listeners (client IDs) between two concurrent requests 343 // (because the app would have no way to know to which request a later event applied) 344 if (afi.getClientId().equals(entry.getAudioFocusInfo().getClientId())) { 345 if (entry.getAudioContext() == requestedContext) { 346 // This is a repeat of a request that is currently blocked. 347 // Evaluate it as if it were a new request, but note that we should remove 348 // the old pending request, and move it. 349 // We do not want to evaluate the new request against itself. 350 Slogf.i(TAG, "Replacing pending request from same client id", afi); 351 replacedCurrentEntry = entry; 352 continue; 353 } else { 354 // Trivially reject a request for a different USAGE 355 Slogf.e(TAG, "Client %s has already requested focus for %s - cannot request " 356 + "focus for %s on same listener.", entry.getClientId(), 357 usageToString(entry.getAudioFocusInfo().getAttributes().getUsage()), 358 usageToString(afi.getAttributes().getUsage())); 359 return AUDIOFOCUS_REQUEST_FAILED; 360 } 361 } 362 } 363 364 TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE); 365 t.traceBegin("car-audio-evaluate-focus-request-for-" + afi.getClientId()); 366 OemCarAudioFocusResult evaluationResults = 367 evaluateFocusRequestLocked(replacedCurrentEntry, afi); 368 369 if (evaluationResults.equals(OemCarAudioFocusResult.EMPTY_OEM_CAR_AUDIO_FOCUS_RESULTS)) { 370 t.traceEnd(); 371 return AUDIOFOCUS_REQUEST_FAILED; 372 } 373 374 if (evaluationResults.getAudioFocusResult() == AUDIOFOCUS_REQUEST_FAILED 375 || evaluationResults.getAudioFocusEntry() == null) { 376 t.traceEnd(); 377 return AUDIOFOCUS_REQUEST_FAILED; 378 } 379 380 if (replacedCurrentEntry != null) { 381 mFocusHolders.remove(replacedCurrentEntry.getClientId()); 382 mFocusLosers.remove(replacedCurrentEntry.getClientId()); 383 permanentlyLost.add(replacedCurrentEntry); 384 } 385 386 // Now that we've decided we'll grant focus, construct our new FocusEntry 387 AudioFocusEntry focusEntry = evaluationResults.getAudioFocusEntry(); 388 FocusEntry newEntry = new FocusEntry(focusEntry.getAudioFocusInfo(), 389 focusEntry.getAudioContextId(), mPackageManager); 390 AudioFocusInfo newEntryAfi = newEntry.getAudioFocusInfo(); 391 392 // Now that we're sure we'll accept this request, update any requests which we would 393 // block but are already out of focus but waiting to come back 394 List<AudioFocusEntry> blocked = evaluationResults.getNewlyBlockedAudioFocusEntries(); 395 CarAudioFadeConfiguration transientCarAudioFadeConfigFromXml = null; 396 CarAudioFadeConfiguration defaultCarAudioFadeConfigFromXml = null; 397 Map<AudioAttributes, CarAudioFadeConfiguration> transientCarAudioFadeConfigsFromOemService = 398 null; 399 if (isFadeManagerSupported()) { 400 defaultCarAudioFadeConfigFromXml = mCarAudioZone.getCurrentCarAudioZoneConfig() 401 .getDefaultCarAudioFadeConfiguration(); 402 transientCarAudioFadeConfigFromXml = mCarAudioZone.getCurrentCarAudioZoneConfig() 403 .getCarAudioFadeConfigurationForAudioAttributes(newEntryAfi.getAttributes()); 404 transientCarAudioFadeConfigsFromOemService = 405 evaluationResults.getAudioAttributesToCarAudioFadeConfigurationMap(); 406 } 407 for (int index = 0; index < blocked.size(); index++) { 408 AudioFocusEntry newlyBlocked = blocked.get(index); 409 FocusEntry entry = mFocusLosers.get(newlyBlocked.getAudioFocusInfo().getClientId()); 410 // If we're out of focus it must be because somebody is blocking us 411 assert !entry.isUnblocked(); 412 413 if (permanent) { 414 FadeManagerConfiguration transientFadeManagerConfig = getTransientFadeManagerConfig( 415 defaultCarAudioFadeConfigFromXml, getOptimalUsageBasedTransientFadeConfig( 416 entry.getAudioFocusInfo().getAttributes(), 417 transientCarAudioFadeConfigFromXml, 418 transientCarAudioFadeConfigsFromOemService)); 419 // This entry has now lost focus forever 420 sendFocusLossLocked(entry.getAudioFocusInfo(), AUDIOFOCUS_LOSS, newEntryAfi, 421 !entry.isDucked(), transientFadeManagerConfig); 422 entry.setDucked(false); 423 FocusEntry deadEntry = mFocusLosers.remove( 424 entry.getAudioFocusInfo().getClientId()); 425 assert deadEntry != null; 426 permanentlyLost.add(entry); 427 } else { 428 if (!allowDucking && entry.isDucked()) { 429 // This entry was previously allowed to duck, but can no longer do so. 430 Slogf.i(TAG, "Converting duckable loss to non-duckable for " 431 + entry.getClientId()); 432 // transient loss does not trigger fade 433 sendFocusLossLocked(entry.getAudioFocusInfo(), AUDIOFOCUS_LOSS_TRANSIENT, 434 newEntryAfi, /* shouldFade= */ false, 435 /* transientFadeManagerConfig= */ null); 436 entry.setDucked(false); 437 } 438 // Note that this new request is yet one more reason we can't (yet) have focus 439 entry.addBlocker(newEntry); 440 } 441 } 442 443 // Notify and update any requests which are now losing focus as a result of the new request 444 List<AudioFocusEntry> loss = evaluationResults.getNewlyLostAudioFocusEntries(); 445 for (int index = 0; index < loss.size(); index++) { 446 AudioFocusEntry newlyLoss = loss.get(index); 447 FocusEntry entry = mFocusHolders.get(newlyLoss.getAudioFocusInfo().getClientId()); 448 // If we have focus (but are about to loose it), nobody should be blocking us yet 449 assert entry.isUnblocked(); 450 451 if (permanent) { 452 FadeManagerConfiguration transientFadeManagerConfig = getTransientFadeManagerConfig( 453 defaultCarAudioFadeConfigFromXml, getOptimalUsageBasedTransientFadeConfig( 454 entry.getAudioFocusInfo().getAttributes(), 455 transientCarAudioFadeConfigFromXml, 456 transientCarAudioFadeConfigsFromOemService)); 457 sendFocusLossLocked(entry.getAudioFocusInfo(), AUDIOFOCUS_LOSS, newEntryAfi, 458 !entry.isDucked(), transientFadeManagerConfig); 459 permanentlyLost.add(entry); 460 } else { 461 int lossType = AUDIOFOCUS_LOSS_TRANSIENT; 462 if (allowDucking && entry.receivesDuckEvents()) { 463 lossType = AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; 464 entry.setDucked(true); 465 } 466 sendFocusLossLocked(entry.getAudioFocusInfo(), lossType, newEntryAfi, 467 /* shouldFade= */ false, /* transientFadeManagerConfig= */ null); 468 // Add ourselves to the list of requests waiting to get focus back and 469 // note why we lost focus so we can tell when it's time to get it back 470 mFocusLosers.put(entry.getAudioFocusInfo().getClientId(), entry); 471 entry.addBlocker(newEntry); 472 } 473 // The entry no longer holds focus, so take it out of the holders list 474 mFocusHolders.remove(entry.getAudioFocusInfo().getClientId()); 475 } 476 477 if (evaluationResults.getAudioFocusResult() != AUDIOFOCUS_REQUEST_DELAYED) { 478 // If the entry is replacing an existing one, and if a delayed Request is pending 479 // this replaced entry is not a blocker of the delayed. 480 // So add it before reconsidering the delayed. 481 mFocusHolders.put(afi.getClientId(), newEntry); 482 } 483 484 // Now that all new blockers have been added, clear out any other requests that have been 485 // permanently lost as a result of this request. Treat them as abandoned - if they're on 486 // any blocker lists, remove them. If any focus requests become unblocked as a result, 487 // re-grant them. (This can happen when a GAIN_TRANSIENT_MAY_DUCK request replaces a 488 // GAIN_TRANSIENT request from the same listener.) 489 for (int index = 0; index < permanentlyLost.size(); index++) { 490 FocusEntry entry = permanentlyLost.get(index); 491 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 492 Slogf.d(TAG, "Cleaning up entry " + entry.getClientId()); 493 } 494 removeBlockerAndRestoreUnblockedWaitersLocked(entry); 495 } 496 497 if (evaluationResults.getAudioFocusResult() == AUDIOFOCUS_REQUEST_DELAYED) { 498 swapDelayedAudioFocusRequestLocked(afi); 499 t.traceEnd(); 500 return AUDIOFOCUS_REQUEST_DELAYED; 501 } 502 503 t.traceEnd(); 504 Slogf.i(TAG, "AUDIOFOCUS_REQUEST_GRANTED"); 505 return AUDIOFOCUS_REQUEST_GRANTED; 506 } 507 508 @GuardedBy("mLock") evaluateFocusRequestLocked(FocusEntry replacedCurrentEntry, AudioFocusInfo audioFocusInfo)509 private OemCarAudioFocusResult evaluateFocusRequestLocked(FocusEntry replacedCurrentEntry, 510 AudioFocusInfo audioFocusInfo) { 511 512 return isExternalFocusEnabled() 513 ? evaluateFocusRequestExternallyLocked(audioFocusInfo, replacedCurrentEntry) : 514 evaluateFocusRequestInternallyLocked(audioFocusInfo, replacedCurrentEntry); 515 } 516 517 @GuardedBy("mLock") evaluateFocusRequestInternallyLocked( AudioFocusInfo audioFocusInfo, FocusEntry replacedCurrentEntry)518 private OemCarAudioFocusResult evaluateFocusRequestInternallyLocked( 519 AudioFocusInfo audioFocusInfo, FocusEntry replacedCurrentEntry) { 520 TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE); 521 t.traceBegin("evaluate-focus-request-internally"); 522 boolean allowDucking = 523 (audioFocusInfo.getGainRequest() == AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); 524 boolean allowDelayedFocus = canReceiveDelayedFocus(audioFocusInfo); 525 526 int requestedUsage = audioFocusInfo.getAttributes().getSystemUsage(); 527 FocusEvaluation holdersEvaluation = evaluateAgainstFocusHoldersLocked(replacedCurrentEntry, 528 requestedUsage, allowDucking, allowDelayedFocus); 529 530 if (holdersEvaluation.equals(FocusEvaluation.FOCUS_EVALUATION_FAILED)) { 531 t.traceEnd(); 532 return OemCarAudioFocusResult.EMPTY_OEM_CAR_AUDIO_FOCUS_RESULTS; 533 } 534 535 FocusEvaluation losersEvaluation = evaluateAgainstFocusLosersLocked(replacedCurrentEntry, 536 requestedUsage, allowDucking, allowDelayedFocus); 537 538 if (losersEvaluation.equals(FocusEvaluation.FOCUS_EVALUATION_FAILED)) { 539 t.traceEnd(); 540 return OemCarAudioFocusResult.EMPTY_OEM_CAR_AUDIO_FOCUS_RESULTS; 541 } 542 543 boolean delayFocus = holdersEvaluation.mAudioFocusEvalResults == AUDIOFOCUS_REQUEST_DELAYED 544 || losersEvaluation.mAudioFocusEvalResults == AUDIOFOCUS_REQUEST_DELAYED; 545 546 int results = delayFocus ? AUDIOFOCUS_REQUEST_DELAYED : AUDIOFOCUS_REQUEST_GRANTED; 547 548 t.traceBegin("evaluate-focus-entry-build"); 549 AudioFocusEntry focusEntry = 550 new AudioFocusEntry.Builder(audioFocusInfo, 551 mCarAudioContext.getContextForAudioAttribute( 552 audioFocusInfo.getAttributes()), 553 getVolumeGroupForAttribute(audioFocusInfo.getAttributes()), 554 AUDIOFOCUS_GAIN).build(); 555 t.traceEnd(); 556 557 t.traceBegin("evaluate-focus-result-build"); 558 OemCarAudioFocusResult focusResult = new OemCarAudioFocusResult.Builder( 559 convertAudioFocusEntries(holdersEvaluation.mChangedEntries), 560 convertAudioFocusEntries(losersEvaluation.mChangedEntries), 561 results).setAudioFocusEntry(focusEntry).build(); 562 t.traceEnd(); 563 t.traceEnd(); 564 return focusResult; 565 } 566 567 @GuardedBy("mLock") evaluateFocusRequestExternallyLocked(AudioFocusInfo requestInfo, FocusEntry replacedCurrentEntry)568 private OemCarAudioFocusResult evaluateFocusRequestExternallyLocked(AudioFocusInfo requestInfo, 569 FocusEntry replacedCurrentEntry) { 570 TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE); 571 t.traceBegin("evaluate-focus-request-externally"); 572 OemCarAudioFocusEvaluationRequest.Builder builder = 573 new OemCarAudioFocusEvaluationRequest.Builder(getMutedVolumeGroups(), 574 getAudioFocusEntries(mFocusHolders, replacedCurrentEntry), 575 getAudioFocusEntries(mFocusLosers, replacedCurrentEntry), mCarAudioZone.getId()) 576 .setAudioFocusRequest(convertAudioFocusInfo(requestInfo)); 577 578 if (mAudioFeaturesInfo != null) { 579 builder.setAudioFeaturesInfo(mAudioFeaturesInfo); 580 } 581 582 OemCarAudioFocusEvaluationRequest request = builder.build(); 583 584 logFocusEvent("Calling oem service with request " + request); 585 OemCarAudioFocusResult focusResult = CarLocalServices.getService(CarOemProxyService.class) 586 .getCarOemAudioFocusService().evaluateAudioFocusRequest(request); 587 logFocusEvent("oem service returns focus result " + focusResult); 588 t.traceEnd(); 589 return focusResult; 590 } 591 convertAudioFocusInfo(AudioFocusInfo info)592 private AudioFocusEntry convertAudioFocusInfo(AudioFocusInfo info) { 593 return new AudioFocusEntry.Builder(info, 594 mCarAudioContext.getContextForAudioAttribute(info.getAttributes()), 595 getVolumeGroupForAttribute(info.getAttributes()), 596 AUDIOFOCUS_LOSS_TRANSIENT).build(); 597 } 598 getAudioFocusEntries(ArrayMap<String, FocusEntry> entryMap, FocusEntry replacedCurrentEntry)599 private List<AudioFocusEntry> getAudioFocusEntries(ArrayMap<String, FocusEntry> entryMap, 600 FocusEntry replacedCurrentEntry) { 601 List<AudioFocusEntry> entries = new ArrayList<>(entryMap.size()); 602 for (int index = 0; index < entryMap.size(); index++) { 603 // Will consider focus evaluation for a current entry and replace it if focus is 604 // granted 605 if (replacedCurrentEntry != null 606 && replacedCurrentEntry.getClientId().equals(entryMap.keyAt(index))) { 607 continue; 608 } 609 entries.add(convertFocusEntry(entryMap.valueAt(index))); 610 } 611 return entries; 612 } 613 convertFocusEntry(FocusEntry entry)614 private AudioFocusEntry convertFocusEntry(FocusEntry entry) { 615 return convertAudioFocusInfo(entry.getAudioFocusInfo()); 616 } 617 getMutedVolumeGroups()618 private List<CarVolumeGroupInfo> getMutedVolumeGroups() { 619 return mCarVolumeInfoWrapper.getMutedVolumeGroups(mCarAudioZone.getId()); 620 } 621 isExternalFocusEnabled()622 private boolean isExternalFocusEnabled() { 623 CarOemProxyService proxy = CarLocalServices.getService(CarOemProxyService.class); 624 if (!proxy.isOemServiceEnabled()) { 625 return false; 626 } 627 628 if (!proxy.isOemServiceReady()) { 629 logFocusEvent("Focus was called but OEM service is not yet ready."); 630 return false; 631 } 632 633 return proxy.getCarOemAudioFocusService() != null; 634 } 635 convertAudioFocusEntries(List<FocusEntry> changedEntries)636 private List<AudioFocusEntry> convertAudioFocusEntries(List<FocusEntry> changedEntries) { 637 List<AudioFocusEntry> audioFocusEntries = new ArrayList<>(changedEntries.size()); 638 for (int index = 0; index < changedEntries.size(); index++) { 639 audioFocusEntries.add(convertFocusEntry(changedEntries.get(index))); 640 } 641 return audioFocusEntries; 642 } 643 getVolumeGroupForAttribute(AudioAttributes attributes)644 private int getVolumeGroupForAttribute(AudioAttributes attributes) { 645 return mCarVolumeInfoWrapper.getVolumeGroupIdForAudioAttribute(mCarAudioZone.getId(), 646 attributes); 647 } 648 649 @GuardedBy("mLock") evaluateAgainstFocusLosersLocked( FocusEntry replacedBlockedEntry, int requestedUsage, boolean allowDucking, boolean allowDelayedFocus)650 private FocusEvaluation evaluateAgainstFocusLosersLocked( 651 FocusEntry replacedBlockedEntry, int requestedUsage, boolean allowDucking, 652 boolean allowDelayedFocus) { 653 Slogf.i(TAG, "Scanning those who've already lost focus..."); 654 TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE); 655 t.traceBegin("evaluate-focus-losers"); 656 var results = evaluateAgainstFocusArrayLocked(mFocusLosers, replacedBlockedEntry, 657 requestedUsage, allowDucking, allowDelayedFocus); 658 t.traceEnd(); 659 return results; 660 } 661 662 @GuardedBy("mLock") evaluateAgainstFocusHoldersLocked( FocusEntry replacedCurrentEntry, int requestedUsage, boolean allowDucking, boolean allowDelayedFocus)663 private FocusEvaluation evaluateAgainstFocusHoldersLocked( 664 FocusEntry replacedCurrentEntry, int requestedUsage, boolean allowDucking, 665 boolean allowDelayedFocus) { 666 Slogf.i(TAG, "Scanning focus holders..."); 667 TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE); 668 t.traceBegin("evaluate-focus-holders"); 669 var results = evaluateAgainstFocusArrayLocked(mFocusHolders, replacedCurrentEntry, 670 requestedUsage, allowDucking, allowDelayedFocus); 671 t.traceEnd(); 672 return results; 673 } 674 675 @GuardedBy("mLock") evaluateAgainstFocusArrayLocked(ArrayMap<String, FocusEntry> focusArray, FocusEntry replacedEntry, int requestedUsage, boolean allowDucking, boolean allowDelayedFocus)676 private FocusEvaluation evaluateAgainstFocusArrayLocked(ArrayMap<String, FocusEntry> focusArray, 677 FocusEntry replacedEntry, int requestedUsage, boolean allowDucking, 678 boolean allowDelayedFocus) { 679 boolean delayFocusForCurrentRequest = false; 680 ArrayList<FocusEntry> changedEntries = new ArrayList<FocusEntry>(); 681 for (int index = 0; index < focusArray.size(); index++) { 682 FocusEntry entry = focusArray.valueAt(index); 683 Slogf.i(TAG, entry.getAudioFocusInfo().getClientId()); 684 685 if (replacedEntry != null && entry.getClientId().equals(replacedEntry.getClientId())) { 686 continue; 687 } 688 689 int interactionResult = mFocusInteraction.evaluateRequest(requestedUsage, entry, 690 allowDucking, allowDelayedFocus, changedEntries); 691 if (interactionResult == AUDIOFOCUS_REQUEST_FAILED) { 692 return FocusEvaluation.FOCUS_EVALUATION_FAILED; 693 } 694 if (interactionResult == AUDIOFOCUS_REQUEST_DELAYED) { 695 delayFocusForCurrentRequest = true; 696 } 697 } 698 int results = delayFocusForCurrentRequest 699 ? AUDIOFOCUS_REQUEST_DELAYED : AUDIOFOCUS_REQUEST_GRANTED; 700 return new FocusEvaluation(changedEntries, results); 701 } 702 canSwapCallOrRingerClientRequest(String clientId, AudioAttributes currentAttributes, AudioAttributes requestedAttributes)703 private static boolean canSwapCallOrRingerClientRequest(String clientId, 704 AudioAttributes currentAttributes, AudioAttributes requestedAttributes) { 705 return isCallFocusRequestClientId(clientId) 706 && isRingerOrCallAudioAttributes(currentAttributes) 707 && isRingerOrCallAudioAttributes(requestedAttributes); 708 } 709 isRingerOrCallAudioAttributes(AudioAttributes attributes)710 private static boolean isRingerOrCallAudioAttributes(AudioAttributes attributes) { 711 return CarAudioContext.isRingerOrCallAudioAttribute(attributes); 712 } 713 714 @Override onAudioFocusRequest(AudioFocusInfo afi, int requestResult)715 public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) { 716 int response; 717 AudioPolicy policy; 718 synchronized (mLock) { 719 policy = mAudioPolicy; 720 response = evaluateFocusRequestLocked(afi); 721 } 722 723 // Post our reply for delivery to the original focus requester 724 mAudioManager.setFocusRequestResult(afi, response, policy); 725 logFocusEvent("onAudioFocusRequest for client " + afi.getClientId() 726 + " with gain type " + focusEventToString(afi.getGainRequest()) 727 + " resulted in " + focusRequestResponseToString(response)); 728 } 729 730 @GuardedBy("mLock") swapDelayedAudioFocusRequestLocked(AudioFocusInfo afi)731 private void swapDelayedAudioFocusRequestLocked(AudioFocusInfo afi) { 732 // If we are swapping to a different client then send the focus loss signal 733 if (mDelayedRequest != null 734 && !afi.getClientId().equals(mDelayedRequest.getClientId())) { 735 sendFocusLossLocked(mDelayedRequest, AUDIOFOCUS_LOSS, afi, /* shouldFade= */ false, 736 /* transientFadeManagerConfig= */ null); 737 } 738 mDelayedRequest = afi; 739 } 740 canReceiveDelayedFocus(AudioFocusInfo afi)741 private boolean canReceiveDelayedFocus(AudioFocusInfo afi) { 742 if (afi.getGainRequest() != AUDIOFOCUS_GAIN) { 743 return false; 744 } 745 return (afi.getFlags() & AUDIOFOCUS_FLAG_DELAY_OK) == AUDIOFOCUS_FLAG_DELAY_OK; 746 } 747 748 /** 749 * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) 750 * Note that we'll get this call for a focus holder that dies while in the focus stack, so 751 * we don't need to watch for death notifications directly. 752 * */ 753 @Override onAudioFocusAbandon(AudioFocusInfo afi)754 public void onAudioFocusAbandon(AudioFocusInfo afi) { 755 logFocusEvent("onAudioFocusAbandon for client " + afi.getClientId()); 756 synchronized (mLock) { 757 FocusEntry deadEntry = removeFocusEntryLocked(afi); 758 759 if (deadEntry != null) { 760 removeBlockerAndRestoreUnblockedWaitersLocked(deadEntry); 761 } 762 } 763 } 764 765 /** 766 * Remove Focus entry from focus holder or losers entry lists 767 * @param afi Audio Focus Info to remove 768 * @return Removed Focus Entry 769 */ 770 @GuardedBy("mLock") removeFocusEntryLocked(AudioFocusInfo afi)771 private FocusEntry removeFocusEntryLocked(AudioFocusInfo afi) { 772 Slogf.i(TAG, "removeFocusEntry " + afi.getClientId()); 773 if (mDelayedRequest != null && afi.getClientId().equals(mDelayedRequest.getClientId())) { 774 logFocusEvent("Audio focus abandoned for delayed focus entry " + afi.getClientId()); 775 mDelayedRequest = null; 776 return null; 777 } 778 // Remove this entry from our active or pending list 779 FocusEntry deadEntry = mFocusHolders.remove(afi.getClientId()); 780 if (deadEntry == null) { 781 deadEntry = mFocusLosers.remove(afi.getClientId()); 782 if (deadEntry == null) { 783 // Caller is providing an unrecognized clientId!? 784 Slogf.w(TAG, "Audio focus abandoned by unrecognized client id: " 785 + afi.getClientId()); 786 // This probably means an app double released focused for some reason. One 787 // harmless possibility is a race between an app being told it lost focus and the 788 // app voluntarily abandoning focus. More likely the app is just sloppy. :) 789 // The more nefarious possibility is that the clientId is actually corrupted 790 // somehow, in which case we might have a real focus entry that we're going to fail 791 // to remove. If that were to happen, I'd expect either the app to swallow it 792 // silently, or else take unexpected action (eg: resume playing spontaneously), or 793 // else to see "Failure to signal ..." gain/loss error messages in the log from 794 // this module when a focus change tries to take action on a truly zombie entry. 795 } 796 } 797 return deadEntry; 798 } 799 800 @GuardedBy("mLock") removeBlockerAndRestoreUnblockedWaitersLocked(FocusEntry deadEntry)801 private void removeBlockerAndRestoreUnblockedWaitersLocked(FocusEntry deadEntry) { 802 attemptToGainFocusForDelayedAudioFocusRequestLocked(); 803 removeBlockerAndRestoreUnblockedFocusLosersLocked(deadEntry); 804 } 805 806 @GuardedBy("mLock") attemptToGainFocusForDelayedAudioFocusRequestLocked()807 private void attemptToGainFocusForDelayedAudioFocusRequestLocked() { 808 if (mDelayedRequest == null) { 809 return; 810 } 811 // Prevent cleanup of permanent lost to recall attemptToGainFocusForDelayedAudioFocusRequest 812 // Whatever granted / denied / delayed again, no need to restore, mDelayedRequest restored 813 // if delayed again. 814 AudioFocusInfo delayedFocusInfo = mDelayedRequest; 815 mDelayedRequest = null; 816 int delayedFocusRequestResults = evaluateFocusRequestLocked(delayedFocusInfo); 817 if (delayedFocusRequestResults == AUDIOFOCUS_REQUEST_GRANTED) { 818 FocusEntry focusEntry = mFocusHolders.get(delayedFocusInfo.getClientId()); 819 if (dispatchFocusGainedLocked(focusEntry.getAudioFocusInfo()) 820 == AUDIOFOCUS_REQUEST_FAILED) { 821 Slogf.e(TAG, "Failure to signal gain of audio focus gain for " 822 + "delayed focus clientId " + focusEntry.getClientId()); 823 mFocusHolders.remove(focusEntry.getClientId()); 824 removeBlockerFromBlockedFocusLosersLocked(focusEntry); 825 sendFocusLossLocked(focusEntry.getAudioFocusInfo(), AUDIOFOCUS_LOSS, 826 /* winner= */ null, /* shouldFade= */ false, 827 /* transientFadeManagerConfig = */ null); 828 logFocusEvent("Did not gain delayed audio focus for " + focusEntry.getClientId()); 829 } 830 } else if (delayedFocusRequestResults == AUDIOFOCUS_REQUEST_FAILED) { 831 // Delayed request has permanently be denied 832 logFocusEvent("Delayed audio focus retry failed for " + delayedFocusInfo.getClientId()); 833 sendFocusLossLocked(delayedFocusInfo, AUDIOFOCUS_LOSS, /* winner= */ null, 834 /* shouldFade= */ false, /* transientFadeManagerConfig = */ null); 835 } else { 836 assert mDelayedRequest.equals(delayedFocusInfo); 837 } 838 } 839 840 /** 841 * Removes the dead entry from blocked waiters but does not send focus gain signal 842 */ 843 @GuardedBy("mLock") removeBlockerFromBlockedFocusLosersLocked(FocusEntry deadEntry)844 private void removeBlockerFromBlockedFocusLosersLocked(FocusEntry deadEntry) { 845 // Remove this entry from the blocking list of any pending requests 846 Iterator<FocusEntry> it = mFocusLosers.values().iterator(); 847 while (it.hasNext()) { 848 FocusEntry entry = it.next(); 849 // Remove the retiring entry from all blocker lists 850 entry.removeBlocker(deadEntry); 851 } 852 } 853 854 /** 855 * Removes the dead entry from blocked waiters and sends focus gain signal 856 */ 857 @GuardedBy("mLock") removeBlockerAndRestoreUnblockedFocusLosersLocked(FocusEntry deadEntry)858 private void removeBlockerAndRestoreUnblockedFocusLosersLocked(FocusEntry deadEntry) { 859 // Remove this entry from the blocking list of any pending requests 860 Iterator<FocusEntry> it = mFocusLosers.values().iterator(); 861 while (it.hasNext()) { 862 FocusEntry entry = it.next(); 863 864 // Remove the retiring entry from all blocker lists 865 entry.removeBlocker(deadEntry); 866 867 // Any entry whose blocking list becomes empty should regain focus 868 if (entry.isUnblocked()) { 869 Slogf.i(TAG, "Restoring unblocked entry " + entry.getClientId()); 870 // Pull this entry out of the focus losers list 871 it.remove(); 872 873 // Clear ducked status to prevent spurious LOSS_TRANSIENT to be sent while checking 874 // blocked entries and converting duckable loss to non-duckable 875 entry.setDucked(false); 876 877 // Add it back into the focus holders list 878 mFocusHolders.put(entry.getClientId(), entry); 879 880 dispatchFocusGainedLocked(entry.getAudioFocusInfo()); 881 } 882 } 883 } 884 885 /** 886 * Dispatch focus gain 887 * @param afi Audio focus info 888 * @return {@link AUDIOFOCUS_REQUEST_GRANTED} if focus is dispatched successfully 889 */ dispatchFocusGainedLocked(AudioFocusInfo afi)890 private int dispatchFocusGainedLocked(AudioFocusInfo afi) { 891 // Send the focus (re)gain notification 892 int result = mAudioManager.dispatchAudioFocusChange(afi, AUDIOFOCUS_GAIN, mAudioPolicy); 893 if (result == AUDIOFOCUS_REQUEST_FAILED) { 894 // TODO: Is this actually an error, or is it okay for an entry in the focus 895 // stack to NOT have a listener? If that's the case, should we even keep 896 // it in the focus stack? 897 Slogf.e(TAG, "Failure to signal gain of audio focus with error: " + result); 898 } 899 900 logFocusEvent("dispatchFocusGainedLocked for client " + afi.getClientId() 901 + " with gain type " + focusEventToString(afi.getGainRequest()) 902 + " resulted in " + focusRequestResponseToString(result)); 903 return result; 904 } 905 906 /** 907 * Query the current list of focus loser for uid 908 * @param uid uid to query current focus loser 909 * @return list of current focus losers for uid 910 */ getAudioFocusLosersForUid(int uid)911 ArrayList<AudioFocusInfo> getAudioFocusLosersForUid(int uid) { 912 synchronized (mLock) { 913 return getAudioFocusList(new UidAudioFocusInfoComparator(uid), mFocusLosers); 914 } 915 } 916 getAudioFocusHolders()917 List<AudioFocusInfo> getAudioFocusHolders() { 918 synchronized (mLock) { 919 return getAudioFocusInfos(mFocusHolders); 920 } 921 } 922 923 /** 924 * Query the current list of focus holders for uid 925 * @param uid uid to query current focus holders 926 * @return list of current focus holders that for uid 927 */ getAudioFocusHoldersForUid(int uid)928 ArrayList<AudioFocusInfo> getAudioFocusHoldersForUid(int uid) { 929 synchronized (mLock) { 930 return getAudioFocusList(new UidAudioFocusInfoComparator(uid), mFocusHolders); 931 } 932 } 933 getAudioFocusLosers()934 List<AudioFocusInfo> getAudioFocusLosers() { 935 synchronized (mLock) { 936 return getAudioFocusInfos(mFocusLosers); 937 } 938 } 939 940 /** 941 * Remove the audio focus info, if entry is still active 942 * dispatch lose focus transient to listeners 943 * @param afi Audio Focus info to remove 944 */ removeAudioFocusInfoAndTransientlyLoseFocus(AudioFocusInfo afi)945 void removeAudioFocusInfoAndTransientlyLoseFocus(AudioFocusInfo afi) { 946 synchronized (mLock) { 947 FocusEntry deadEntry = removeFocusEntryLocked(afi); 948 if (deadEntry != null) { 949 sendFocusLossLocked(deadEntry.getAudioFocusInfo(), AUDIOFOCUS_LOSS_TRANSIENT, 950 /* winner= */ null, /* shouldFade= */ false, 951 /* transientFadeManagerConfig = */ null); 952 removeBlockerAndRestoreUnblockedWaitersLocked(deadEntry); 953 } 954 } 955 } 956 957 /** 958 * Reevaluate focus request and regain focus 959 * @param afi audio focus info to reevaluate 960 * @return {@link AUDIOFOCUS_REQUEST_GRANTED} if focus is granted 961 */ reevaluateAndRegainAudioFocus(AudioFocusInfo afi)962 int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) { 963 int results; 964 synchronized (mLock) { 965 results = evaluateFocusRequestLocked(afi); 966 if (results == AUDIOFOCUS_REQUEST_GRANTED) { 967 results = dispatchFocusGainedLocked(afi); 968 } 969 } 970 971 return results; 972 } 973 974 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)975 public void dump(IndentingPrintWriter writer) { 976 synchronized (mLock) { 977 writer.println("*CarAudioFocus*"); 978 writer.increaseIndent(); 979 writer.printf("Audio Zone ID: %d\n", mCarAudioZone.getId()); 980 writer.printf("Is focus restricted? %b\n", mIsFocusRestricted); 981 writer.printf("Is external focus eval enabled? %b\n", isExternalFocusEnabled()); 982 writer.println(); 983 mFocusInteraction.dump(writer); 984 985 writer.println("Current Focus Holders:"); 986 writer.increaseIndent(); 987 for (String clientId : mFocusHolders.keySet()) { 988 mFocusHolders.get(clientId).dump(writer); 989 } 990 writer.decreaseIndent(); 991 992 writer.println("Transient Focus Losers:"); 993 writer.increaseIndent(); 994 for (String clientId : mFocusLosers.keySet()) { 995 mFocusLosers.get(clientId).dump(writer); 996 } 997 writer.decreaseIndent(); 998 999 writer.printf("Queued Delayed Focus: %s\n", 1000 mDelayedRequest == null ? "None" : mDelayedRequest.getClientId()); 1001 1002 writer.println("Focus Events:"); 1003 writer.increaseIndent(); 1004 mFocusEventLogger.dump(writer); 1005 writer.decreaseIndent(); 1006 1007 writer.decreaseIndent(); 1008 } 1009 } 1010 1011 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)1012 public void dumpProto(ProtoOutputStream proto) { 1013 long carAudioFocusToken = proto.start(CarAudioZoneFocusProto.CAR_AUDIO_FOCUSES); 1014 synchronized (mLock) { 1015 proto.write(CarAudioFocusProto.ZONE_ID, mCarAudioZone.getId()); 1016 proto.write(CarAudioFocusProto.FOCUS_RESTRICTED, mIsFocusRestricted); 1017 proto.write(CarAudioFocusProto.EXTERNAL_FOCUS_ENABLED, isExternalFocusEnabled()); 1018 1019 mFocusInteraction.dumpProto(proto); 1020 1021 for (String clientId : mFocusHolders.keySet()) { 1022 mFocusHolders.get(clientId).dumpProto(CarAudioFocusProto.FOCUS_HOLDERS, proto); 1023 } 1024 1025 for (String clientId : mFocusLosers.keySet()) { 1026 mFocusLosers.get(clientId).dumpProto(CarAudioFocusProto.FOCUS_LOSERS, proto); 1027 } 1028 1029 if (mDelayedRequest != null) { 1030 proto.write(CarAudioFocusProto.DELAYED_FOCUS, mDelayedRequest.getClientId()); 1031 } 1032 } 1033 proto.end(carAudioFocusToken); 1034 } 1035 focusEventToString(int focusEvent)1036 private static String focusEventToString(int focusEvent) { 1037 switch (focusEvent) { 1038 case AUDIOFOCUS_GAIN: 1039 return "GAIN"; 1040 case AUDIOFOCUS_GAIN_TRANSIENT: 1041 return "GAIN_TRANSIENT"; 1042 case AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 1043 return "GAIN_TRANSIENT_EXCLUSIVE"; 1044 case AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 1045 return "GAIN_TRANSIENT_MAY_DUCK"; 1046 case AUDIOFOCUS_LOSS: 1047 return "LOSS"; 1048 case AUDIOFOCUS_LOSS_TRANSIENT: 1049 return "LOSS_TRANSIENT"; 1050 case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 1051 return "LOSS_TRANSIENT_CAN_DUCK"; 1052 default: 1053 return "unknown event " + focusEvent; 1054 } 1055 } 1056 focusRequestResponseToString(int response)1057 private static String focusRequestResponseToString(int response) { 1058 if (response == AUDIOFOCUS_REQUEST_GRANTED) { 1059 return "REQUEST_GRANTED"; 1060 } else if (response == AUDIOFOCUS_REQUEST_FAILED) { 1061 return "REQUEST_FAILED"; 1062 } 1063 return "REQUEST_DELAYED"; 1064 } 1065 logFocusEvent(String log)1066 private void logFocusEvent(String log) { 1067 mFocusEventLogger.log(log); 1068 Slogf.i(TAG, log); 1069 } 1070 1071 /** 1072 * Returns the focus interaction for this car focus instance. 1073 */ getFocusInteraction()1074 public FocusInteraction getFocusInteraction() { 1075 return mFocusInteraction; 1076 } 1077 1078 private static final class FocusEvaluation { 1079 1080 private static final FocusEvaluation FOCUS_EVALUATION_FAILED = 1081 new FocusEvaluation(/* changedEntries= */ new ArrayList<>(/* initialCap= */ 0), 1082 AUDIOFOCUS_REQUEST_FAILED); 1083 1084 private final List<FocusEntry> mChangedEntries; 1085 private final int mAudioFocusEvalResults; 1086 FocusEvaluation(List<FocusEntry> changedEntries, int audioFocusEvalResults)1087 FocusEvaluation(List<FocusEntry> changedEntries, int audioFocusEvalResults) { 1088 mChangedEntries = changedEntries; 1089 mAudioFocusEvalResults = audioFocusEvalResults; 1090 } 1091 1092 @Override 1093 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) toString()1094 public String toString() { 1095 return new StringBuilder().append("{Changed Entries: ").append(mChangedEntries) 1096 .append(", Results: ").append(mAudioFocusEvalResults) 1097 .append(" }").toString(); 1098 } 1099 } 1100 1101 /** 1102 * Returns the currently active focus holder for media 1103 * 1104 * @param userId user id to select 1105 * @param audioAttributes audio attributes to query 1106 * @return list of currently active focus holder with matching audio attribute 1107 */ getActiveAudioFocusForUserAndAudioAttributes( AudioAttributes audioAttributes, @UserIdInt int userId)1108 public List<AudioFocusInfo> getActiveAudioFocusForUserAndAudioAttributes( 1109 AudioAttributes audioAttributes, @UserIdInt int userId) { 1110 Objects.requireNonNull(audioAttributes, 1111 "Audio attributes can no be null"); 1112 synchronized (mLock) { 1113 return getAudioFocusList( 1114 new UserIdAndAudioAttributeAudioFocusInfoComparator(audioAttributes, userId), 1115 mFocusHolders); 1116 } 1117 } 1118 1119 /** 1120 * Returns the currently inactive focus holder for a particular audio attributes 1121 * 1122 * @param audioAttributes audio attributes to query 1123 * @param userId user id to select 1124 * @return list of currently inactive focus holder with matching audio attribute 1125 */ getInactiveAudioFocusForUserAndAudioAttributes( AudioAttributes audioAttributes, @UserIdInt int userId)1126 public List<AudioFocusInfo> getInactiveAudioFocusForUserAndAudioAttributes( 1127 AudioAttributes audioAttributes, @UserIdInt int userId) { 1128 Objects.requireNonNull(audioAttributes, 1129 "Audio Attributes can no be null"); 1130 synchronized (mLock) { 1131 List<AudioFocusInfo> inactiveList = getAudioFocusList( 1132 new UserIdAndAudioAttributeAudioFocusInfoComparator(audioAttributes, userId), 1133 mFocusLosers); 1134 1135 if (mDelayedRequest != null 1136 && CarAudioContext.AudioAttributesWrapper.audioAttributeMatches( 1137 audioAttributes, mDelayedRequest.getAttributes())) { 1138 inactiveList.add(mDelayedRequest); 1139 mDelayedRequest = null; 1140 } 1141 1142 return inactiveList; 1143 } 1144 } 1145 getAudioFocusInfos( ArrayMap<String, FocusEntry> focusEntries)1146 private static List<AudioFocusInfo> getAudioFocusInfos( 1147 ArrayMap<String, FocusEntry> focusEntries) { 1148 List<AudioFocusInfo> focusInfos = new ArrayList<>(focusEntries.size()); 1149 for (int index = 0; index < focusEntries.size(); index++) { 1150 focusInfos.add(focusEntries.valueAt(index).getAudioFocusInfo()); 1151 } 1152 return focusInfos; 1153 } 1154 getAudioFocusList(AudioFocusInfoComparator comparator, Map<String, FocusEntry> mapToQuery)1155 private static ArrayList<AudioFocusInfo> getAudioFocusList(AudioFocusInfoComparator comparator, 1156 Map<String, FocusEntry> mapToQuery) { 1157 ArrayList<AudioFocusInfo> matchingInfoList = new ArrayList<>(); 1158 for (String clientId : mapToQuery.keySet()) { 1159 AudioFocusInfo afi = mapToQuery.get(clientId).getAudioFocusInfo(); 1160 if (comparator.matches(afi)) { 1161 matchingInfoList.add(afi); 1162 } 1163 } 1164 return matchingInfoList; 1165 } 1166 1167 private interface AudioFocusInfoComparator { matches(AudioFocusInfo afi)1168 boolean matches(AudioFocusInfo afi); 1169 } 1170 1171 private static final class UidAudioFocusInfoComparator implements AudioFocusInfoComparator { 1172 1173 private final int mUid; 1174 UidAudioFocusInfoComparator(int uid)1175 UidAudioFocusInfoComparator(int uid) { 1176 mUid = uid; 1177 } 1178 1179 @Override matches(AudioFocusInfo afi)1180 public boolean matches(AudioFocusInfo afi) { 1181 return afi.getClientUid() == mUid; 1182 } 1183 } 1184 1185 private static final class UserIdAndAudioAttributeAudioFocusInfoComparator 1186 implements AudioFocusInfoComparator { 1187 1188 private final int mUserId; 1189 private final AudioAttributes mAudioAttribute; 1190 UserIdAndAudioAttributeAudioFocusInfoComparator( AudioAttributes audioAttributes, @UserIdInt int userId)1191 UserIdAndAudioAttributeAudioFocusInfoComparator( 1192 AudioAttributes audioAttributes, @UserIdInt int userId) { 1193 mAudioAttribute = audioAttributes; 1194 mUserId = userId; 1195 } 1196 1197 @Override matches(AudioFocusInfo afi)1198 public boolean matches(AudioFocusInfo afi) { 1199 return (UserHandle.getUserHandleForUid(afi.getClientUid()).getIdentifier() == mUserId) 1200 && CarAudioContext.AudioAttributesWrapper 1201 .audioAttributeMatches(mAudioAttribute, afi.getAttributes()); 1202 } 1203 } 1204 getTransientFadeManagerConfig( CarAudioFadeConfiguration defaultCarAudioFadeConfigFromXml, CarAudioFadeConfiguration transientCarAudioFadeConfig)1205 private FadeManagerConfiguration getTransientFadeManagerConfig( 1206 CarAudioFadeConfiguration defaultCarAudioFadeConfigFromXml, 1207 CarAudioFadeConfiguration transientCarAudioFadeConfig) { 1208 if (!isFadeManagerSupported()) { 1209 return null; 1210 } 1211 1212 if (transientCarAudioFadeConfig != null) { 1213 return transientCarAudioFadeConfig.getFadeManagerConfiguration(); 1214 } 1215 1216 // Default configuration for primary zone is already set with core audio framework. 1217 // Therefore, no need to set default fade config as transient. When not primary, use default 1218 // fade configuration for transient if none is set. 1219 return (defaultCarAudioFadeConfigFromXml == null || mCarAudioZone.isPrimaryZone()) 1220 ? null : defaultCarAudioFadeConfigFromXml.getFadeManagerConfiguration(); 1221 } 1222 1223 // priority: transient from oem service > transient from xml getOptimalUsageBasedTransientFadeConfig(AudioAttributes attr, CarAudioFadeConfiguration transientCarAudioFadeConfigFromXml, Map<AudioAttributes, CarAudioFadeConfiguration> attrToCarAudioFadeConfigsFromOemService)1224 private CarAudioFadeConfiguration getOptimalUsageBasedTransientFadeConfig(AudioAttributes attr, 1225 CarAudioFadeConfiguration transientCarAudioFadeConfigFromXml, 1226 Map<AudioAttributes, 1227 CarAudioFadeConfiguration> attrToCarAudioFadeConfigsFromOemService) { 1228 if (!isFadeManagerSupported()) { 1229 return null; 1230 } 1231 1232 if (attrToCarAudioFadeConfigsFromOemService != null 1233 && !attrToCarAudioFadeConfigsFromOemService.isEmpty()) { 1234 return attrToCarAudioFadeConfigsFromOemService.get(attr); 1235 } 1236 return transientCarAudioFadeConfigFromXml; 1237 } 1238 } 1239