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