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 android.annotation.NonNull;
20 import android.annotation.UserIdInt;
21 import android.car.media.CarAudioManager;
22 import android.content.pm.PackageManager;
23 import android.media.AudioAttributes;
24 import android.media.AudioFocusInfo;
25 import android.media.AudioManager;
26 import android.media.audiopolicy.AudioPolicy;
27 import android.os.Bundle;
28 import android.util.Log;
29 
30 import com.android.car.CarLog;
31 import com.android.internal.util.Preconditions;
32 
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.Objects;
38 
39 /**
40  * Implements {@link AudioPolicy.AudioPolicyFocusListener}
41  *
42  * <p><b>Note:</b> Manages audio focus on a per zone basis.
43  */
44 class CarZonesAudioFocus extends AudioPolicy.AudioPolicyFocusListener {
45 
46     private final boolean mDelayedFocusEnabled;
47     private CarAudioService mCarAudioService; // Dynamically assigned just after construction
48     private AudioPolicy mAudioPolicy; // Dynamically assigned just after construction
49 
50     private final Map<Integer, CarAudioFocus> mFocusZones = new HashMap<>();
51 
CarZonesAudioFocus(@onNull AudioManager audioManager, @NonNull PackageManager packageManager, @NonNull CarAudioZone[] carAudioZones, @NonNull CarAudioSettings carAudioSettings, boolean enableDelayedAudioFocus)52     CarZonesAudioFocus(@NonNull AudioManager audioManager,
53             @NonNull PackageManager packageManager,
54             @NonNull CarAudioZone[] carAudioZones,
55             @NonNull CarAudioSettings carAudioSettings,
56             boolean enableDelayedAudioFocus) {
57         //Create the zones here, the policy will be set setOwningPolicy,
58         // which is called right after this constructor.
59         Objects.requireNonNull(audioManager);
60         Objects.requireNonNull(packageManager);
61         Objects.requireNonNull(carAudioZones);
62         Objects.requireNonNull(carAudioSettings);
63         Preconditions.checkArgument(carAudioZones.length != 0,
64                 "There must be a minimum of one audio zone");
65 
66         //Create focus for all the zones
67         for (CarAudioZone audioZone : carAudioZones) {
68             int audioZoneId = audioZone.getId();
69             if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
70                 Log.d(CarLog.TAG_AUDIO,
71                         "CarZonesAudioFocus adding new zone " + audioZoneId);
72             }
73             CarAudioFocus zoneFocusListener =
74                     new CarAudioFocus(audioManager, packageManager,
75                             new FocusInteraction(carAudioSettings), enableDelayedAudioFocus);
76             mFocusZones.put(audioZoneId, zoneFocusListener);
77         }
78         mDelayedFocusEnabled = enableDelayedAudioFocus;
79     }
80 
81 
82     /**
83      * Query the current list of focus loser in zoneId for uid
84      * @param uid uid to query for current focus losers
85      * @param zoneId zone id to query for info
86      * @return list of current focus losers for uid
87      */
getAudioFocusLosersForUid(int uid, int zoneId)88     ArrayList<AudioFocusInfo> getAudioFocusLosersForUid(int uid, int zoneId) {
89         CarAudioFocus focus = mFocusZones.get(zoneId);
90         return focus.getAudioFocusLosersForUid(uid);
91     }
92 
93     /**
94      * Query the current list of focus holders in zoneId for uid
95      * @param uid uid to query for current focus holders
96      * @param zoneId zone id to query
97      * @return list of current focus holders that for uid
98      */
getAudioFocusHoldersForUid(int uid, int zoneId)99     ArrayList<AudioFocusInfo> getAudioFocusHoldersForUid(int uid, int zoneId) {
100         CarAudioFocus focus = mFocusZones.get(zoneId);
101         return focus.getAudioFocusHoldersForUid(uid);
102     }
103 
104     /**
105      * For each entry in list, transiently lose focus
106      * @param afiList list of audio focus entries
107      * @param zoneId zone id where focus should should be lost
108      */
transientlyLoseInFocusInZone(@onNull ArrayList<AudioFocusInfo> afiList, int zoneId)109     void transientlyLoseInFocusInZone(@NonNull ArrayList<AudioFocusInfo> afiList,
110             int zoneId) {
111         CarAudioFocus focus = mFocusZones.get(zoneId);
112 
113         for (AudioFocusInfo info : afiList) {
114             focus.removeAudioFocusInfoAndTransientlyLoseFocus(info);
115         }
116     }
117 
reevaluateAndRegainAudioFocus(AudioFocusInfo afi)118     int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) {
119         CarAudioFocus focus = getFocusForAudioFocusInfo(afi);
120         return focus.reevaluateAndRegainAudioFocus(afi);
121     }
122 
123 
124     /**
125      * Sets the owning policy of the audio focus
126      *
127      * <p><b>Note:</b> This has to happen after the construction to avoid a chicken and egg
128      * problem when setting up the AudioPolicy which must depend on this object.
129 
130      * @param carAudioService owning car audio service
131      * @param parentPolicy owning parent car audio policy
132      */
setOwningPolicy(CarAudioService carAudioService, AudioPolicy parentPolicy)133     void setOwningPolicy(CarAudioService carAudioService, AudioPolicy parentPolicy) {
134         mAudioPolicy = parentPolicy;
135         mCarAudioService = carAudioService;
136 
137         for (int zoneId : mFocusZones.keySet()) {
138             mFocusZones.get(zoneId).setOwningPolicy(mAudioPolicy);
139         }
140     }
141 
142     @Override
onAudioFocusRequest(AudioFocusInfo afi, int requestResult)143     public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
144         CarAudioFocus focus = getFocusForAudioFocusInfo(afi);
145         focus.onAudioFocusRequest(afi, requestResult);
146     }
147 
148     /**
149      * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
150      * Note that we'll get this call for a focus holder that dies while in the focus stack, so
151      * we don't need to watch for death notifications directly.
152      */
153     @Override
onAudioFocusAbandon(AudioFocusInfo afi)154     public void onAudioFocusAbandon(AudioFocusInfo afi) {
155         CarAudioFocus focus = getFocusForAudioFocusInfo(afi);
156         focus.onAudioFocusAbandon(afi);
157     }
158 
getFocusForAudioFocusInfo(AudioFocusInfo afi)159     private CarAudioFocus getFocusForAudioFocusInfo(AudioFocusInfo afi) {
160         //getFocusForAudioFocusInfo defaults to returning default zoneId
161         //if uid has not been mapped, thus the value returned will be
162         //default zone focus
163         int zoneId = mCarAudioService.getZoneIdForUid(afi.getClientUid());
164 
165         // If the bundle attribute for AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID has been assigned
166         // Use zone id from that instead.
167         Bundle bundle = afi.getAttributes().getBundle();
168 
169         if (bundle != null) {
170             int bundleZoneId =
171                     bundle.getInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
172                             -1);
173             // check if the zone id is within current zones bounds
174             if (mCarAudioService.isAudioZoneIdValid(bundleZoneId)) {
175                 if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
176                     Log.d(CarLog.TAG_AUDIO,
177                             "getFocusForAudioFocusInfo valid zoneId " + bundleZoneId
178                                     + " with bundle request for client " + afi.getClientId());
179                 }
180                 zoneId = bundleZoneId;
181             } else {
182                 Log.w(CarLog.TAG_AUDIO,
183                         "getFocusForAudioFocusInfo invalid zoneId " + bundleZoneId
184                                 + " with bundle request for client " + afi.getClientId()
185                                 + ", dispatching focus request to zoneId " + zoneId);
186             }
187         }
188 
189         CarAudioFocus focus =  mFocusZones.get(zoneId);
190         return focus;
191     }
192 
193     /**
194      * dumps the current state of the CarZoneAudioFocus
195      *
196      * @param indent indent to append to each new line
197      * @param writer stream to write current state
198      */
dump(String indent, PrintWriter writer)199     void dump(String indent, PrintWriter writer) {
200         writer.printf("%s*CarZonesAudioFocus*\n", indent);
201         writer.printf("%s\tDelayed Focus Enabled: %b\n", indent, mDelayedFocusEnabled);
202         writer.printf("%s\tCar Zones Audio Focus Listeners:\n", indent);
203         Integer[] keys = mFocusZones.keySet().stream().sorted().toArray(Integer[]::new);
204         for (Integer zoneId : keys) {
205             writer.printf("%s\tZone Id: %s\n", indent, zoneId.toString());
206             mFocusZones.get(zoneId).dump(indent + "\t", writer);
207         }
208     }
209 
updateUserForZoneId(int audioZoneId, @UserIdInt int userId)210     public void updateUserForZoneId(int audioZoneId, @UserIdInt int userId) {
211         Preconditions.checkArgument(mCarAudioService.isAudioZoneIdValid(audioZoneId),
212                 "Invalid zoneId %d", audioZoneId);
213         mFocusZones.get(audioZoneId).getFocusInteraction().setUserIdForSettings(userId);
214     }
215 }
216