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