• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.car.audio.hal;
18 
19 import static android.car.builtin.media.AudioManagerHelper.usageToString;
20 import static android.media.AudioManager.AUDIOFOCUS_LOSS;
21 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
22 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED;
23 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
24 
25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
26 
27 import android.annotation.NonNull;
28 import android.car.builtin.util.Slogf;
29 import android.car.media.CarAudioManager;
30 import android.media.AudioAttributes;
31 import android.media.AudioFocusRequest;
32 import android.media.AudioManager;
33 import android.os.Bundle;
34 import android.util.ArrayMap;
35 import android.util.ArraySet;
36 import android.util.Log;
37 import android.util.SparseArray;
38 
39 import com.android.car.CarLog;
40 import com.android.car.audio.CarAudioContext;
41 import com.android.car.audio.CarAudioContext.AudioAttributesWrapper;
42 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
43 import com.android.car.internal.annotation.AttributeUsage;
44 import com.android.car.internal.util.IndentingPrintWriter;
45 import com.android.internal.annotations.GuardedBy;
46 import com.android.internal.util.Preconditions;
47 
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Objects;
52 import java.util.Set;
53 
54 /**
55  * Manages focus requests from the HAL on a per-zone per-usage basis
56  */
57 public final class HalAudioFocus implements HalFocusListener {
58     private static final String TAG = CarLog.tagFor(HalAudioFocus.class);
59 
60     private final AudioManager mAudioManager;
61     private final AudioControlWrapper mAudioControlWrapper;
62 
63     private final Object mLock = new Object();
64 
65     // Map of Maps. Top level keys are ZoneIds. Second level keys are audio attribute wrapper.
66     // Values are HalAudioFocusRequests
67     @GuardedBy("mLock")
68     private final SparseArray<Map<AudioAttributesWrapper, HalAudioFocusRequest>>
69             mHalFocusRequestsByZoneAndUsage;
70 
HalAudioFocus(@onNull AudioManager audioManager, @NonNull AudioControlWrapper audioControlWrapper, @NonNull int[] audioZoneIds)71     public HalAudioFocus(@NonNull AudioManager audioManager,
72             @NonNull AudioControlWrapper audioControlWrapper,
73             @NonNull int[] audioZoneIds) {
74         mAudioManager = Objects.requireNonNull(audioManager);
75         mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper);
76         Objects.requireNonNull(audioZoneIds);
77 
78         mHalFocusRequestsByZoneAndUsage = new SparseArray<>(audioZoneIds.length);
79         for (int index = 0; index < audioZoneIds.length; index++) {
80             mHalFocusRequestsByZoneAndUsage.put(audioZoneIds[index], new ArrayMap<>());
81         }
82     }
83 
84     /**
85      * Registers {@code IFocusListener} on {@code AudioControlWrapper} to receive HAL audio focus
86      * request and abandon calls.
87      */
registerFocusListener()88     public void registerFocusListener() {
89         mAudioControlWrapper.registerFocusListener(this);
90     }
91 
92     /**
93      * Unregisters {@code IFocusListener} from {@code AudioControlWrapper}.
94      */
unregisterFocusListener()95     public void unregisterFocusListener() {
96         mAudioControlWrapper.unregisterFocusListener();
97     }
98 
99     /**
100      * See {@link HalFocusListener#requestAudioFocus(int, int, int)}
101      */
requestAudioFocus(@ttributeUsage int usage, int zoneId, int focusGain)102     public void requestAudioFocus(@AttributeUsage int usage, int zoneId, int focusGain) {
103         synchronized (mLock) {
104             Preconditions.checkArgument(mHalFocusRequestsByZoneAndUsage.contains(zoneId),
105                     "Invalid zoneId %d provided in requestAudioFocus", zoneId);
106             if (Slogf.isLoggable(TAG, Log.DEBUG)) {
107                 Slogf.d(TAG, "Requesting focus gain " + focusGain + " with usage "
108                         + usageToString(usage) + " and zoneId " + zoneId);
109             }
110             AudioAttributesWrapper audioAttributesWrapper =
111                     CarAudioContext.getAudioAttributeWrapperFromUsage(usage);
112             HalAudioFocusRequest currentRequest =
113                     mHalFocusRequestsByZoneAndUsage.get(zoneId).get(audioAttributesWrapper);
114             if (currentRequest != null) {
115                 if (Slogf.isLoggable(TAG, Log.DEBUG)) {
116                     Slogf.d(TAG, "A request already exists for zoneId " + zoneId + " and usage "
117                             + usage);
118                 }
119                 mAudioControlWrapper.onAudioFocusChange(usage, zoneId, currentRequest.mFocusStatus);
120             } else {
121                 makeAudioFocusRequestLocked(audioAttributesWrapper, zoneId, focusGain);
122             }
123         }
124     }
125 
126     /**
127      * See {@link HalFocusListener#abandonAudioFocus(int, int)}
128      */
abandonAudioFocus(@ttributeUsage int usage, int zoneId)129     public void abandonAudioFocus(@AttributeUsage int usage, int zoneId) {
130         synchronized (mLock) {
131             Preconditions.checkArgument(mHalFocusRequestsByZoneAndUsage.contains(zoneId),
132                     "Invalid zoneId %d provided in abandonAudioFocus", zoneId);
133             if (Slogf.isLoggable(TAG, Log.DEBUG)) {
134                 Slogf.d(TAG, "Abandoning focus with usage " + usageToString(usage)
135                         + " for zoneId " + zoneId);
136             }
137             abandonAudioFocusLocked(CarAudioContext.getAudioAttributeWrapperFromUsage(usage),
138                     zoneId);
139         }
140     }
141 
142     /**
143      * Clear out all existing focus requests. Called when HAL dies.
144      */
reset()145     public void reset() {
146         Slogf.d(TAG, "Resetting HAL Audio Focus requests");
147         synchronized (mLock) {
148             for (int i = 0; i < mHalFocusRequestsByZoneAndUsage.size(); i++) {
149                 int zoneId = mHalFocusRequestsByZoneAndUsage.keyAt(i);
150                 Map<AudioAttributesWrapper, HalAudioFocusRequest>
151                         requestsByAttributes = mHalFocusRequestsByZoneAndUsage.valueAt(i);
152                 Set<AudioAttributesWrapper> wrapperSet =
153                         new ArraySet<>(requestsByAttributes.keySet());
154                 for (AudioAttributesWrapper wrapper : wrapperSet) {
155                     abandonAudioFocusLocked(wrapper, zoneId);
156                 }
157             }
158         }
159     }
160 
161     /**
162      * Returns the currently active {@link AudioAttribute}'s for an audio zone
163      */
getActiveAudioAttributesForZone(int audioZoneId)164     public List<AudioAttributes> getActiveAudioAttributesForZone(int audioZoneId) {
165         synchronized (mLock) {
166             Map<AudioAttributesWrapper, HalAudioFocusRequest> halFocusRequestsForZone =
167                     mHalFocusRequestsByZoneAndUsage.get(audioZoneId);
168             List<AudioAttributes> activeAudioAttributes =
169                     new ArrayList<>(halFocusRequestsForZone.size());
170 
171             for (AudioAttributesWrapper wrapper : halFocusRequestsForZone.keySet()) {
172                 activeAudioAttributes.add(wrapper.getAudioAttributes());
173             }
174 
175             return activeAudioAttributes;
176         }
177     }
178 
179     /**
180      * dumps the current state of the HalAudioFocus
181      *
182      * @param writer stream to write current state
183      */
184     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)185     public void dump(IndentingPrintWriter writer) {
186         writer.println("*HalAudioFocus*");
187 
188         writer.increaseIndent();
189         writer.println("Current focus requests:");
190         writer.increaseIndent();
191         synchronized (mLock) {
192             for (int i = 0; i < mHalFocusRequestsByZoneAndUsage.size(); i++) {
193                 int zoneId = mHalFocusRequestsByZoneAndUsage.keyAt(i);
194                 writer.printf("Zone %s:\n", zoneId);
195                 writer.increaseIndent();
196 
197                 Map<AudioAttributesWrapper, HalAudioFocusRequest> requestsByAttributes =
198                         mHalFocusRequestsByZoneAndUsage.valueAt(i);
199                 for (HalAudioFocusRequest request : requestsByAttributes.values()) {
200                     writer.printf("%s\n", request);
201                 }
202                 writer.decreaseIndent();
203             }
204         }
205         writer.decreaseIndent();
206         writer.decreaseIndent();
207     }
208 
209     @GuardedBy("mLock")
abandonAudioFocusLocked(AudioAttributesWrapper audioAttributesWrapper, int zoneId)210     private void abandonAudioFocusLocked(AudioAttributesWrapper audioAttributesWrapper,
211             int zoneId) {
212         Map<AudioAttributesWrapper, HalAudioFocusRequest> halAudioFocusRequests =
213                 mHalFocusRequestsByZoneAndUsage.get(zoneId);
214         HalAudioFocusRequest currentRequest = halAudioFocusRequests.get(audioAttributesWrapper);
215 
216         if (currentRequest == null) {
217             if (Slogf.isLoggable(TAG, Log.DEBUG)) {
218                 Slogf.d(TAG, "No focus to abandon for audio attributes " + audioAttributesWrapper
219                         + " and zoneId " + zoneId);
220             }
221             return;
222         } else {
223             // remove it from map
224             halAudioFocusRequests.remove(audioAttributesWrapper);
225         }
226 
227         int result = mAudioManager.abandonAudioFocusRequest(currentRequest.mAudioFocusRequest);
228         if (result == AUDIOFOCUS_REQUEST_GRANTED) {
229             if (Slogf.isLoggable(TAG, Log.DEBUG)) {
230                 Slogf.d(TAG, "Abandoned focus for audio attributes " + audioAttributesWrapper
231                         + "and zoneId " + zoneId);
232             }
233             mAudioControlWrapper.onAudioFocusChange(audioAttributesWrapper.getAudioAttributes()
234                     .getSystemUsage(), zoneId, AUDIOFOCUS_LOSS);
235         } else {
236             Slogf.w(TAG, "Failed to abandon focus for audio attributes " + audioAttributesWrapper
237                     + " and zoneId " + zoneId);
238         }
239     }
240 
generateAudioAttributes( AudioAttributesWrapper audioAttributesWrapper, int zoneId)241     private AudioAttributes generateAudioAttributes(
242             AudioAttributesWrapper audioAttributesWrapper, int zoneId) {
243         AudioAttributes.Builder builder =
244                 new AudioAttributes.Builder(audioAttributesWrapper.getAudioAttributes());
245         Bundle bundle = new Bundle();
246         bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID, zoneId);
247         builder.addBundle(bundle);
248 
249         return builder.build();
250     }
251 
252     @GuardedBy("mLock")
generateFocusRequestLocked( AudioAttributesWrapper audioAttributesWrapper, int zoneId, int focusGain)253     private AudioFocusRequest generateFocusRequestLocked(
254             AudioAttributesWrapper audioAttributesWrapper,
255             int zoneId, int focusGain) {
256         AudioAttributes attributes = generateAudioAttributes(audioAttributesWrapper, zoneId);
257         return new AudioFocusRequest.Builder(focusGain)
258                 .setAudioAttributes(attributes)
259                 .setOnAudioFocusChangeListener((int focusChange) -> {
260                     onAudioFocusChange(attributes.getSystemUsage(), zoneId, focusChange);
261                 })
262                 .build();
263     }
264 
onAudioFocusChange(int usage, int zoneId, int focusChange)265     private void onAudioFocusChange(int usage, int zoneId, int focusChange) {
266         AudioAttributesWrapper wrapper = CarAudioContext.getAudioAttributeWrapperFromUsage(usage);
267         synchronized (mLock) {
268             HalAudioFocusRequest currentRequest =
269                     mHalFocusRequestsByZoneAndUsage.get(zoneId).get(wrapper);
270             if (currentRequest != null) {
271                 if (focusChange == AUDIOFOCUS_LOSS) {
272                     mHalFocusRequestsByZoneAndUsage.get(zoneId).remove(wrapper);
273                 } else {
274                     currentRequest.mFocusStatus = focusChange;
275                 }
276                 mAudioControlWrapper.onAudioFocusChange(usage, zoneId, focusChange);
277             }
278 
279         }
280     }
281 
282     @GuardedBy("mLock")
makeAudioFocusRequestLocked( AudioAttributesWrapper audioAttributesWrapper, int zoneId, int focusGain)283     private void makeAudioFocusRequestLocked(
284             AudioAttributesWrapper audioAttributesWrapper,
285             int zoneId, int focusGain) {
286         AudioFocusRequest audioFocusRequest =
287                 generateFocusRequestLocked(audioAttributesWrapper, zoneId, focusGain);
288 
289         int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest);
290 
291         int resultingFocusGain = focusGain;
292 
293         if (requestResult == AUDIOFOCUS_REQUEST_GRANTED) {
294             HalAudioFocusRequest halAudioFocusRequest =
295                     new HalAudioFocusRequest(audioFocusRequest, focusGain);
296             mHalFocusRequestsByZoneAndUsage.get(zoneId)
297                     .put(audioAttributesWrapper, halAudioFocusRequest);
298         } else if (requestResult == AUDIOFOCUS_REQUEST_FAILED) {
299             resultingFocusGain = AUDIOFOCUS_LOSS;
300         } else if (requestResult == AUDIOFOCUS_REQUEST_DELAYED) {
301             Slogf.w(TAG, "Delayed result for request with audio attributes "
302                     + audioAttributesWrapper + ", zoneId " + zoneId
303                     + ", and focusGain " + focusGain);
304             resultingFocusGain = AUDIOFOCUS_LOSS;
305         }
306 
307         mAudioControlWrapper.onAudioFocusChange(
308                 audioAttributesWrapper.getAudioAttributes().getSystemUsage(),
309                 zoneId, resultingFocusGain);
310     }
311 
312     private final class HalAudioFocusRequest {
313         final AudioFocusRequest mAudioFocusRequest;
314 
315         int mFocusStatus;
316 
HalAudioFocusRequest(AudioFocusRequest audioFocusRequest, int focusStatus)317         HalAudioFocusRequest(AudioFocusRequest audioFocusRequest, int focusStatus) {
318             mAudioFocusRequest = audioFocusRequest;
319             mFocusStatus = focusStatus;
320         }
321 
322         @Override
toString()323         public String toString() {
324             return new StringBuilder()
325                     .append("Request: ")
326                     .append("[Audio attributes: ")
327                     .append(mAudioFocusRequest.getAudioAttributes())
328                     .append(", Focus request: ")
329                     .append(mAudioFocusRequest.getFocusGain())
330                     .append("]")
331                     .append(", Status: ")
332                     .append(mFocusStatus)
333                     .toString();
334         }
335     }
336 }
337