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