• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.audio;
17 
18 import static 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