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.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 
21 import android.annotation.NonNull;
22 import android.media.AudioFocusInfo;
23 import android.util.IndentingPrintWriter;
24 import android.util.SparseArray;
25 
26 import com.android.car.audio.CarZonesAudioFocus.CarFocusCallback;
27 import com.android.car.audio.hal.AudioControlWrapper;
28 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Objects;
35 
36 final class CarDucking implements CarFocusCallback {
37     private static final String TAG = CarDucking.class.getSimpleName();
38 
39     private final SparseArray<CarAudioZone> mCarAudioZones;
40     private final AudioControlWrapper mAudioControlWrapper;
41     private final Object mLock = new Object();
42 
43     @GuardedBy("mLock")
44     private final SparseArray<CarDuckingInfo> mCurrentDuckingInfo = new SparseArray<>();
45 
CarDucking(@onNull SparseArray<CarAudioZone> carAudioZones, @NonNull AudioControlWrapper audioControlWrapper)46     CarDucking(@NonNull SparseArray<CarAudioZone> carAudioZones,
47             @NonNull AudioControlWrapper audioControlWrapper) {
48         mCarAudioZones = Objects.requireNonNull(carAudioZones);
49         mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper);
50         for (int i = 0; i < carAudioZones.size(); i++) {
51             int zoneId = carAudioZones.keyAt(i);
52             mCurrentDuckingInfo.put(zoneId,
53                     new CarDuckingInfo(zoneId, new ArrayList<>(), new ArrayList<>(), new int[0]));
54         }
55     }
56 
57     @VisibleForTesting
getCurrentDuckingInfo()58     SparseArray<CarDuckingInfo> getCurrentDuckingInfo() {
59         synchronized (mLock) {
60             return mCurrentDuckingInfo;
61         }
62     }
63 
64     @Override
onFocusChange(int[] audioZoneIds, @NonNull SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId)65     public void onFocusChange(int[] audioZoneIds,
66             @NonNull SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId) {
67         synchronized (mLock) {
68             List<CarDuckingInfo> newDuckingInfos = new ArrayList<>(audioZoneIds.length);
69             for (int i = 0; i < audioZoneIds.length; i++) {
70                 int zoneId = audioZoneIds[i];
71                 List<AudioFocusInfo> focusHolders = focusHoldersByZoneId.get(zoneId);
72                 CarDuckingInfo newDuckingInfo = updateDuckingForZoneIdLocked(zoneId, focusHolders);
73                 newDuckingInfos.add(newDuckingInfo);
74             }
75             mAudioControlWrapper.onDevicesToDuckChange(newDuckingInfos);
76         }
77     }
78 
79     @GuardedBy("mLock")
updateDuckingForZoneIdLocked(int zoneId, List<AudioFocusInfo> focusHolders)80     private CarDuckingInfo updateDuckingForZoneIdLocked(int zoneId,
81             List<AudioFocusInfo> focusHolders) {
82         CarDuckingInfo oldDuckingInfo = mCurrentDuckingInfo.get(zoneId);
83         CarDuckingInfo newDuckingInfo = generateNewDuckingInfoLocked(oldDuckingInfo,
84                 focusHolders);
85         mCurrentDuckingInfo.put(zoneId, newDuckingInfo);
86         return newDuckingInfo;
87     }
88 
89     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)90     public void dump(IndentingPrintWriter writer) {
91         writer.printf("*%s*\n", TAG);
92         writer.increaseIndent();
93         synchronized (mLock) {
94             for (int i = 0; i < mCurrentDuckingInfo.size(); i++) {
95                 mCurrentDuckingInfo.valueAt(i).dump(writer);
96             }
97         }
98         writer.decreaseIndent();
99     }
100 
101     @GuardedBy("mLock")
generateNewDuckingInfoLocked(CarDuckingInfo oldDuckingInfo, List<AudioFocusInfo> focusHolders)102     private CarDuckingInfo generateNewDuckingInfoLocked(CarDuckingInfo oldDuckingInfo,
103             List<AudioFocusInfo> focusHolders) {
104         int zoneId = oldDuckingInfo.mZoneId;
105         CarAudioZone zone = mCarAudioZones.get(zoneId);
106 
107         int[] usagesHoldingFocus = CarDuckingUtils.getUsagesHoldingFocus(focusHolders);
108         List<String> addressesToDuck = CarDuckingUtils.getAddressesToDuck(usagesHoldingFocus, zone);
109         List<String> addressesToUnduck = CarDuckingUtils.getAddressesToUnduck(addressesToDuck,
110                 oldDuckingInfo.mAddressesToDuck);
111 
112         return new CarDuckingInfo(zoneId, addressesToDuck, addressesToUnduck, usagesHoldingFocus);
113     }
114 }
115