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 AudioFocusEntry focusEntry = 549 new AudioFocusEntry.Builder(audioFocusInfo, 550 mCarAudioContext.getContextForAudioAttribute( 551 audioFocusInfo.getAttributes()), 552 getVolumeGroupForAttribute(audioFocusInfo.getAttributes()), 553 AUDIOFOCUS_GAIN).build(); 554 555 OemCarAudioFocusResult focusResult = new OemCarAudioFocusResult.Builder( 556 convertAudioFocusEntries(holdersEvaluation.mChangedEntries), 557 convertAudioFocusEntries(losersEvaluation.mChangedEntries), 558 results).setAudioFocusEntry(focusEntry).build(); 559 t.traceEnd(); 560 return focusResult; 561 } 562 563 @GuardedBy("mLock") evaluateFocusRequestExternallyLocked(AudioFocusInfo requestInfo, FocusEntry replacedCurrentEntry)564 private OemCarAudioFocusResult evaluateFocusRequestExternallyLocked(AudioFocusInfo requestInfo, 565 FocusEntry replacedCurrentEntry) { 566 TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE); 567 t.traceBegin("evaluate-focus-request-externally"); 568 OemCarAudioFocusEvaluationRequest.Builder builder = 569 new OemCarAudioFocusEvaluationRequest.Builder(getMutedVolumeGroups(), 570 getAudioFocusEntries(mFocusHolders, replacedCurrentEntry), 571 getAudioFocusEntries(mFocusLosers, replacedCurrentEntry), mCarAudioZone.getId()) 572 .setAudioFocusRequest(convertAudioFocusInfo(requestInfo)); 573 574 if (mAudioFeaturesInfo != null) { 575 builder.setAudioFeaturesInfo(mAudioFeaturesInfo); 576 } 577 578 OemCarAudioFocusEvaluationRequest request = builder.build(); 579 580 logFocusEvent("Calling oem service with request " + request); 581 OemCarAudioFocusResult focusResult = CarLocalServices.getService(CarOemProxyService.class) 582 .getCarOemAudioFocusService().evaluateAudioFocusRequest(request); 583 logFocusEvent("oem service returns focus result " + focusResult); 584 t.traceEnd(); 585 return focusResult; 586 } 587 convertAudioFocusInfo(AudioFocusInfo info)588 private AudioFocusEntry convertAudioFocusInfo(AudioFocusInfo info) { 589 return new AudioFocusEntry.Builder(info, 590 mCarAudioContext.getContextForAudioAttribute(info.getAttributes()), 591 getVolumeGroupForAttribute(info.getAttributes()), 592 AUDIOFOCUS_LOSS_TRANSIENT).build(); 593 } 594 getAudioFocusEntries(ArrayMap<String, FocusEntry> entryMap, FocusEntry replacedCurrentEntry)595 private List<AudioFocusEntry> getAudioFocusEntries(ArrayMap<String, FocusEntry> entryMap, 596 FocusEntry replacedCurrentEntry) { 597 List<AudioFocusEntry> entries = new ArrayList<>(entryMap.size()); 598 for (int index = 0; index < entryMap.size(); index++) { 599 // Will consider focus evaluation for a current entry and replace it if focus is 600 // granted 601 if (replacedCurrentEntry != null 602 && replacedCurrentEntry.getClientId().equals(entryMap.keyAt(index))) { 603 continue; 604 } 605 entries.add(convertFocusEntry(entryMap.valueAt(index))); 606 } 607 return entries; 608 } 609 convertFocusEntry(FocusEntry entry)610 private AudioFocusEntry convertFocusEntry(FocusEntry entry) { 611 return convertAudioFocusInfo(entry.getAudioFocusInfo()); 612 } 613 getMutedVolumeGroups()614 private List<CarVolumeGroupInfo> getMutedVolumeGroups() { 615 return mCarVolumeInfoWrapper.getMutedVolumeGroups(mCarAudioZone.getId()); 616 } 617 isExternalFocusEnabled()618 private boolean isExternalFocusEnabled() { 619 CarOemProxyService proxy = CarLocalServices.getService(CarOemProxyService.class); 620 if (!proxy.isOemServiceEnabled()) { 621 return false; 622 } 623 624 if (!proxy.isOemServiceReady()) { 625 logFocusEvent("Focus was called but OEM service is not yet ready."); 626 return false; 627 } 628 629 return proxy.getCarOemAudioFocusService() != null; 630 } 631 convertAudioFocusEntries(List<FocusEntry> changedEntries)632 private List<AudioFocusEntry> convertAudioFocusEntries(List<FocusEntry> changedEntries) { 633 List<AudioFocusEntry> audioFocusEntries = new ArrayList<>(changedEntries.size()); 634 for (int index = 0; index < changedEntries.size(); index++) { 635 audioFocusEntries.add(convertFocusEntry(changedEntries.get(index))); 636 } 637 return audioFocusEntries; 638 } 639 getVolumeGroupForAttribute(AudioAttributes attributes)640 private int getVolumeGroupForAttribute(AudioAttributes attributes) { 641 return mCarVolumeInfoWrapper.getVolumeGroupIdForAudioAttribute(mCarAudioZone.getId(), 642 attributes); 643 } 644 645 @GuardedBy("mLock") evaluateAgainstFocusLosersLocked( FocusEntry replacedBlockedEntry, int requestedUsage, boolean allowDucking, boolean allowDelayedFocus)646 private FocusEvaluation evaluateAgainstFocusLosersLocked( 647 FocusEntry replacedBlockedEntry, int requestedUsage, boolean allowDucking, 648 boolean allowDelayedFocus) { 649 Slogf.i(TAG, "Scanning those who've already lost focus..."); 650 return evaluateAgainstFocusArrayLocked(mFocusLosers, replacedBlockedEntry, 651 requestedUsage, allowDucking, allowDelayedFocus); 652 } 653 654 @GuardedBy("mLock") evaluateAgainstFocusHoldersLocked( FocusEntry replacedCurrentEntry, int requestedUsage, boolean allowDucking, boolean allowDelayedFocus)655 private FocusEvaluation evaluateAgainstFocusHoldersLocked( 656 FocusEntry replacedCurrentEntry, int requestedUsage, boolean allowDucking, 657 boolean allowDelayedFocus) { 658 Slogf.i(TAG, "Scanning focus holders..."); 659 return evaluateAgainstFocusArrayLocked(mFocusHolders, replacedCurrentEntry, 660 requestedUsage, allowDucking, allowDelayedFocus); 661 } 662 663 @GuardedBy("mLock") evaluateAgainstFocusArrayLocked(ArrayMap<String, FocusEntry> focusArray, FocusEntry replacedEntry, int requestedUsage, boolean allowDucking, boolean allowDelayedFocus)664 private FocusEvaluation evaluateAgainstFocusArrayLocked(ArrayMap<String, FocusEntry> focusArray, 665 FocusEntry replacedEntry, int requestedUsage, boolean allowDucking, 666 boolean allowDelayedFocus) { 667 boolean delayFocusForCurrentRequest = false; 668 ArrayList<FocusEntry> changedEntries = new ArrayList<FocusEntry>(); 669 for (int index = 0; index < focusArray.size(); index++) { 670 FocusEntry entry = focusArray.valueAt(index); 671 Slogf.i(TAG, entry.getAudioFocusInfo().getClientId()); 672 673 if (replacedEntry != null && entry.getClientId().equals(replacedEntry.getClientId())) { 674 continue; 675 } 676 677 int interactionResult = mFocusInteraction.evaluateRequest(requestedUsage, entry, 678 allowDucking, allowDelayedFocus, changedEntries); 679 if (interactionResult == AUDIOFOCUS_REQUEST_FAILED) { 680 return FocusEvaluation.FOCUS_EVALUATION_FAILED; 681 } 682 if (interactionResult == AUDIOFOCUS_REQUEST_DELAYED) { 683 delayFocusForCurrentRequest = true; 684 } 685 } 686 int results = delayFocusForCurrentRequest 687 ? AUDIOFOCUS_REQUEST_DELAYED : AUDIOFOCUS_REQUEST_GRANTED; 688 return new FocusEvaluation(changedEntries, results); 689 } 690 canSwapCallOrRingerClientRequest(String clientId, AudioAttributes currentAttributes, AudioAttributes requestedAttributes)691 private static boolean canSwapCallOrRingerClientRequest(String clientId, 692 AudioAttributes currentAttributes, AudioAttributes requestedAttributes) { 693 return isCallFocusRequestClientId(clientId) 694 && isRingerOrCallAudioAttributes(currentAttributes) 695 && isRingerOrCallAudioAttributes(requestedAttributes); 696 } 697 isRingerOrCallAudioAttributes(AudioAttributes attributes)698 private static boolean isRingerOrCallAudioAttributes(AudioAttributes attributes) { 699 return CarAudioContext.isRingerOrCallAudioAttribute(attributes); 700 } 701 702 @Override onAudioFocusRequest(AudioFocusInfo afi, int requestResult)703 public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) { 704 int response; 705 AudioPolicy policy; 706 synchronized (mLock) { 707 policy = mAudioPolicy; 708 response = evaluateFocusRequestLocked(afi); 709 } 710 711 // Post our reply for delivery to the original focus requester 712 mAudioManager.setFocusRequestResult(afi, response, policy); 713 logFocusEvent("onAudioFocusRequest for client " + afi.getClientId() 714 + " with gain type " + focusEventToString(afi.getGainRequest()) 715 + " resulted in " + focusRequestResponseToString(response)); 716 } 717 718 @GuardedBy("mLock") swapDelayedAudioFocusRequestLocked(AudioFocusInfo afi)719 private void swapDelayedAudioFocusRequestLocked(AudioFocusInfo afi) { 720 // If we are swapping to a different client then send the focus loss signal 721 if (mDelayedRequest != null 722 && !afi.getClientId().equals(mDelayedRequest.getClientId())) { 723 sendFocusLossLocked(mDelayedRequest, AUDIOFOCUS_LOSS, afi, /* shouldFade= */ false, 724 /* transientFadeManagerConfig= */ null); 725 } 726 mDelayedRequest = afi; 727 } 728 canReceiveDelayedFocus(AudioFocusInfo afi)729 private boolean canReceiveDelayedFocus(AudioFocusInfo afi) { 730 if (afi.getGainRequest() != AUDIOFOCUS_GAIN) { 731 return false; 732 } 733 return (afi.getFlags() & AUDIOFOCUS_FLAG_DELAY_OK) == AUDIOFOCUS_FLAG_DELAY_OK; 734 } 735 736 /** 737 * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) 738 * Note that we'll get this call for a focus holder that dies while in the focus stack, so 739 * we don't need to watch for death notifications directly. 740 * */ 741 @Override onAudioFocusAbandon(AudioFocusInfo afi)742 public void onAudioFocusAbandon(AudioFocusInfo afi) { 743 logFocusEvent("onAudioFocusAbandon for client " + afi.getClientId()); 744 synchronized (mLock) { 745 FocusEntry deadEntry = removeFocusEntryLocked(afi); 746 747 if (deadEntry != null) { 748 removeBlockerAndRestoreUnblockedWaitersLocked(deadEntry); 749 } 750 } 751 } 752 753 /** 754 * Remove Focus entry from focus holder or losers entry lists 755 * @param afi Audio Focus Info to remove 756 * @return Removed Focus Entry 757 */ 758 @GuardedBy("mLock") removeFocusEntryLocked(AudioFocusInfo afi)759 private FocusEntry removeFocusEntryLocked(AudioFocusInfo afi) { 760 Slogf.i(TAG, "removeFocusEntry " + afi.getClientId()); 761 if (mDelayedRequest != null && afi.getClientId().equals(mDelayedRequest.getClientId())) { 762 logFocusEvent("Audio focus abandoned for delayed focus entry " + afi.getClientId()); 763 mDelayedRequest = null; 764 return null; 765 } 766 // Remove this entry from our active or pending list 767 FocusEntry deadEntry = mFocusHolders.remove(afi.getClientId()); 768 if (deadEntry == null) { 769 deadEntry = mFocusLosers.remove(afi.getClientId()); 770 if (deadEntry == null) { 771 // Caller is providing an unrecognized clientId!? 772 Slogf.w(TAG, "Audio focus abandoned by unrecognized client id: " 773 + afi.getClientId()); 774 // This probably means an app double released focused for some reason. One 775 // harmless possibility is a race between an app being told it lost focus and the 776 // app voluntarily abandoning focus. More likely the app is just sloppy. :) 777 // The more nefarious possibility is that the clientId is actually corrupted 778 // somehow, in which case we might have a real focus entry that we're going to fail 779 // to remove. If that were to happen, I'd expect either the app to swallow it 780 // silently, or else take unexpected action (eg: resume playing spontaneously), or 781 // else to see "Failure to signal ..." gain/loss error messages in the log from 782 // this module when a focus change tries to take action on a truly zombie entry. 783 } 784 } 785 return deadEntry; 786 } 787 788 @GuardedBy("mLock") removeBlockerAndRestoreUnblockedWaitersLocked(FocusEntry deadEntry)789 private void removeBlockerAndRestoreUnblockedWaitersLocked(FocusEntry deadEntry) { 790 attemptToGainFocusForDelayedAudioFocusRequestLocked(); 791 removeBlockerAndRestoreUnblockedFocusLosersLocked(deadEntry); 792 } 793 794 @GuardedBy("mLock") attemptToGainFocusForDelayedAudioFocusRequestLocked()795 private void attemptToGainFocusForDelayedAudioFocusRequestLocked() { 796 if (mDelayedRequest == null) { 797 return; 798 } 799 // Prevent cleanup of permanent lost to recall attemptToGainFocusForDelayedAudioFocusRequest 800 // Whatever granted / denied / delayed again, no need to restore, mDelayedRequest restored 801 // if delayed again. 802 AudioFocusInfo delayedFocusInfo = mDelayedRequest; 803 mDelayedRequest = null; 804 int delayedFocusRequestResults = evaluateFocusRequestLocked(delayedFocusInfo); 805 if (delayedFocusRequestResults == AUDIOFOCUS_REQUEST_GRANTED) { 806 FocusEntry focusEntry = mFocusHolders.get(delayedFocusInfo.getClientId()); 807 if (dispatchFocusGainedLocked(focusEntry.getAudioFocusInfo()) 808 == AUDIOFOCUS_REQUEST_FAILED) { 809 Slogf.e(TAG, "Failure to signal gain of audio focus gain for " 810 + "delayed focus clientId " + focusEntry.getClientId()); 811 mFocusHolders.remove(focusEntry.getClientId()); 812 removeBlockerFromBlockedFocusLosersLocked(focusEntry); 813 sendFocusLossLocked(focusEntry.getAudioFocusInfo(), AUDIOFOCUS_LOSS, 814 /* winner= */ null, /* shouldFade= */ false, 815 /* transientFadeManagerConfig = */ null); 816 logFocusEvent("Did not gain delayed audio focus for " + focusEntry.getClientId()); 817 } 818 } else if (delayedFocusRequestResults == AUDIOFOCUS_REQUEST_FAILED) { 819 // Delayed request has permanently be denied 820 logFocusEvent("Delayed audio focus retry failed for " + delayedFocusInfo.getClientId()); 821 sendFocusLossLocked(delayedFocusInfo, AUDIOFOCUS_LOSS, /* winner= */ null, 822 /* shouldFade= */ false, /* transientFadeManagerConfig = */ null); 823 } else { 824 assert mDelayedRequest.equals(delayedFocusInfo); 825 } 826 } 827 828 /** 829 * Removes the dead entry from blocked waiters but does not send focus gain signal 830 */ 831 @GuardedBy("mLock") removeBlockerFromBlockedFocusLosersLocked(FocusEntry deadEntry)832 private void removeBlockerFromBlockedFocusLosersLocked(FocusEntry deadEntry) { 833 // Remove this entry from the blocking list of any pending requests 834 Iterator<FocusEntry> it = mFocusLosers.values().iterator(); 835 while (it.hasNext()) { 836 FocusEntry entry = it.next(); 837 // Remove the retiring entry from all blocker lists 838 entry.removeBlocker(deadEntry); 839 } 840 } 841 842 /** 843 * Removes the dead entry from blocked waiters and sends focus gain signal 844 */ 845 @GuardedBy("mLock") removeBlockerAndRestoreUnblockedFocusLosersLocked(FocusEntry deadEntry)846 private void removeBlockerAndRestoreUnblockedFocusLosersLocked(FocusEntry deadEntry) { 847 // Remove this entry from the blocking list of any pending requests 848 Iterator<FocusEntry> it = mFocusLosers.values().iterator(); 849 while (it.hasNext()) { 850 FocusEntry entry = it.next(); 851 852 // Remove the retiring entry from all blocker lists 853 entry.removeBlocker(deadEntry); 854 855 // Any entry whose blocking list becomes empty should regain focus 856 if (entry.isUnblocked()) { 857 Slogf.i(TAG, "Restoring unblocked entry " + entry.getClientId()); 858 // Pull this entry out of the focus losers list 859 it.remove(); 860 861 // Clear ducked status to prevent spurious LOSS_TRANSIENT to be sent while checking 862 // blocked entries and converting duckable loss to non-duckable 863 entry.setDucked(false); 864 865 // Add it back into the focus holders list 866 mFocusHolders.put(entry.getClientId(), entry); 867 868 dispatchFocusGainedLocked(entry.getAudioFocusInfo()); 869 } 870 } 871 } 872 873 /** 874 * Dispatch focus gain 875 * @param afi Audio focus info 876 * @return {@link AUDIOFOCUS_REQUEST_GRANTED} if focus is dispatched successfully 877 */ dispatchFocusGainedLocked(AudioFocusInfo afi)878 private int dispatchFocusGainedLocked(AudioFocusInfo afi) { 879 // Send the focus (re)gain notification 880 int result = mAudioManager.dispatchAudioFocusChange(afi, AUDIOFOCUS_GAIN, mAudioPolicy); 881 if (result == AUDIOFOCUS_REQUEST_FAILED) { 882 // TODO: Is this actually an error, or is it okay for an entry in the focus 883 // stack to NOT have a listener? If that's the case, should we even keep 884 // it in the focus stack? 885 Slogf.e(TAG, "Failure to signal gain of audio focus with error: " + result); 886 } 887 888 logFocusEvent("dispatchFocusGainedLocked for client " + afi.getClientId() 889 + " with gain type " + focusEventToString(afi.getGainRequest()) 890 + " resulted in " + focusRequestResponseToString(result)); 891 return result; 892 } 893 894 /** 895 * Query the current list of focus loser for uid 896 * @param uid uid to query current focus loser 897 * @return list of current focus losers for uid 898 */ getAudioFocusLosersForUid(int uid)899 ArrayList<AudioFocusInfo> getAudioFocusLosersForUid(int uid) { 900 synchronized (mLock) { 901 return getAudioFocusList(new UidAudioFocusInfoComparator(uid), mFocusLosers); 902 } 903 } 904 getAudioFocusHolders()905 List<AudioFocusInfo> getAudioFocusHolders() { 906 synchronized (mLock) { 907 return getAudioFocusInfos(mFocusHolders); 908 } 909 } 910 911 /** 912 * Query the current list of focus holders for uid 913 * @param uid uid to query current focus holders 914 * @return list of current focus holders that for uid 915 */ getAudioFocusHoldersForUid(int uid)916 ArrayList<AudioFocusInfo> getAudioFocusHoldersForUid(int uid) { 917 synchronized (mLock) { 918 return getAudioFocusList(new UidAudioFocusInfoComparator(uid), mFocusHolders); 919 } 920 } 921 getAudioFocusLosers()922 List<AudioFocusInfo> getAudioFocusLosers() { 923 synchronized (mLock) { 924 return getAudioFocusInfos(mFocusLosers); 925 } 926 } 927 928 /** 929 * Remove the audio focus info, if entry is still active 930 * dispatch lose focus transient to listeners 931 * @param afi Audio Focus info to remove 932 */ removeAudioFocusInfoAndTransientlyLoseFocus(AudioFocusInfo afi)933 void removeAudioFocusInfoAndTransientlyLoseFocus(AudioFocusInfo afi) { 934 synchronized (mLock) { 935 FocusEntry deadEntry = removeFocusEntryLocked(afi); 936 if (deadEntry != null) { 937 sendFocusLossLocked(deadEntry.getAudioFocusInfo(), AUDIOFOCUS_LOSS_TRANSIENT, 938 /* winner= */ null, /* shouldFade= */ false, 939 /* transientFadeManagerConfig = */ null); 940 removeBlockerAndRestoreUnblockedWaitersLocked(deadEntry); 941 } 942 } 943 } 944 945 /** 946 * Reevaluate focus request and regain focus 947 * @param afi audio focus info to reevaluate 948 * @return {@link AUDIOFOCUS_REQUEST_GRANTED} if focus is granted 949 */ reevaluateAndRegainAudioFocus(AudioFocusInfo afi)950 int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) { 951 int results; 952 synchronized (mLock) { 953 results = evaluateFocusRequestLocked(afi); 954 if (results == AUDIOFOCUS_REQUEST_GRANTED) { 955 results = dispatchFocusGainedLocked(afi); 956 } 957 } 958 959 return results; 960 } 961 962 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)963 public void dump(IndentingPrintWriter writer) { 964 synchronized (mLock) { 965 writer.println("*CarAudioFocus*"); 966 writer.increaseIndent(); 967 writer.printf("Audio Zone ID: %d\n", mCarAudioZone.getId()); 968 writer.printf("Is focus restricted? %b\n", mIsFocusRestricted); 969 writer.printf("Is external focus eval enabled? %b\n", isExternalFocusEnabled()); 970 writer.println(); 971 mFocusInteraction.dump(writer); 972 973 writer.println("Current Focus Holders:"); 974 writer.increaseIndent(); 975 for (String clientId : mFocusHolders.keySet()) { 976 mFocusHolders.get(clientId).dump(writer); 977 } 978 writer.decreaseIndent(); 979 980 writer.println("Transient Focus Losers:"); 981 writer.increaseIndent(); 982 for (String clientId : mFocusLosers.keySet()) { 983 mFocusLosers.get(clientId).dump(writer); 984 } 985 writer.decreaseIndent(); 986 987 writer.printf("Queued Delayed Focus: %s\n", 988 mDelayedRequest == null ? "None" : mDelayedRequest.getClientId()); 989 990 writer.println("Focus Events:"); 991 writer.increaseIndent(); 992 mFocusEventLogger.dump(writer); 993 writer.decreaseIndent(); 994 995 writer.decreaseIndent(); 996 } 997 } 998 999 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)1000 public void dumpProto(ProtoOutputStream proto) { 1001 long carAudioFocusToken = proto.start(CarAudioZoneFocusProto.CAR_AUDIO_FOCUSES); 1002 synchronized (mLock) { 1003 proto.write(CarAudioFocusProto.ZONE_ID, mCarAudioZone.getId()); 1004 proto.write(CarAudioFocusProto.FOCUS_RESTRICTED, mIsFocusRestricted); 1005 proto.write(CarAudioFocusProto.EXTERNAL_FOCUS_ENABLED, isExternalFocusEnabled()); 1006 1007 mFocusInteraction.dumpProto(proto); 1008 1009 for (String clientId : mFocusHolders.keySet()) { 1010 mFocusHolders.get(clientId).dumpProto(CarAudioFocusProto.FOCUS_HOLDERS, proto); 1011 } 1012 1013 for (String clientId : mFocusLosers.keySet()) { 1014 mFocusLosers.get(clientId).dumpProto(CarAudioFocusProto.FOCUS_LOSERS, proto); 1015 } 1016 1017 if (mDelayedRequest != null) { 1018 proto.write(CarAudioFocusProto.DELAYED_FOCUS, mDelayedRequest.getClientId()); 1019 } 1020 } 1021 proto.end(carAudioFocusToken); 1022 } 1023 focusEventToString(int focusEvent)1024 private static String focusEventToString(int focusEvent) { 1025 switch (focusEvent) { 1026 case AUDIOFOCUS_GAIN: 1027 return "GAIN"; 1028 case AUDIOFOCUS_GAIN_TRANSIENT: 1029 return "GAIN_TRANSIENT"; 1030 case AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 1031 return "GAIN_TRANSIENT_EXCLUSIVE"; 1032 case AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 1033 return "GAIN_TRANSIENT_MAY_DUCK"; 1034 case AUDIOFOCUS_LOSS: 1035 return "LOSS"; 1036 case AUDIOFOCUS_LOSS_TRANSIENT: 1037 return "LOSS_TRANSIENT"; 1038 case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 1039 return "LOSS_TRANSIENT_CAN_DUCK"; 1040 default: 1041 return "unknown event " + focusEvent; 1042 } 1043 } 1044 focusRequestResponseToString(int response)1045 private static String focusRequestResponseToString(int response) { 1046 if (response == AUDIOFOCUS_REQUEST_GRANTED) { 1047 return "REQUEST_GRANTED"; 1048 } else if (response == AUDIOFOCUS_REQUEST_FAILED) { 1049 return "REQUEST_FAILED"; 1050 } 1051 return "REQUEST_DELAYED"; 1052 } 1053 logFocusEvent(String log)1054 private void logFocusEvent(String log) { 1055 mFocusEventLogger.log(log); 1056 Slogf.i(TAG, log); 1057 } 1058 1059 /** 1060 * Returns the focus interaction for this car focus instance. 1061 */ getFocusInteraction()1062 public FocusInteraction getFocusInteraction() { 1063 return mFocusInteraction; 1064 } 1065 1066 private static final class FocusEvaluation { 1067 1068 private static final FocusEvaluation FOCUS_EVALUATION_FAILED = 1069 new FocusEvaluation(/* changedEntries= */ new ArrayList<>(/* initialCap= */ 0), 1070 AUDIOFOCUS_REQUEST_FAILED); 1071 1072 private final List<FocusEntry> mChangedEntries; 1073 private final int mAudioFocusEvalResults; 1074 FocusEvaluation(List<FocusEntry> changedEntries, int audioFocusEvalResults)1075 FocusEvaluation(List<FocusEntry> changedEntries, int audioFocusEvalResults) { 1076 mChangedEntries = changedEntries; 1077 mAudioFocusEvalResults = audioFocusEvalResults; 1078 } 1079 1080 @Override 1081 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) toString()1082 public String toString() { 1083 return new StringBuilder().append("{Changed Entries: ").append(mChangedEntries) 1084 .append(", Results: ").append(mAudioFocusEvalResults) 1085 .append(" }").toString(); 1086 } 1087 } 1088 1089 /** 1090 * Returns the currently active focus holder for media 1091 * 1092 * @param userId user id to select 1093 * @param audioAttributes audio attributes to query 1094 * @return list of currently active focus holder with matching audio attribute 1095 */ getActiveAudioFocusForUserAndAudioAttributes( AudioAttributes audioAttributes, @UserIdInt int userId)1096 public List<AudioFocusInfo> getActiveAudioFocusForUserAndAudioAttributes( 1097 AudioAttributes audioAttributes, @UserIdInt int userId) { 1098 Objects.requireNonNull(audioAttributes, 1099 "Audio attributes can no be null"); 1100 synchronized (mLock) { 1101 return getAudioFocusList( 1102 new UserIdAndAudioAttributeAudioFocusInfoComparator(audioAttributes, userId), 1103 mFocusHolders); 1104 } 1105 } 1106 1107 /** 1108 * Returns the currently inactive focus holder for a particular audio attributes 1109 * 1110 * @param audioAttributes audio attributes to query 1111 * @param userId user id to select 1112 * @return list of currently inactive focus holder with matching audio attribute 1113 */ getInactiveAudioFocusForUserAndAudioAttributes( AudioAttributes audioAttributes, @UserIdInt int userId)1114 public List<AudioFocusInfo> getInactiveAudioFocusForUserAndAudioAttributes( 1115 AudioAttributes audioAttributes, @UserIdInt int userId) { 1116 Objects.requireNonNull(audioAttributes, 1117 "Audio Attributes can no be null"); 1118 synchronized (mLock) { 1119 List<AudioFocusInfo> inactiveList = getAudioFocusList( 1120 new UserIdAndAudioAttributeAudioFocusInfoComparator(audioAttributes, userId), 1121 mFocusLosers); 1122 1123 if (mDelayedRequest != null 1124 && CarAudioContext.AudioAttributesWrapper.audioAttributeMatches( 1125 audioAttributes, mDelayedRequest.getAttributes())) { 1126 inactiveList.add(mDelayedRequest); 1127 mDelayedRequest = null; 1128 } 1129 1130 return inactiveList; 1131 } 1132 } 1133 getAudioFocusInfos( ArrayMap<String, FocusEntry> focusEntries)1134 private static List<AudioFocusInfo> getAudioFocusInfos( 1135 ArrayMap<String, FocusEntry> focusEntries) { 1136 List<AudioFocusInfo> focusInfos = new ArrayList<>(focusEntries.size()); 1137 for (int index = 0; index < focusEntries.size(); index++) { 1138 focusInfos.add(focusEntries.valueAt(index).getAudioFocusInfo()); 1139 } 1140 return focusInfos; 1141 } 1142 getAudioFocusList(AudioFocusInfoComparator comparator, Map<String, FocusEntry> mapToQuery)1143 private static ArrayList<AudioFocusInfo> getAudioFocusList(AudioFocusInfoComparator comparator, 1144 Map<String, FocusEntry> mapToQuery) { 1145 ArrayList<AudioFocusInfo> matchingInfoList = new ArrayList<>(); 1146 for (String clientId : mapToQuery.keySet()) { 1147 AudioFocusInfo afi = mapToQuery.get(clientId).getAudioFocusInfo(); 1148 if (comparator.matches(afi)) { 1149 matchingInfoList.add(afi); 1150 } 1151 } 1152 return matchingInfoList; 1153 } 1154 1155 private interface AudioFocusInfoComparator { matches(AudioFocusInfo afi)1156 boolean matches(AudioFocusInfo afi); 1157 } 1158 1159 private static final class UidAudioFocusInfoComparator implements AudioFocusInfoComparator { 1160 1161 private final int mUid; 1162 UidAudioFocusInfoComparator(int uid)1163 UidAudioFocusInfoComparator(int uid) { 1164 mUid = uid; 1165 } 1166 1167 @Override matches(AudioFocusInfo afi)1168 public boolean matches(AudioFocusInfo afi) { 1169 return afi.getClientUid() == mUid; 1170 } 1171 } 1172 1173 private static final class UserIdAndAudioAttributeAudioFocusInfoComparator 1174 implements AudioFocusInfoComparator { 1175 1176 private final int mUserId; 1177 private final AudioAttributes mAudioAttribute; 1178 UserIdAndAudioAttributeAudioFocusInfoComparator( AudioAttributes audioAttributes, @UserIdInt int userId)1179 UserIdAndAudioAttributeAudioFocusInfoComparator( 1180 AudioAttributes audioAttributes, @UserIdInt int userId) { 1181 mAudioAttribute = audioAttributes; 1182 mUserId = userId; 1183 } 1184 1185 @Override matches(AudioFocusInfo afi)1186 public boolean matches(AudioFocusInfo afi) { 1187 return (UserHandle.getUserHandleForUid(afi.getClientUid()).getIdentifier() == mUserId) 1188 && CarAudioContext.AudioAttributesWrapper 1189 .audioAttributeMatches(mAudioAttribute, afi.getAttributes()); 1190 } 1191 } 1192 getTransientFadeManagerConfig( CarAudioFadeConfiguration defaultCarAudioFadeConfigFromXml, CarAudioFadeConfiguration transientCarAudioFadeConfig)1193 private FadeManagerConfiguration getTransientFadeManagerConfig( 1194 CarAudioFadeConfiguration defaultCarAudioFadeConfigFromXml, 1195 CarAudioFadeConfiguration transientCarAudioFadeConfig) { 1196 if (!isFadeManagerSupported()) { 1197 return null; 1198 } 1199 1200 if (transientCarAudioFadeConfig != null) { 1201 return transientCarAudioFadeConfig.getFadeManagerConfiguration(); 1202 } 1203 1204 // Default configuration for primary zone is already set with core audio framework. 1205 // Therefore, no need to set default fade config as transient. When not primary, use default 1206 // fade configuration for transient if none is set. 1207 return (defaultCarAudioFadeConfigFromXml == null || mCarAudioZone.isPrimaryZone()) 1208 ? null : defaultCarAudioFadeConfigFromXml.getFadeManagerConfiguration(); 1209 } 1210 1211 // priority: transient from oem service > transient from xml getOptimalUsageBasedTransientFadeConfig(AudioAttributes attr, CarAudioFadeConfiguration transientCarAudioFadeConfigFromXml, Map<AudioAttributes, CarAudioFadeConfiguration> attrToCarAudioFadeConfigsFromOemService)1212 private CarAudioFadeConfiguration getOptimalUsageBasedTransientFadeConfig(AudioAttributes attr, 1213 CarAudioFadeConfiguration transientCarAudioFadeConfigFromXml, 1214 Map<AudioAttributes, 1215 CarAudioFadeConfiguration> attrToCarAudioFadeConfigsFromOemService) { 1216 if (!isFadeManagerSupported()) { 1217 return null; 1218 } 1219 1220 if (attrToCarAudioFadeConfigsFromOemService != null 1221 && !attrToCarAudioFadeConfigsFromOemService.isEmpty()) { 1222 return attrToCarAudioFadeConfigsFromOemService.get(attr); 1223 } 1224 return transientCarAudioFadeConfigFromXml; 1225 } 1226 } 1227