• 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.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING;
20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
21 
22 import android.annotation.NonNull;
23 import android.car.builtin.util.Slogf;
24 import android.hardware.automotive.audiocontrol.MutingInfo;
25 import android.util.Log;
26 import android.util.SparseArray;
27 import android.util.proto.ProtoOutputStream;
28 
29 import com.android.car.CarLog;
30 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupMutingProto;
31 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupMutingProto.CarMutingInfo;
32 import com.android.car.audio.hal.AudioControlWrapper;
33 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
34 import com.android.car.internal.util.IndentingPrintWriter;
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.util.Preconditions;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Objects;
42 
43 final class CarVolumeGroupMuting {
44 
45     private static final String TAG = CarLog.tagFor(CarVolumeGroupMuting.class);
46 
47     private final SparseArray<CarAudioZone> mCarAudioZones;
48     private final AudioControlWrapper mAudioControlWrapper;
49     private final Object mLock = new Object();
50     @GuardedBy("mLock")
51     private List<MutingInfo> mLastMutingInformation;
52     @GuardedBy("mLock")
53     private boolean mIsMutingRestricted;
54 
CarVolumeGroupMuting(@onNull SparseArray<CarAudioZone> carAudioZones, @NonNull AudioControlWrapper audioControlWrapper)55     CarVolumeGroupMuting(@NonNull SparseArray<CarAudioZone> carAudioZones,
56             @NonNull AudioControlWrapper audioControlWrapper) {
57         mCarAudioZones = Objects.requireNonNull(carAudioZones, "Car Audio Zones can not be null");
58         Preconditions.checkArgument(carAudioZones.size() != 0,
59                 "At least one car audio zone must be present.");
60         mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper,
61                 "Audio Control Wrapper can not be null");
62         requireGroupMutingSupported(audioControlWrapper);
63         mLastMutingInformation = new ArrayList<>();
64     }
65 
requireGroupMutingSupported(AudioControlWrapper audioControlWrapper)66     private static void requireGroupMutingSupported(AudioControlWrapper audioControlWrapper) {
67         if (audioControlWrapper
68                 .supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING)) {
69             return;
70         }
71         throw new IllegalStateException("audioUseCarVolumeGroupMuting is enabled but "
72                 + "IAudioControl HAL does not support volume group muting");
73     }
74 
75     /**
76      * Signal that mute has changed.
77      */
carMuteChanged()78     public void carMuteChanged() {
79         if (Slogf.isLoggable(TAG, Log.DEBUG)) {
80             Slogf.d(TAG, "carMuteChanged");
81         }
82 
83         List<MutingInfo> mutingInfo = generateMutingInfo();
84         setLastMutingInfo(mutingInfo);
85         mAudioControlWrapper.onDevicesToMuteChange(mutingInfo);
86     }
87 
setRestrictMuting(boolean isMutingRestricted)88     public void setRestrictMuting(boolean isMutingRestricted) {
89         synchronized (mLock) {
90             mIsMutingRestricted = isMutingRestricted;
91         }
92 
93         carMuteChanged();
94     }
95 
isMutingRestricted()96     private boolean isMutingRestricted() {
97         synchronized (mLock) {
98             return mIsMutingRestricted;
99         }
100     }
101 
setLastMutingInfo(List<MutingInfo> mutingInfo)102     private void setLastMutingInfo(List<MutingInfo> mutingInfo) {
103         synchronized (mLock) {
104             mLastMutingInformation = mutingInfo;
105         }
106     }
107 
108     @VisibleForTesting
getLastMutingInformation()109     List<MutingInfo> getLastMutingInformation() {
110         synchronized (mLock) {
111             return mLastMutingInformation;
112         }
113     }
114 
generateMutingInfo()115     private List<MutingInfo> generateMutingInfo() {
116         List<MutingInfo> mutingInformation = new ArrayList<>(mCarAudioZones.size());
117 
118         boolean isMutingRestricted = isMutingRestricted();
119         for (int index = 0; index < mCarAudioZones.size(); index++) {
120             mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index),
121                     isMutingRestricted));
122         }
123 
124         return mutingInformation;
125     }
126 
127     /**
128      * Dumps internal state
129      */
130     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)131     public void dump(IndentingPrintWriter writer) {
132         writer.println(TAG);
133         writer.increaseIndent();
134         synchronized (mLock) {
135             writer.printf("Is muting restricted? %b\n", mIsMutingRestricted);
136             for (int index = 0; index < mLastMutingInformation.size(); index++) {
137                 dumpCarMutingInfo(writer, mLastMutingInformation.get(index));
138             }
139         }
140         writer.decreaseIndent();
141     }
142 
143     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpCarMutingInfo(IndentingPrintWriter writer, MutingInfo info)144     private void dumpCarMutingInfo(IndentingPrintWriter writer, MutingInfo info) {
145         writer.printf("Zone ID: %d\n", info.zoneId);
146 
147         writer.println("Muted Devices:");
148         writer.increaseIndent();
149         dumpDeviceAddresses(writer, info.deviceAddressesToMute);
150         writer.decreaseIndent();
151 
152         writer.println("Un-muted Devices:");
153         writer.increaseIndent();
154         dumpDeviceAddresses(writer, info.deviceAddressesToUnmute);
155         writer.decreaseIndent();
156     }
157 
158     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpDeviceAddresses(IndentingPrintWriter writer, String[] devices)159     private static void dumpDeviceAddresses(IndentingPrintWriter writer, String[] devices) {
160         for (int index = 0; index < devices.length; index++) {
161             writer.printf("%d %s\n", index, devices[index]);
162         }
163     }
164 
165     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)166     void dumpProto(ProtoOutputStream proto) {
167         long carVolumeGroupMutingToken = proto.start(CarAudioDumpProto.CAR_VOLUME_GROUP_MUTING);
168         synchronized (mLock) {
169             proto.write(CarVolumeGroupMutingProto.IS_MUTING_RESTRICTED, mIsMutingRestricted);
170             for (int index = 0; index < mLastMutingInformation.size(); index++) {
171                 dumpProtoCarMutingInfo(mLastMutingInformation.get(index), proto);
172             }
173         }
174         proto.end(carVolumeGroupMutingToken);
175     }
176 
177     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProtoCarMutingInfo(MutingInfo info, ProtoOutputStream proto)178     private void dumpProtoCarMutingInfo(MutingInfo info, ProtoOutputStream proto) {
179         long lastMutingInfoToken = proto.start(CarVolumeGroupMutingProto.LAST_MUTING_INFORMATION);
180         proto.write(CarMutingInfo.ZONE_ID, info.zoneId);
181         dumpProtoDeviceAddresses(info.deviceAddressesToMute, CarMutingInfo.DEVICE_ADDRESSES_TO_MUTE,
182                 proto);
183         dumpProtoDeviceAddresses(info.deviceAddressesToUnmute,
184                 CarMutingInfo.DEVICE_ADDRESSES_TO_UNMUTE, proto);
185         proto.end(lastMutingInfoToken);
186     }
187 
188     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProtoDeviceAddresses(String[] devices, long fieldId, ProtoOutputStream proto)189     private static void dumpProtoDeviceAddresses(String[] devices, long fieldId,
190             ProtoOutputStream proto) {
191         for (int index = 0; index < devices.length; index++) {
192             proto.write(fieldId, devices[index]);
193         }
194     }
195 
196     @VisibleForTesting
generateMutingInfoFromZone(CarAudioZone audioZone, boolean isMutingRestricted)197     static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone,
198             boolean isMutingRestricted) {
199         MutingInfo mutingInfo = new MutingInfo();
200         mutingInfo.zoneId = audioZone.getId();
201 
202         List<String> mutedDevices = new ArrayList<>();
203         List<String> unMutedDevices = new ArrayList<>();
204         CarVolumeGroup[] groups = audioZone.getCurrentVolumeGroups();
205 
206         for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
207             CarVolumeGroup group = groups[groupIndex];
208 
209             if (group.isMuted() || (isMutingRestricted && !group.hasCriticalAudioContexts())) {
210                 mutedDevices.addAll(group.getAddresses());
211             } else {
212                 unMutedDevices.addAll(group.getAddresses());
213             }
214         }
215 
216         mutingInfo.deviceAddressesToMute = mutedDevices.toArray(new String[mutedDevices.size()]);
217         mutingInfo.deviceAddressesToUnmute =
218                 unMutedDevices.toArray(new String[unMutedDevices.size()]);
219 
220         return mutingInfo;
221     }
222 }
223