• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.audio.CarAudioService.SystemClockWrapper;
20 import static com.android.car.audio.CarAudioUtils.hasExpired;
21 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.media.AudioAttributes;
26 import android.media.AudioPlaybackConfiguration;
27 import android.util.ArrayMap;
28 import android.util.Pair;
29 import android.util.proto.ProtoOutputStream;
30 
31 import com.android.car.audio.CarAudioDumpProto.CarAudioPlaybackCallbackProto;
32 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
33 import com.android.car.internal.util.IndentingPrintWriter;
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.util.Preconditions;
36 
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Objects;
42 
43 final class ZoneAudioPlaybackCallback {
44     private final Object mLock = new Object();
45     @GuardedBy("mLock")
46     private final ArrayMap<AudioAttributes, Long> mAudioAttributesStartTime = new ArrayMap<>();
47     @GuardedBy("mLock")
48     private final ArrayMap<String, AudioPlaybackConfiguration> mLastActiveConfigs =
49             new ArrayMap<>();
50     private final CarAudioZone mCarAudioZone;
51     private final @Nullable  CarAudioPlaybackMonitor mCarAudioPlaybackMonitor;
52     private final SystemClockWrapper mClock;
53     private final int mVolumeKeyEventTimeoutMs;
54 
ZoneAudioPlaybackCallback(@onNull CarAudioZone carAudioZone, @Nullable CarAudioPlaybackMonitor carAudioPlaybackMonitor, @NonNull SystemClockWrapper clock, int volumeKeyEventTimeoutMs)55     ZoneAudioPlaybackCallback(@NonNull CarAudioZone carAudioZone,
56                               @Nullable CarAudioPlaybackMonitor carAudioPlaybackMonitor,
57                               @NonNull SystemClockWrapper clock,
58                               int volumeKeyEventTimeoutMs) {
59         mCarAudioZone = Objects.requireNonNull(carAudioZone, "Audio zone cannot be null");
60         mCarAudioPlaybackMonitor = carAudioPlaybackMonitor;
61         mClock = Objects.requireNonNull(clock, "Clock cannot be null");
62         mVolumeKeyEventTimeoutMs = Preconditions.checkArgumentNonnegative(volumeKeyEventTimeoutMs,
63                 "Volume key event timeout must be positive");
64     }
65 
onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configurations)66     public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configurations) {
67         ArrayMap<String, AudioPlaybackConfiguration> newActiveConfigs =
68                 filterNewActiveConfiguration(configurations);
69         List<Pair<AudioAttributes, Integer>> newlyActiveAudioAttributesWithUid = new ArrayList<>();
70 
71         synchronized (mLock) {
72             List<AudioPlaybackConfiguration> newlyInactiveConfigurations =
73                     getNewlyInactiveConfigurationsLocked(newActiveConfigs);
74             if (mCarAudioPlaybackMonitor != null) {
75                 newlyActiveAudioAttributesWithUid = getNewlyActiveAudioAttributes(newActiveConfigs);
76             }
77 
78             mLastActiveConfigs.clear();
79             mLastActiveConfigs.putAll(newActiveConfigs);
80 
81             startTimersForContextThatBecameInactiveLocked(newlyInactiveConfigurations);
82         }
83 
84         if (mCarAudioPlaybackMonitor != null && !newlyActiveAudioAttributesWithUid.isEmpty()) {
85             mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(
86                     newlyActiveAudioAttributesWithUid, mCarAudioZone.getId());
87         }
88     }
89 
90     /**
91      * Returns all active contexts for the primary zone
92      * @return all active audio contexts, including those that recently became inactive but are
93      * considered active due to the audio playback timeout.
94      */
getAllActiveAudioAttributes()95     public List<AudioAttributes> getAllActiveAudioAttributes() {
96         synchronized (mLock) {
97             List<AudioAttributes> activeContexts = getCurrentlyActiveAttributesLocked();
98             activeContexts
99                     .addAll(getStillActiveContextAndRemoveExpiredContextsLocked());
100             return activeContexts;
101         }
102     }
103 
104     @GuardedBy("mLock")
startTimersForContextThatBecameInactiveLocked( List<AudioPlaybackConfiguration> inactiveConfigs)105     private void startTimersForContextThatBecameInactiveLocked(
106             List<AudioPlaybackConfiguration> inactiveConfigs) {
107         List<AudioAttributes> activeAttributes = mCarAudioZone
108                 .findActiveAudioAttributesFromPlaybackConfigurations(inactiveConfigs);
109 
110         for (int index = 0; index < activeAttributes.size(); index++) {
111             mAudioAttributesStartTime.put(activeAttributes.get(index), mClock.uptimeMillis());
112         }
113     }
114 
115     @GuardedBy("mLock")
getNewlyInactiveConfigurationsLocked( Map<String, AudioPlaybackConfiguration> newActiveConfigurations)116     private List<AudioPlaybackConfiguration> getNewlyInactiveConfigurationsLocked(
117             Map<String, AudioPlaybackConfiguration> newActiveConfigurations) {
118         List<AudioPlaybackConfiguration> newlyInactiveConfigurations = new ArrayList<>();
119         for (int index = 0; index < mLastActiveConfigs.size(); index++) {
120             if (newActiveConfigurations
121                     .containsKey(mLastActiveConfigs.keyAt(index))) {
122                 continue;
123             }
124             newlyInactiveConfigurations.add(mLastActiveConfigs.valueAt(index));
125         }
126         return newlyInactiveConfigurations;
127     }
128 
129     @GuardedBy("mLock")
getNewlyActiveAudioAttributes( ArrayMap<String, AudioPlaybackConfiguration> newActiveConfigurations)130     private List<Pair<AudioAttributes, Integer>> getNewlyActiveAudioAttributes(
131             ArrayMap<String, AudioPlaybackConfiguration> newActiveConfigurations) {
132         List<AudioPlaybackConfiguration> audioPlaybackConfigurationsWithNewAttributes =
133                 new ArrayList<>();
134         for (int index = 0; index < newActiveConfigurations.size(); index++) {
135             if (mLastActiveConfigs.containsKey(newActiveConfigurations.keyAt(index))) {
136                 continue;
137             }
138             audioPlaybackConfigurationsWithNewAttributes
139                     .add(newActiveConfigurations.valueAt(index));
140         }
141         List<Pair<AudioAttributes, Integer>> attributesUidList = new ArrayList<>();
142         for (int index = 0; index < audioPlaybackConfigurationsWithNewAttributes.size(); index++) {
143             AudioPlaybackConfiguration configuration = audioPlaybackConfigurationsWithNewAttributes
144                     .get(index);
145             List<AudioAttributes> attributes = getAudioAttributesFromPlaybacks(
146                     List.of(configuration));
147             if (attributes.isEmpty()) {
148                 continue;
149             }
150             attributesUidList.add(new Pair<>(attributes.get(0), configuration.getClientUid()));
151         }
152         return attributesUidList;
153     }
154 
filterNewActiveConfiguration( List<AudioPlaybackConfiguration> configurations)155     private ArrayMap<String, AudioPlaybackConfiguration> filterNewActiveConfiguration(
156             List<AudioPlaybackConfiguration> configurations) {
157         ArrayMap<String, AudioPlaybackConfiguration> newActiveConfigs = new ArrayMap<>();
158         for (int index = 0; index < configurations.size(); index++) {
159             AudioPlaybackConfiguration configuration = configurations.get(index);
160             if (!configuration.isActive()) {
161                 continue;
162             }
163             if (mCarAudioZone
164                     .isAudioDeviceInfoValidForZone(configuration.getAudioDeviceInfo())) {
165                 newActiveConfigs.put(
166                         configuration.getAudioDeviceInfo().getAddress(), configuration);
167             }
168         }
169         return newActiveConfigs;
170     }
171 
172     @GuardedBy("mLock")
getCurrentlyActiveAttributesLocked()173     private List<AudioAttributes> getCurrentlyActiveAttributesLocked() {
174         return getAudioAttributesFromPlaybacks(mLastActiveConfigs.values());
175     }
176 
177     @GuardedBy("mLock")
getStillActiveContextAndRemoveExpiredContextsLocked()178     private List<AudioAttributes> getStillActiveContextAndRemoveExpiredContextsLocked() {
179         List<AudioAttributes> attributesToRemove = new ArrayList<>();
180         List<AudioAttributes> activeAttributes = new ArrayList<>();
181         for (int index = 0; index < mAudioAttributesStartTime.size(); index++) {
182             long startTime = mAudioAttributesStartTime.valueAt(index);
183             if (hasExpired(startTime, mClock.uptimeMillis(), mVolumeKeyEventTimeoutMs)) {
184                 attributesToRemove.add(mAudioAttributesStartTime.keyAt(index));
185                 continue;
186             }
187             activeAttributes.add(mAudioAttributesStartTime.keyAt(index));
188         }
189 
190         for (int indexToRemove = 0; indexToRemove < attributesToRemove.size(); indexToRemove++) {
191             mAudioAttributesStartTime.remove(attributesToRemove.get(indexToRemove));
192         }
193         return activeAttributes;
194     }
195 
resetStillActiveContexts()196     void resetStillActiveContexts() {
197         synchronized (mLock) {
198             mAudioAttributesStartTime.clear();
199         }
200     }
201 
getAudioAttributesFromPlaybacks( Collection<AudioPlaybackConfiguration> playbacks)202     private List<AudioAttributes> getAudioAttributesFromPlaybacks(
203             Collection<AudioPlaybackConfiguration> playbacks) {
204         return mCarAudioZone.findActiveAudioAttributesFromPlaybackConfigurations(
205                 new ArrayList<>(playbacks));
206     }
207 
208     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)209     public void dump(IndentingPrintWriter writer) {
210         writer.printf("Audio zone: %d\n", mCarAudioZone.getId());
211 
212         dumpLastActiveConfigsAndAudioAttributesStartTime(writer);
213     }
214 
215     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpLastActiveConfigsAndAudioAttributesStartTime(IndentingPrintWriter writer)216     public void dumpLastActiveConfigsAndAudioAttributesStartTime(IndentingPrintWriter writer) {
217         synchronized (mLock) {
218             writer.println("Last active configs:");
219             writer.increaseIndent();
220             for (int i = 0; i < mLastActiveConfigs.size(); i++) {
221                 writer.printf("Audio device address %s to config %s\n",
222                         mLastActiveConfigs.keyAt(i), mLastActiveConfigs.valueAt(i));
223             }
224             writer.decreaseIndent();
225             writer.println("Audio attributes start times:");
226             writer.increaseIndent();
227             for (int i = 0; i < mAudioAttributesStartTime.size(); i++) {
228                 writer.printf("Audio Attributes %s mapped to start time of %d\n",
229                         mAudioAttributesStartTime.keyAt(i), mAudioAttributesStartTime.valueAt(i));
230             }
231             writer.decreaseIndent();
232         }
233     }
234 
235     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)236     void dumpProto(ProtoOutputStream proto) {
237         long token = proto.start(CarAudioPlaybackCallbackProto.ZONE_AUDIO_PLAYBACK_CALLBACKS);
238         proto.write(CarAudioPlaybackCallbackProto.ZoneAudioPlaybackCallbackProto.ZONE_ID,
239                 mCarAudioZone.getId());
240         dumpProtoLastActiveConfigsAndAudioAttributesStartTime(proto);
241         proto.end(token);
242     }
243 
244     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProtoLastActiveConfigsAndAudioAttributesStartTime(ProtoOutputStream proto)245     public void dumpProtoLastActiveConfigsAndAudioAttributesStartTime(ProtoOutputStream proto) {
246         synchronized (mLock) {
247             for (int i = 0; i < mLastActiveConfigs.size(); i++) {
248                 long lastActiveConfigToken = proto.start(CarAudioPlaybackCallbackProto
249                         .ZoneAudioPlaybackCallbackProto.LAST_ACTIVE_CONFIGS);
250                 proto.write(CarAudioPlaybackCallbackProto.ZoneAudioPlaybackCallbackProto
251                         .AudioDeviceAddressToConfig.ADDRESS, mLastActiveConfigs.keyAt(i));
252                 proto.write(CarAudioPlaybackCallbackProto.ZoneAudioPlaybackCallbackProto
253                         .AudioDeviceAddressToConfig.CONFIG, mLastActiveConfigs.valueAt(i)
254                         .toString());
255                 proto.end(lastActiveConfigToken);
256             }
257 
258             for (int i = 0; i < mAudioAttributesStartTime.size(); i++) {
259                 long audioAttributeToStartTimeToken = proto.start(CarAudioPlaybackCallbackProto
260                         .ZoneAudioPlaybackCallbackProto.AUDIO_ATTRIBUTES_TO_START_TIMES);
261                 CarAudioContextInfo.dumpCarAudioAttributesProto(mAudioAttributesStartTime.keyAt(i),
262                         CarAudioPlaybackCallbackProto.ZoneAudioPlaybackCallbackProto
263                                 .AudioAttributesToStartTime.AUDIO_ATTRIBUTES, proto);
264                 proto.write(CarAudioPlaybackCallbackProto.ZoneAudioPlaybackCallbackProto
265                         .AudioAttributesToStartTime.START_TIME,
266                         mAudioAttributesStartTime.valueAt(i));
267                 proto.end(audioAttributeToStartTimeToken);
268             }
269         }
270     }
271 }
272