• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 
21 import android.annotation.NonNull;
22 import android.annotation.UserIdInt;
23 import android.car.builtin.util.Slogf;
24 import android.car.media.CarAudioManager;
25 import android.content.pm.PackageManager;
26 import android.media.AudioAttributes;
27 import android.media.AudioFocusInfo;
28 import android.media.AudioManager;
29 import android.media.audiopolicy.AudioPolicy;
30 import android.os.Bundle;
31 import android.util.SparseArray;
32 
33 import com.android.car.CarLocalServices;
34 import com.android.car.CarLog;
35 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
36 import com.android.car.internal.util.IndentingPrintWriter;
37 import com.android.car.oem.CarOemProxyService;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.util.Preconditions;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.Objects;
44 
45 /**
46  * Implements {@link AudioPolicy.AudioPolicyFocusListener}
47  *
48  * <p><b>Note:</b> Manages audio focus on a per zone basis.
49  */
50 final class CarZonesAudioFocus extends AudioPolicy.AudioPolicyFocusListener {
51     private static final String TAG = CarLog.tagFor(CarZonesAudioFocus.class);
52 
53     private final CarFocusCallback mCarFocusCallback;
54     private CarAudioService mCarAudioService; // Dynamically assigned just after construction
55     private AudioPolicy mAudioPolicy; // Dynamically assigned just after construction
56 
57     private final SparseArray<CarAudioFocus> mFocusZones;
58 
createCarZonesAudioFocus(AudioManager audioManager, PackageManager packageManager, SparseArray<CarAudioZone> carAudioZones, CarAudioSettings carAudioSettings, CarFocusCallback carFocusCallback, CarVolumeInfoWrapper carVolumeInfoWrapper)59     public static CarZonesAudioFocus createCarZonesAudioFocus(AudioManager audioManager,
60             PackageManager packageManager,
61             SparseArray<CarAudioZone> carAudioZones,
62             CarAudioSettings carAudioSettings,
63             CarFocusCallback carFocusCallback,
64             CarVolumeInfoWrapper carVolumeInfoWrapper) {
65         Objects.requireNonNull(audioManager, "Audio manager cannot be null");
66         Objects.requireNonNull(packageManager, "Package manager cannot be null");
67         Objects.requireNonNull(carAudioZones, "Car audio zones cannot be null");
68         Preconditions.checkArgument(carAudioZones.size() != 0,
69                 "There must be a minimum of one audio zone");
70         Objects.requireNonNull(carAudioSettings, "Car audio settings cannot be null");
71         Objects.requireNonNull(carVolumeInfoWrapper, "Car volume info cannot be null");
72 
73         SparseArray<CarAudioFocus> audioFocusPerZone = new SparseArray<>();
74 
75         //Create focus for all the zones
76         for (int i = 0; i < carAudioZones.size(); i++) {
77             CarAudioZone audioZone = carAudioZones.valueAt(i);
78             int audioZoneId = audioZone.getId();
79             Slogf.d(TAG, "Adding new zone %d", audioZoneId);
80 
81             CarAudioFocus zoneFocusListener = new CarAudioFocus(audioManager,
82                     packageManager, new FocusInteraction(carAudioSettings),
83                     audioZone.getCarAudioContext(), carVolumeInfoWrapper, audioZoneId);
84             audioFocusPerZone.put(audioZoneId, zoneFocusListener);
85         }
86         return new CarZonesAudioFocus(audioFocusPerZone, carFocusCallback);
87     }
88 
89     @VisibleForTesting
CarZonesAudioFocus(SparseArray<CarAudioFocus> focusZones, CarFocusCallback carFocusCallback)90     CarZonesAudioFocus(SparseArray<CarAudioFocus> focusZones, CarFocusCallback carFocusCallback) {
91         mFocusZones = focusZones;
92         mCarFocusCallback = carFocusCallback;
93     }
94 
95     /**
96      * Query the current list of focus loser in zoneId for uid
97      * @param uid uid to query for current focus losers
98      * @param zoneId zone id to query for info
99      * @return list of current focus losers for uid
100      */
getAudioFocusLosersForUid(int uid, int zoneId)101     ArrayList<AudioFocusInfo> getAudioFocusLosersForUid(int uid, int zoneId) {
102         CarAudioFocus focus = mFocusZones.get(zoneId);
103         return focus.getAudioFocusLosersForUid(uid);
104     }
105 
106     /**
107      * Query the current list of focus holders in zoneId for uid
108      * @param uid uid to query for current focus holders
109      * @param zoneId zone id to query
110      * @return list of current focus holders that for uid
111      */
getAudioFocusHoldersForUid(int uid, int zoneId)112     ArrayList<AudioFocusInfo> getAudioFocusHoldersForUid(int uid, int zoneId) {
113         CarAudioFocus focus = mFocusZones.get(zoneId);
114         return focus.getAudioFocusHoldersForUid(uid);
115     }
116 
117     /**
118      * For each entry in list, transiently lose focus
119      * @param afiList list of audio focus entries
120      * @param zoneId zone id where focus should should be lost
121      */
transientlyLoseInFocusInZone(@onNull ArrayList<AudioFocusInfo> afiList, int zoneId)122     void transientlyLoseInFocusInZone(@NonNull ArrayList<AudioFocusInfo> afiList, int zoneId) {
123         CarAudioFocus focus = mFocusZones.get(zoneId);
124 
125         for (AudioFocusInfo info : afiList) {
126             focus.removeAudioFocusInfoAndTransientlyLoseFocus(info);
127         }
128     }
129 
reevaluateAndRegainAudioFocus(AudioFocusInfo afi)130     int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) {
131         int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
132         return getCarAudioFocusForZoneId(zoneId).reevaluateAndRegainAudioFocus(afi);
133     }
134 
135     /**
136      * Sets the owning policy of the audio focus
137      *
138      * <p><b>Note:</b> This has to happen after the construction to avoid a chicken and egg
139      * problem when setting up the AudioPolicy which must depend on this object.
140 
141      * @param carAudioService owning car audio service
142      * @param parentPolicy owning parent car audio policy
143      */
setOwningPolicy(CarAudioService carAudioService, AudioPolicy parentPolicy)144     void setOwningPolicy(CarAudioService carAudioService, AudioPolicy parentPolicy) {
145         mAudioPolicy = parentPolicy;
146         mCarAudioService = carAudioService;
147 
148         for (int i = 0; i < mFocusZones.size(); i++) {
149             mFocusZones.valueAt(i).setOwningPolicy(mAudioPolicy);
150         }
151     }
152 
setRestrictFocus(boolean isFocusRestricted)153     void setRestrictFocus(boolean isFocusRestricted) {
154         int[] zoneIds = new int[mFocusZones.size()];
155         for (int i = 0; i < mFocusZones.size(); i++) {
156             zoneIds[i] = mFocusZones.keyAt(i);
157             mFocusZones.valueAt(i).setRestrictFocus(isFocusRestricted);
158         }
159         notifyFocusListeners(zoneIds);
160     }
161 
162     @Override
onAudioFocusRequest(AudioFocusInfo afi, int requestResult)163     public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
164         int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
165         getCarAudioFocusForZoneId(zoneId).onAudioFocusRequest(afi, requestResult);
166         notifyFocusListeners(new int[]{zoneId});
167     }
168 
169     /**
170      * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
171      * Note that we'll get this call for a focus holder that dies while in the focus stack, so
172      * we don't need to watch for death notifications directly.
173      */
174     @Override
onAudioFocusAbandon(AudioFocusInfo afi)175     public void onAudioFocusAbandon(AudioFocusInfo afi) {
176         int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
177         getCarAudioFocusForZoneId(zoneId).onAudioFocusAbandon(afi);
178         notifyFocusListeners(new int[]{zoneId});
179     }
180 
181     @NonNull
getCarAudioFocusForZoneId(int zoneId)182     private CarAudioFocus getCarAudioFocusForZoneId(int zoneId) {
183         return mFocusZones.get(zoneId);
184     }
185 
getAudioZoneIdForAudioFocusInfo(AudioFocusInfo afi)186     private int getAudioZoneIdForAudioFocusInfo(AudioFocusInfo afi) {
187         int zoneId = mCarAudioService.getZoneIdForUid(afi.getClientUid());
188 
189         // If the bundle attribute for AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID has been assigned
190         // Use zone id from that instead.
191         Bundle bundle = afi.getAttributes().getBundle();
192 
193         if (bundle != null) {
194             int bundleZoneId =
195                     bundle.getInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
196                             -1);
197             // check if the zone id is within current zones bounds
198             if (mCarAudioService.isAudioZoneIdValid(bundleZoneId)) {
199                 Slogf.d(TAG, "getFocusForAudioFocusInfo valid zoneId %d with bundle request for"
200                         + " client %s", bundleZoneId, afi.getClientId());
201                 zoneId = bundleZoneId;
202             } else {
203                 Slogf.w(TAG, "getFocusForAudioFocusInfo invalid zoneId %d with bundle request for "
204                                 + "client %s, dispatching focus request to zoneId %d", bundleZoneId,
205                         afi.getClientId(), zoneId);
206             }
207         }
208 
209         return zoneId;
210     }
211 
notifyFocusListeners(int[] zoneIds)212     private void notifyFocusListeners(int[] zoneIds) {
213         SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId = new SparseArray<>(zoneIds.length);
214         for (int i = 0; i < zoneIds.length; i++) {
215             int zoneId = zoneIds[i];
216             focusHoldersByZoneId.put(zoneId, mFocusZones.get(zoneId).getAudioFocusHolders());
217             sendFocusChangeToOemService(getCarAudioFocusForZoneId(zoneId), zoneId);
218         }
219 
220         if (mCarFocusCallback == null) {
221             return;
222         }
223         mCarFocusCallback.onFocusChange(zoneIds, focusHoldersByZoneId);
224     }
225 
sendFocusChangeToOemService(CarAudioFocus carAudioFocus, int zoneId)226     private void sendFocusChangeToOemService(CarAudioFocus carAudioFocus, int zoneId) {
227         CarOemProxyService proxy = CarLocalServices.getService(CarOemProxyService.class);
228         if (proxy == null || !proxy.isOemServiceEnabled() || !proxy.isOemServiceReady()
229                 || proxy.getCarOemAudioFocusService() == null) {
230             return;
231         }
232 
233         proxy.getCarOemAudioFocusService().notifyAudioFocusChange(
234                 carAudioFocus.getAudioFocusHolders(), carAudioFocus.getAudioFocusLosers(), zoneId);
235     }
236 
237     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)238     void dump(IndentingPrintWriter writer) {
239         writer.println("*CarZonesAudioFocus*");
240         writer.increaseIndent();
241         writer.printf("Has Focus Callback: %b\n", mCarFocusCallback != null);
242         writer.println("Car Zones Audio Focus Listeners:");
243         writer.increaseIndent();
244         for (int i = 0; i < mFocusZones.size(); i++) {
245             writer.printf("Zone Id: %d\n", mFocusZones.keyAt(i));
246             writer.increaseIndent();
247             mFocusZones.valueAt(i).dump(writer);
248             writer.decreaseIndent();
249         }
250         writer.decreaseIndent();
251         writer.decreaseIndent();
252     }
253 
updateUserForZoneId(int audioZoneId, @UserIdInt int userId)254     public void updateUserForZoneId(int audioZoneId, @UserIdInt int userId) {
255         Preconditions.checkArgument(mCarAudioService.isAudioZoneIdValid(audioZoneId),
256                 "Invalid zoneId %d", audioZoneId);
257         mFocusZones.get(audioZoneId).getFocusInteraction().setUserIdForSettings(userId);
258     }
259 
260     /**
261      * Callback to get notified of the active focus holders after any focus request or abandon  call
262      */
263     public interface CarFocusCallback {
264         /**
265          * Called after a focus request or abandon call is handled.
266          *
267          * @param audioZoneIds IDs of the zones where the changes took place
268          * @param focusHoldersByZoneId sparse array by zone ID, where each value is a list of
269          * {@link AudioFocusInfo}s holding focus in specified audio zone
270          */
onFocusChange(int[] audioZoneIds, @NonNull SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId)271         void onFocusChange(int[] audioZoneIds,
272                 @NonNull SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId);
273     }
274 }
275