1 /*
2  * Copyright (C) 2022 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.server.audio;
18 
19 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT;
20 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
21 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID;
22 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH;
23 import static android.media.AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID;
24 import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
25 import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D;
26 import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
27 import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
28 import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
29 import static android.media.audio.Flags.automaticBtDeviceType;
30 
31 import android.annotation.IntDef;
32 import android.annotation.NonNull;
33 import android.media.AudioAttributes;
34 import android.media.AudioDeviceAttributes;
35 import android.media.AudioDeviceInfo;
36 import android.media.AudioManager.AudioDeviceCategory;
37 import android.media.AudioPlaybackConfiguration;
38 import android.media.AudioSystem;
39 import android.media.ILoudnessCodecUpdatesDispatcher;
40 import android.media.LoudnessCodecInfo;
41 import android.media.permission.ClearCallingIdentityContext;
42 import android.media.permission.SafeCloseable;
43 import android.os.Binder;
44 import android.os.PersistableBundle;
45 import android.os.RemoteCallbackList;
46 import android.os.RemoteException;
47 import android.os.SystemProperties;
48 import android.util.Log;
49 import android.util.SparseIntArray;
50 
51 import com.android.internal.annotations.GuardedBy;
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.server.audio.AudioServiceEvents.LoudnessEvent;
54 import com.android.server.utils.EventLogger;
55 
56 import java.io.PrintWriter;
57 import java.lang.annotation.Retention;
58 import java.lang.annotation.RetentionPolicy;
59 import java.util.ArrayList;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Objects;
65 import java.util.Optional;
66 import java.util.Set;
67 import java.util.stream.Collectors;
68 
69 /**
70  * Class to handle the updates in loudness parameters and responsible to generate parameters that
71  * can be set directly on a MediaCodec.
72  */
73 public class LoudnessCodecHelper {
74     private static final String TAG = "AS.LoudnessCodecHelper";
75 
76     private static final boolean DEBUG = false;
77 
78     /**
79      * Property containing a string to set for a custom built in speaker SPL range as defined by
80      * CTA2075. The options that can be set are:
81      * - "small": for max SPL with test signal < 75 dB,
82      * - "medium": for max SPL with test signal between 70 and 90 dB,
83      * - "large": for max SPL with test signal > 85 dB.
84      */
85     private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE =
86             "audio.loudness.builtin-speaker-spl-range-size";
87 
88     @VisibleForTesting
89     static final int SPL_RANGE_UNKNOWN = 0;
90     @VisibleForTesting
91     static final int SPL_RANGE_SMALL = 1;
92     @VisibleForTesting
93     static final int SPL_RANGE_MEDIUM = 2;
94     @VisibleForTesting
95     static final int SPL_RANGE_LARGE = 3;
96 
97     /** The possible transducer SPL ranges as defined in CTA2075 */
98     @IntDef({
99             SPL_RANGE_UNKNOWN,
100             SPL_RANGE_SMALL,
101             SPL_RANGE_MEDIUM,
102             SPL_RANGE_LARGE
103     })
104     @Retention(RetentionPolicy.SOURCE)
105     public @interface DeviceSplRange {
106     }
107 
108     private static final class LoudnessRemoteCallbackList extends
109             RemoteCallbackList<ILoudnessCodecUpdatesDispatcher> {
110         private final LoudnessCodecHelper mLoudnessCodecHelper;
111 
LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper)112         LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper) {
113             mLoudnessCodecHelper = loudnessCodecHelper;
114         }
115 
116         @Override
onCallbackDied(ILoudnessCodecUpdatesDispatcher callback, Object cookie)117         public void onCallbackDied(ILoudnessCodecUpdatesDispatcher callback, Object cookie) {
118             Integer pid = null;
119             if (cookie instanceof Integer) {
120                 pid = (Integer) cookie;
121             }
122             if (pid != null) {
123                 if (DEBUG) {
124                     Log.d(TAG, "Client with pid " + pid + " died, removing from receiving updates");
125                 }
126                 sLogger.enqueue(LoudnessEvent.getClientDied(pid));
127                 mLoudnessCodecHelper.onClientPidDied(pid);
128             }
129             super.onCallbackDied(callback, cookie);
130         }
131     }
132 
133     private static final EventLogger sLogger = new EventLogger(
134             AudioService.LOG_NB_EVENTS_LOUDNESS_CODEC, "Loudness updates");
135 
136     private final LoudnessRemoteCallbackList mLoudnessUpdateDispatchers =
137             new LoudnessRemoteCallbackList(this);
138 
139     private final Object mLock = new Object();
140 
141     /** Contains for each started track id the known started piids. */
142     @GuardedBy("mLock")
143     private final HashMap<LoudnessTrackId, Set<Integer>> mStartedConfigPiids =
144             new HashMap<>();
145 
146     /** Contains for each LoudnessTrackId a set of started coudec infos. */
147     @GuardedBy("mLock")
148     private final HashMap<LoudnessTrackId, Set<LoudnessCodecInfo>> mStartedConfigInfo =
149             new HashMap<>();
150 
151     /** Contains the current device id assignment for each piid. */
152     @GuardedBy("mLock")
153     private final SparseIntArray mPiidToDeviceIdCache = new SparseIntArray();
154 
155     /** Maps each piid to the owner process of the player. */
156     @GuardedBy("mLock")
157     private final SparseIntArray mPiidToPidCache = new SparseIntArray();
158 
159     private final AudioService mAudioService;
160 
161     /** Contains the properties necessary to compute the codec loudness related parameters. */
162     @VisibleForTesting
163     static final class LoudnessCodecInputProperties {
164         private final int mMetadataType;
165 
166         private final boolean mIsDownmixing;
167 
168         @DeviceSplRange
169         private final int mDeviceSplRange;
170 
171         static final class Builder {
172             private int mMetadataType;
173 
174             private boolean mIsDownmixing;
175 
176             @DeviceSplRange
177             private int mDeviceSplRange;
178 
setMetadataType(int metadataType)179             Builder setMetadataType(int metadataType) {
180                 mMetadataType = metadataType;
181                 return this;
182             }
183 
setIsDownmixing(boolean isDownmixing)184             Builder setIsDownmixing(boolean isDownmixing) {
185                 mIsDownmixing = isDownmixing;
186                 return this;
187             }
188 
setDeviceSplRange(@eviceSplRange int deviceSplRange)189             Builder setDeviceSplRange(@DeviceSplRange int deviceSplRange) {
190                 mDeviceSplRange = deviceSplRange;
191                 return this;
192             }
193 
build()194             LoudnessCodecInputProperties build() {
195                 return new LoudnessCodecInputProperties(mMetadataType,
196                         mIsDownmixing, mDeviceSplRange);
197             }
198         }
199 
LoudnessCodecInputProperties(int metadataType, boolean isDownmixing, @DeviceSplRange int deviceSplRange)200         private LoudnessCodecInputProperties(int metadataType,
201                 boolean isDownmixing,
202                 @DeviceSplRange int deviceSplRange) {
203             mMetadataType = metadataType;
204             mIsDownmixing = isDownmixing;
205             mDeviceSplRange = deviceSplRange;
206         }
207 
208         @Override
equals(Object obj)209         public boolean equals(Object obj) {
210             if (this == obj) {
211                 return true;
212             }
213             if (obj == null) {
214                 return false;
215             }
216             // type check and cast
217             if (getClass() != obj.getClass()) {
218                 return false;
219             }
220             final LoudnessCodecInputProperties lcip = (LoudnessCodecInputProperties) obj;
221             return mMetadataType == lcip.mMetadataType
222                     && mIsDownmixing == lcip.mIsDownmixing
223                     && mDeviceSplRange == lcip.mDeviceSplRange;
224         }
225 
226         @Override
hashCode()227         public int hashCode() {
228             return Objects.hash(mMetadataType, mIsDownmixing, mDeviceSplRange);
229         }
230 
231         @Override
toString()232         public String toString() {
233             return "Loudness properties:"
234                     + " device SPL range: " + splRangeToString(mDeviceSplRange)
235                     + " down-mixing: " + mIsDownmixing
236                     + " metadata type: " + mMetadataType;
237         }
238 
createLoudnessParameters()239         PersistableBundle createLoudnessParameters() {
240             PersistableBundle loudnessParams = new PersistableBundle();
241 
242             switch (mDeviceSplRange) {
243                 case SPL_RANGE_LARGE:
244                     // corresponds to -31dB attenuation
245                     loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 124);
246                     if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) {
247                         loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, 0);
248                     } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) {
249                         // general compression
250                         loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6);
251                     }
252                     break;
253                 case SPL_RANGE_MEDIUM:
254                     // corresponds to -24dB attenuation
255                     loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 96);
256                     if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) {
257                         loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, mIsDownmixing ? 1 : 0);
258                     } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) {
259                         // general compression
260                         loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6);
261                     }
262                     break;
263                 case SPL_RANGE_SMALL:
264                     // corresponds to -16dB attenuation
265                     loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 64);
266                     if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) {
267                         loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, 1);
268                     } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) {
269                         // limited playback range compression
270                         loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 3);
271                     }
272                     break;
273                 default:
274                     // corresponds to -24dB attenuation
275                     loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 96);
276                     if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) {
277                         loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, mIsDownmixing ? 1 : 0);
278                     } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) {
279                         // general compression
280                         loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6);
281                     }
282                     break;
283             }
284 
285             return loudnessParams;
286         }
287     }
288 
289     /**
290      * Contains the properties necessary to identify the tracks that are receiving annotated
291      * loudness data.
292      **/
293     @VisibleForTesting
294     static final class LoudnessTrackId {
295         private final int mSessionId;
296 
297         private final int mPid;
298 
LoudnessTrackId(int sessionId, int pid)299         private LoudnessTrackId(int sessionId, int pid) {
300             mSessionId = sessionId;
301             mPid = pid;
302         }
303 
304         @Override
equals(Object obj)305         public boolean equals(Object obj) {
306             if (this == obj) {
307                 return true;
308             }
309             if (obj == null) {
310                 return false;
311             }
312             // type check and cast
313             if (getClass() != obj.getClass()) {
314                 return false;
315             }
316             final LoudnessTrackId lti = (LoudnessTrackId) obj;
317             return mSessionId == lti.mSessionId && mPid == lti.mPid;
318         }
319 
320         @Override
hashCode()321         public int hashCode() {
322             return Objects.hash(mSessionId, mPid);
323         }
324 
325         @Override
toString()326         public String toString() {
327             return "Loudness track id:"
328                     + " session ID: " + mSessionId
329                     + " pid: " + mPid;
330         }
331     }
332 
333     @GuardedBy("mLock")
334     private final HashMap<LoudnessCodecInputProperties, PersistableBundle> mCachedProperties =
335             new HashMap<>();
336 
LoudnessCodecHelper(@onNull AudioService audioService)337     LoudnessCodecHelper(@NonNull AudioService audioService) {
338         mAudioService = Objects.requireNonNull(audioService);
339     }
340 
registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher)341     void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) {
342         mLoudnessUpdateDispatchers.register(dispatcher, Binder.getCallingPid());
343     }
344 
unregisterLoudnessCodecUpdatesDispatcher( ILoudnessCodecUpdatesDispatcher dispatcher)345     void unregisterLoudnessCodecUpdatesDispatcher(
346             ILoudnessCodecUpdatesDispatcher dispatcher) {
347         mLoudnessUpdateDispatchers.unregister(dispatcher);
348     }
349 
startLoudnessCodecUpdates(int sessionId)350     void startLoudnessCodecUpdates(int sessionId) {
351         int pid = Binder.getCallingPid();
352         if (DEBUG) {
353             Log.d(TAG,
354                     "startLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid);
355         }
356 
357         final LoudnessTrackId newConfig = new LoudnessTrackId(sessionId, pid);
358         HashSet<Integer> newPiids;
359         synchronized (mLock) {
360             if (mStartedConfigInfo.containsKey(newConfig)) {
361                 Log.w(TAG, "Already started loudness updates for config: " + newConfig);
362                 return;
363             }
364 
365             mStartedConfigInfo.put(newConfig, new HashSet<>());
366             newPiids = new HashSet<>();
367             mStartedConfigPiids.put(newConfig, newPiids);
368         }
369 
370         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
371             mAudioService.getActivePlaybackConfigurations().stream().filter(
372                     conf -> conf.getSessionId() == sessionId
373                             && conf.getClientPid() == pid).forEach(apc -> {
374                                 int piid = apc.getPlayerInterfaceId();
375                                 synchronized (mLock) {
376                                     newPiids.add(piid);
377                                     mPiidToPidCache.put(piid, pid);
378                                     sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid));
379                                 }
380                             });
381         }
382     }
383 
stopLoudnessCodecUpdates(int sessionId)384     void stopLoudnessCodecUpdates(int sessionId) {
385         int pid = Binder.getCallingPid();
386         if (DEBUG) {
387             Log.d(TAG,
388                     "stopLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid);
389         }
390 
391         final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
392         synchronized (mLock) {
393             if (!mStartedConfigInfo.containsKey(config)) {
394                 Log.w(TAG, "Loudness updates are already stopped config: " + config);
395                 return;
396             }
397 
398             final Set<Integer> startedPiidSet = mStartedConfigPiids.get(config);
399             if (startedPiidSet == null) {
400                 Log.e(TAG, "Loudness updates are already stopped config: " + config);
401                 return;
402             }
403             for (Integer piid : startedPiidSet) {
404                 sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1)));
405                 mPiidToDeviceIdCache.delete(piid);
406                 mPiidToPidCache.delete(piid);
407             }
408             mStartedConfigPiids.remove(config);
409             mStartedConfigInfo.remove(config);
410         }
411     }
412 
addLoudnessCodecInfo(int sessionId, int mediaCodecHash, LoudnessCodecInfo info)413     void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
414             LoudnessCodecInfo info) {
415         int pid = Binder.getCallingPid();
416         if (DEBUG) {
417             Log.d(TAG, "addLoudnessCodecInfo: sessionId " + sessionId
418                     + " mcHash " + mediaCodecHash + " info " + info + " pid " + pid);
419         }
420 
421         final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
422         Set<LoudnessCodecInfo> infoSet;
423         Set<Integer> piids;
424         synchronized (mLock) {
425             if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey(
426                     config)) {
427                 Log.w(TAG, "Cannot add new loudness info for stopped config " + config);
428                 return;
429             }
430 
431             piids = mStartedConfigPiids.get(config);
432             infoSet = mStartedConfigInfo.get(config);
433             infoSet.add(info);
434         }
435 
436         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
437             final PersistableBundle updateBundle = new PersistableBundle();
438             Optional<AudioPlaybackConfiguration> apc =
439                     mAudioService.getActivePlaybackConfigurations().stream().filter(
440                             conf -> conf.getSessionId() == sessionId
441                                     && conf.getClientPid() == pid).findFirst();
442             if (apc.isEmpty()) {
443                 if (DEBUG) {
444                     Log.d(TAG,
445                             "No APCs found when adding loudness codec info. Using AudioAttributes"
446                                     + " routing for initial update");
447                 }
448                 updateBundle.putPersistableBundle(Integer.toString(mediaCodecHash),
449                         getLoudnessParams(info));
450             } else {
451                 final AudioDeviceInfo deviceInfo = apc.get().getAudioDeviceInfo();
452                 if (deviceInfo != null) {
453                     synchronized (mLock) {
454                         // found a piid that matches the configuration
455                         piids.add(apc.get().getPlayerInterfaceId());
456 
457                         updateBundle.putPersistableBundle(
458                                 Integer.toString(mediaCodecHash),
459                                 getCodecBundle_l(deviceInfo.getInternalType(),
460                                         deviceInfo.getAddress(), info));
461                     }
462                 }
463             }
464             if (!updateBundle.isDefinitelyEmpty()) {
465                 dispatchNewLoudnessParameters(sessionId, updateBundle);
466             }
467         }
468     }
469 
removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo)470     void removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo) {
471         if (DEBUG) {
472             Log.d(TAG, "removeLoudnessCodecInfo: session ID" + sessionId + " info " + codecInfo);
473         }
474 
475         int pid = Binder.getCallingPid();
476         final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
477         synchronized (mLock) {
478             if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey(
479                     config)) {
480                 Log.w(TAG, "Cannot remove loudness info for stopped config " + config);
481                 return;
482             }
483             final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config);
484             if (!codecInfos.remove(codecInfo)) {
485                 Log.w(TAG, "Could not find to remove codecInfo " + codecInfo);
486             }
487         }
488     }
489 
getLoudnessParams(LoudnessCodecInfo codecInfo)490     PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) {
491         if (DEBUG) {
492             Log.d(TAG, "getLoudnessParams: codecInfo " + codecInfo);
493         }
494         final ArrayList<AudioDeviceAttributes> devicesForAttributes =
495                 mAudioService.getDevicesForAttributesInt(new AudioAttributes.Builder()
496                         .setUsage(AudioAttributes.USAGE_MEDIA)
497                         .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
498                         .build(), /*forVolume=*/false);
499         if (!devicesForAttributes.isEmpty()) {
500             final AudioDeviceAttributes audioDeviceAttribute = devicesForAttributes.get(0);
501             synchronized (mLock) {
502                 return getCodecBundle_l(audioDeviceAttribute.getInternalType(),
503                         audioDeviceAttribute.getAddress(), codecInfo);
504             }
505         }
506 
507         // return empty Bundle
508         return new PersistableBundle();
509     }
510 
511     /** Method to be called whenever there is a changed in the active playback configurations. */
updateCodecParameters(List<AudioPlaybackConfiguration> configs)512     void updateCodecParameters(List<AudioPlaybackConfiguration> configs) {
513         if (DEBUG) {
514             Log.d(TAG, "updateCodecParameters: configs " + configs);
515         }
516 
517         List<AudioPlaybackConfiguration> updateApcList = new ArrayList<>();
518         synchronized (mLock) {
519             for (final AudioPlaybackConfiguration apc : configs) {
520                 int piid = apc.getPlayerInterfaceId();
521                 int cachedDeviceId = mPiidToDeviceIdCache.get(piid, PLAYER_DEVICEID_INVALID);
522                 AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
523                 if (deviceInfo == null) {
524                     if (DEBUG) {
525                         Log.d(TAG, "No device info for piid: " + piid);
526                     }
527                     if (cachedDeviceId != PLAYER_DEVICEID_INVALID) {
528                         mPiidToDeviceIdCache.delete(piid);
529                         if (DEBUG) {
530                             Log.d(TAG, "Remove cached device id for piid: " + piid);
531                         }
532                     }
533                     continue;
534                 }
535                 if (cachedDeviceId == deviceInfo.getId()) {
536                     // deviceId did not change
537                     if (DEBUG) {
538                         Log.d(TAG, "DeviceId " + cachedDeviceId + " for piid: " + piid
539                                 + " did not change");
540                     }
541                     continue;
542                 }
543                 mPiidToDeviceIdCache.put(piid, deviceInfo.getId());
544                 final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(),
545                         apc.getClientPid());
546                 if (mStartedConfigInfo.containsKey(config) && mStartedConfigPiids.containsKey(
547                         config)) {
548                     if (DEBUG) {
549                         Log.d(TAG, "Updating config: " + config + " with APC " + apc);
550                     }
551                     updateApcList.add(apc);
552                     // update the started piid set
553                     mStartedConfigPiids.get(config).add(piid);
554                 }
555             }
556         }
557 
558         updateApcList.forEach(this::updateCodecParametersForConfiguration);
559     }
560 
561     /** Updates and dispatches the new loudness parameters for all its registered codecs. */
dump(PrintWriter pw)562     void dump(PrintWriter pw) {
563         // Registered clients
564         pw.println("\nRegistered clients:\n");
565         synchronized (mLock) {
566             for (Map.Entry<LoudnessTrackId, Set<Integer>> entry : mStartedConfigPiids.entrySet()) {
567                 for (Integer piid : entry.getValue()) {
568                     int pid = mPiidToPidCache.get(piid, -1);
569                     final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(
570                             entry.getKey());
571                     if (codecInfos != null) {
572                         pw.println(
573                                 String.format("Player piid %d pid %d active codec types %s\n", piid,
574                                         pid, codecInfos.stream().map(Object::toString).collect(
575                                                 Collectors.joining(", "))));
576                     }
577                 }
578             }
579             pw.println();
580         }
581 
582         sLogger.dump(pw);
583         pw.println();
584     }
585 
onClientPidDied(int pid)586     private void onClientPidDied(int pid) {
587         synchronized (mLock) {
588             for (int i = 0; i < mPiidToPidCache.size(); ++i) {
589                 int piid = mPiidToPidCache.keyAt(i);
590                 if (mPiidToPidCache.get(piid) == pid) {
591                     if (DEBUG) {
592                         Log.d(TAG, "Removing piid  " + piid);
593                     }
594                     mPiidToDeviceIdCache.delete(piid);
595                 }
596             }
597 
598             mStartedConfigPiids.entrySet().removeIf(entry -> entry.getKey().mPid == pid);
599             mStartedConfigInfo.entrySet().removeIf(entry -> entry.getKey().mPid == pid);
600         }
601     }
602 
603     /**
604      * Updates and dispatches the new loudness parameters for the {@code codecInfos} set.
605      *
606      * @param apc the player configuration for which the loudness parameters are updated.
607      */
updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc)608     private void updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc) {
609         if (DEBUG) {
610             Log.d(TAG, "updateCodecParametersForConfiguration apc:" + apc);
611         }
612 
613         final PersistableBundle allBundles = new PersistableBundle();
614 
615         synchronized (mLock) {
616             final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(),
617                     apc.getClientPid());
618             final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config);
619             final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
620 
621             if (codecInfos != null && deviceInfo != null) {
622                 for (LoudnessCodecInfo info : codecInfos) {
623                     if (info != null) {
624                         allBundles.putPersistableBundle(Integer.toString(info.hashCode()),
625                                 getCodecBundle_l(deviceInfo.getInternalType(),
626                                         deviceInfo.getAddress(), info));
627                     }
628                 }
629             }
630         }
631 
632         if (!allBundles.isDefinitelyEmpty()) {
633             dispatchNewLoudnessParameters(apc.getSessionId(), allBundles);
634         }
635     }
636 
dispatchNewLoudnessParameters(int sessionId, PersistableBundle bundle)637     private void dispatchNewLoudnessParameters(int sessionId,
638             PersistableBundle bundle) {
639         if (DEBUG) {
640             Log.d(TAG,
641                     "dispatchNewLoudnessParameters: sessionId " + sessionId + " bundle: " + bundle);
642         }
643         final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
644         for (int i = 0; i < nbDispatchers; ++i) {
645             try {
646                 mLoudnessUpdateDispatchers.getBroadcastItem(i)
647                         .dispatchLoudnessCodecParameterChange(sessionId, bundle);
648             } catch (RemoteException e) {
649                 Log.e(TAG, "Error dispatching for sessionId " + sessionId + " bundle: " + bundle,
650                         e);
651             }
652         }
653         mLoudnessUpdateDispatchers.finishBroadcast();
654     }
655 
656     @GuardedBy("mLock")
getCodecBundle_l(int internalDeviceType, String address, LoudnessCodecInfo codecInfo)657     private PersistableBundle getCodecBundle_l(int internalDeviceType,
658             String address,
659             LoudnessCodecInfo codecInfo) {
660         LoudnessCodecInputProperties.Builder builder = new LoudnessCodecInputProperties.Builder();
661         LoudnessCodecInputProperties prop = builder.setDeviceSplRange(
662                         getDeviceSplRange(internalDeviceType, address))
663                 .setIsDownmixing(codecInfo.isDownmixing)
664                 .setMetadataType(codecInfo.metadataType)
665                 .build();
666 
667         if (mCachedProperties.containsKey(prop)) {
668             return mCachedProperties.get(prop);
669         }
670         final PersistableBundle codecBundle = prop.createLoudnessParameters();
671         mCachedProperties.put(prop, codecBundle);
672         return codecBundle;
673     }
674 
675     @DeviceSplRange
getDeviceSplRange(int internalDeviceType, String address)676     private int getDeviceSplRange(int internalDeviceType, String address) {
677         @AudioDeviceCategory int deviceCategory;
678         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
679             if (automaticBtDeviceType()) {
680                 deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(address);
681             } else {
682                 deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy(
683                         address, AudioSystem.isBluetoothLeDevice(internalDeviceType));
684             }
685         }
686         if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) {
687             final String splRange = SystemProperties.get(
688                     SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE, "unknown");
689             if (!splRange.equals("unknown")) {
690                 return stringToSplRange(splRange);
691             }
692 
693             @DeviceSplRange int result = SPL_RANGE_SMALL;  // default for phone/tablet/watch
694             if (mAudioService.isPlatformAutomotive() || mAudioService.isPlatformTelevision()) {
695                 result = SPL_RANGE_MEDIUM;
696             }
697 
698             return result;
699         } else if (internalDeviceType == AudioSystem.DEVICE_OUT_USB_HEADSET
700                 || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
701                 || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET
702                 || (AudioSystem.isBluetoothDevice(internalDeviceType)
703                 && deviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES)) {
704             return SPL_RANGE_LARGE;
705         } else if (AudioSystem.isBluetoothDevice(internalDeviceType)) {
706             if (deviceCategory == AUDIO_DEVICE_CATEGORY_CARKIT) {
707                 return SPL_RANGE_MEDIUM;
708             } else if (deviceCategory == AUDIO_DEVICE_CATEGORY_WATCH) {
709                 return SPL_RANGE_SMALL;
710             } else if (deviceCategory == AUDIO_DEVICE_CATEGORY_HEARING_AID) {
711                 return SPL_RANGE_SMALL;
712             }
713         }
714 
715         return SPL_RANGE_UNKNOWN;
716     }
717 
splRangeToString(@eviceSplRange int splRange)718     private static String splRangeToString(@DeviceSplRange int splRange) {
719         switch (splRange) {
720             case SPL_RANGE_LARGE:
721                 return "large";
722             case SPL_RANGE_MEDIUM:
723                 return "medium";
724             case SPL_RANGE_SMALL:
725                 return "small";
726             default:
727                 return "unknown";
728         }
729     }
730 
731     @DeviceSplRange
stringToSplRange(String splRange)732     private static int stringToSplRange(String splRange) {
733         switch (splRange) {
734             case "large":
735                 return SPL_RANGE_LARGE;
736             case "medium":
737                 return SPL_RANGE_MEDIUM;
738             case "small":
739                 return SPL_RANGE_SMALL;
740             default:
741                 return SPL_RANGE_UNKNOWN;
742         }
743     }
744 }
745