• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
20 import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
21 
22 import static com.android.car.audio.CarActivationVolumeConfig.ActivationVolumeInvocationType;
23 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
24 
25 import android.annotation.NonNull;
26 import android.car.builtin.util.Slogf;
27 import android.car.media.CarAudioManager;
28 import android.media.AudioAttributes;
29 import android.os.Binder;
30 import android.telephony.TelephonyCallback;
31 import android.telephony.TelephonyManager;
32 import android.util.Pair;
33 import android.util.SparseArray;
34 import android.util.SparseIntArray;
35 
36 import com.android.car.CarLog;
37 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
38 import com.android.internal.annotations.GuardedBy;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Objects;
43 import java.util.concurrent.Executor;
44 import java.util.concurrent.Executors;
45 
46 public final class CarAudioPlaybackMonitor {
47 
48     private static final int INVALID_UID = -1;
49 
50     private final Object mLock = new Object();
51     private final CarAudioService mCarAudioService;
52     private final SparseArray<CarAudioZone> mCarAudioZones;
53     private final TelephonyManager mTelephonyManager;
54     private final Executor mExecutor;
55     private final CarCallStateListener mCallStateCallback;
56     @GuardedBy("mLock")
57     private final SparseArray<SparseIntArray> mZoneIdGroupIdToUidMap;
58 
CarAudioPlaybackMonitor(@onNull CarAudioService carAudioService, @NonNull SparseArray<CarAudioZone> carAudioZones, @NonNull TelephonyManager telephonyManager)59     CarAudioPlaybackMonitor(@NonNull CarAudioService carAudioService,
60                             @NonNull SparseArray<CarAudioZone> carAudioZones,
61                             @NonNull TelephonyManager telephonyManager) {
62         mCarAudioService = Objects.requireNonNull(carAudioService,
63                 "Car audio service can not be null");
64         mCarAudioZones = Objects.requireNonNull(carAudioZones,
65                 "Car audio zones can not be null");
66         mZoneIdGroupIdToUidMap = new SparseArray<>();
67         mTelephonyManager = Objects.requireNonNull(telephonyManager,
68                 "Telephony manager can not be null");
69         mExecutor = Executors.newSingleThreadExecutor();
70         mCallStateCallback = new CarCallStateListener();
71         mTelephonyManager.registerTelephonyCallback(mExecutor, mCallStateCallback);
72     }
73 
74     /**
75      * Reset car audio playback monitor
76      *
77      * <p>Once reset, car audio playback monitor cannot be reused since the listener to
78      * {@link TelephonyManager} has been unregistered.
79      */
reset()80     void reset() {
81         mTelephonyManager.unregisterTelephonyCallback(mCallStateCallback);
82     }
83 
resetActivationTypesForZone(int zoneId)84     void resetActivationTypesForZone(int zoneId) {
85         synchronized (mLock) {
86             mZoneIdGroupIdToUidMap.remove(zoneId);
87         }
88     }
89 
90     /**
91      * Informs {@link CarAudioService} that newly active playbacks are received and min/max
92      * activation volume should be applied if needed.
93      *
94      * @param newActivePlaybackAttributesWithUid List of pairs of {@link AudioAttributes} and
95      *                                           client Uid of the newly active
96      *                                          {@link android.media.AudioPlaybackConfiguration}s
97      * @param zoneId Zone Id of thr newly active playbacks
98      */
onActiveAudioPlaybackAttributesAdded( List<Pair<AudioAttributes, Integer>> newActivePlaybackAttributesWithUid, int zoneId)99     public void onActiveAudioPlaybackAttributesAdded(
100             List<Pair<AudioAttributes, Integer>> newActivePlaybackAttributesWithUid, int zoneId) {
101         if (newActivePlaybackAttributesWithUid == null
102                 || newActivePlaybackAttributesWithUid.isEmpty()) {
103             return;
104         }
105         CarAudioZoneConfig currentZoneConfig = mCarAudioZones.get(zoneId)
106                 .getCurrentCarAudioZoneConfig();
107         List<ActivationInfo> newActivationVolumeGroupsWithType =
108                 new ArrayList<>();
109         for (int index = 0; index < newActivePlaybackAttributesWithUid.size(); index++) {
110             Pair<AudioAttributes, Integer> newActivePlaybackAttributesWithUidEntry =
111                     newActivePlaybackAttributesWithUid.get(index);
112             ActivationInfo activationVolumeGroupWithInvocationType =
113                     getActivationInfo(
114                             newActivePlaybackAttributesWithUidEntry, currentZoneConfig);
115             if (activationVolumeGroupWithInvocationType == null) {
116                 continue;
117             }
118             newActivationVolumeGroupsWithType.add(activationVolumeGroupWithInvocationType);
119         }
120         if (newActivationVolumeGroupsWithType.isEmpty()) {
121             return;
122         }
123         mCarAudioService.handleActivationVolumeWithActivationInfos(
124                 newActivationVolumeGroupsWithType, zoneId, currentZoneConfig.getZoneConfigId());
125     }
126 
getActivationInfo( Pair<AudioAttributes, Integer> attributesIntegerPair, CarAudioZoneConfig currentZoneConfig)127     private ActivationInfo getActivationInfo(
128             Pair<AudioAttributes, Integer> attributesIntegerPair,
129             CarAudioZoneConfig currentZoneConfig) {
130         int zoneId = currentZoneConfig.getZoneId();
131         CarVolumeGroup volumeGroup = currentZoneConfig.getVolumeGroupForAudioAttributes(
132                 attributesIntegerPair.first);
133         if (volumeGroup == null) {
134             Slogf.w(CarLog.TAG_AUDIO, "Audio attributes %s is not found in zone %d config %d",
135                     attributesIntegerPair.first, zoneId, currentZoneConfig.getZoneConfigId());
136             return null;
137         }
138         int groupId = volumeGroup.getId();
139         int uid = attributesIntegerPair.second;
140         int activationVolumeInvocationType;
141         synchronized (mLock) {
142             // For a playback with a uid that does not exist for a given zone id and volume group
143             // id, it is the playback after boot or zone configuration change for activation volume.
144             if (!mZoneIdGroupIdToUidMap.contains(zoneId)) {
145                 mZoneIdGroupIdToUidMap.put(zoneId, new SparseIntArray());
146                 activationVolumeInvocationType =
147                         CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
148             } else if (mZoneIdGroupIdToUidMap.get(zoneId).get(groupId, INVALID_UID)
149                     == INVALID_UID) {
150                 activationVolumeInvocationType =
151                         CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
152             } else {
153                 // For a playback with a uid existing for the given zone id and volume group id,
154                 // whether it changes the source from the previous playback is determines by
155                 // whether it has the same uid.
156                 int prevUid = mZoneIdGroupIdToUidMap.get(zoneId).get(groupId);
157                 activationVolumeInvocationType = uid == prevUid
158                         ? CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED
159                         : CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED;
160             }
161             mZoneIdGroupIdToUidMap.get(zoneId).put(groupId, uid);
162         }
163         return new ActivationInfo(volumeGroup.getId(),
164                 activationVolumeInvocationType);
165     }
166 
167     static final class ActivationInfo {
168         final int mGroupId;
169         @ActivationVolumeInvocationType
170         final int mInvocationType;
171 
ActivationInfo(int groupId, @ActivationVolumeInvocationType int type)172         ActivationInfo(int groupId, @ActivationVolumeInvocationType int type) {
173             mGroupId = groupId;
174             mInvocationType = type;
175         }
176 
177         @Override
equals(Object o)178         public boolean equals(Object o) {
179             if (this == o) {
180                 return true;
181             }
182             if (!(o instanceof ActivationInfo)) {
183                 return false;
184             }
185             ActivationInfo other = (ActivationInfo) o;
186             return other.mGroupId == mGroupId && other.mInvocationType == mInvocationType;
187         }
188 
189         @Override
hashCode()190         public int hashCode() {
191             return Objects.hash(mGroupId, mInvocationType);
192         }
193 
194         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
195         @Override
toString()196         public String toString() {
197             return "ActivationInfo { volume group id: " + mGroupId + ", invocation type = "
198                     + mInvocationType + "}";
199         }
200     }
201 
202     private class CarCallStateListener extends TelephonyCallback
203             implements TelephonyCallback.CallStateListener {
204         @Override
onCallStateChanged(int state)205         public void onCallStateChanged(int state) {
206             int usage;
207             switch (state) {
208                 case TelephonyManager.CALL_STATE_RINGING:
209                     usage = USAGE_NOTIFICATION_RINGTONE;
210                     break;
211                 case TelephonyManager.CALL_STATE_OFFHOOK:
212                     usage = USAGE_VOICE_COMMUNICATION;
213                     break;
214                 default:
215                     return;
216             }
217             AudioAttributes audioAttributes = CarAudioContext.getAudioAttributeFromUsage(usage);
218             // TODO(331680279): get actual Uid from telephony or define a fake Uid for telephony
219             // playback only.
220             onActiveAudioPlaybackAttributesAdded(List.of(new Pair<>(audioAttributes,
221                             Binder.getCallingUid())), CarAudioManager.PRIMARY_AUDIO_ZONE);
222         }
223     }
224 }
225