1 /*
2  * Copyright (C) 2016 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.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
20 import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
21 import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
22 import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
23 import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
24 import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
25 import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
26 import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
27 import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.content.Context;
32 import android.content.pm.PackageManager;
33 import android.media.AudioAttributes;
34 import android.media.AudioDeviceAttributes;
35 import android.media.AudioDeviceInfo;
36 import android.media.AudioManager;
37 import android.media.AudioPlaybackConfiguration;
38 import android.media.AudioPlaybackConfiguration.FormatInfo;
39 import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
40 import android.media.AudioSystem;
41 import android.media.IPlaybackConfigDispatcher;
42 import android.media.PlayerBase;
43 import android.media.VolumeShaper;
44 import android.os.Binder;
45 import android.os.Handler;
46 import android.os.HandlerThread;
47 import android.os.IBinder;
48 import android.os.Message;
49 import android.os.PersistableBundle;
50 import android.os.RemoteException;
51 import android.os.UserHandle;
52 import android.text.TextUtils;
53 import android.util.Log;
54 import android.util.SparseIntArray;
55 
56 import com.android.internal.annotations.GuardedBy;
57 import com.android.internal.util.ArrayUtils;
58 import com.android.server.utils.EventLogger;
59 
60 import java.io.PrintWriter;
61 import java.text.DateFormat;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Collections;
65 import java.util.Date;
66 import java.util.HashMap;
67 import java.util.Iterator;
68 import java.util.List;
69 import java.util.Set;
70 import java.util.concurrent.ConcurrentLinkedQueue;
71 import java.util.function.Consumer;
72 
73 /**
74  * Class to receive and dispatch updates from AudioSystem about recording configurations.
75  */
76 public final class PlaybackActivityMonitor
77         implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
78 
79     public static final String TAG = "AS.PlaybackActivityMon";
80 
81     /*package*/ static final boolean DEBUG = false;
82     /*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
83     /*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
84     /*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
85     /*package*/ static final int VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID = 4;
86 
87     // ducking settings for a "normal duck" at -14dB
88     private static final VolumeShaper.Configuration DUCK_VSHAPE =
89             new VolumeShaper.Configuration.Builder()
90                 .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
91                 .setCurve(new float[] { 0.f, 1.f } /* times */,
92                     new float[] { 1.f, 0.2f } /* volumes */)
93                 .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
94                 .setDuration(MediaFocusControl.getFocusRampTimeMs(
95                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
96                     new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
97                             .build()))
98                 .build();
99     private static final VolumeShaper.Configuration DUCK_ID =
100             new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_DUCK_ID);
101 
102     // ducking settings for a "strong duck" at -35dB (attenuation factor of 0.017783)
103     private static final VolumeShaper.Configuration STRONG_DUCK_VSHAPE =
104             new VolumeShaper.Configuration.Builder()
105                 .setId(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID)
106                 .setCurve(new float[] { 0.f, 1.f } /* times */,
107                         new float[] { 1.f, 0.017783f } /* volumes */)
108                 .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
109                 .setDuration(MediaFocusControl.getFocusRampTimeMs(
110                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
111                         new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
112                                 .build()))
113                     .build();
114     private static final VolumeShaper.Configuration STRONG_DUCK_ID =
115             new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID);
116 
117     private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
118             new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
119                     .createIfNeeded()
120                     .build();
121 
122     private static final long UNMUTE_DURATION_MS = 100;
123     private static final VolumeShaper.Configuration MUTE_AWAIT_CONNECTION_VSHAPE =
124             new VolumeShaper.Configuration.Builder()
125                     .setId(VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID)
126                     .setCurve(new float[] { 0.f, 1.f } /* times */,
127                             new float[] { 1.f, 0.f } /* volumes */)
128                     .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
129                     // even though we specify a duration, it's only used for the unmute,
130                     // for muting this volume shaper is run with PLAY_SKIP_RAMP
131                     .setDuration(UNMUTE_DURATION_MS)
132                     .build();
133 
134     // TODO support VolumeShaper on those players
135     private static final int[] UNDUCKABLE_PLAYER_TYPES = {
136             AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
137             AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL,
138     };
139 
140     // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
141     private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
142             new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
143 
144     private final ConcurrentLinkedQueue<PlayMonitorClient> mClients = new ConcurrentLinkedQueue<>();
145 
146     private final Object mPlayerLock = new Object();
147     @GuardedBy("mPlayerLock")
148     private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
149             new HashMap<Integer, AudioPlaybackConfiguration>();
150 
151     @GuardedBy("mPlayerLock")
152     private final SparseIntArray mPortIdToPiid = new SparseIntArray();
153 
154     private final Context mContext;
155     private int mSavedAlarmVolume = -1;
156     private final int mMaxAlarmVolume;
157     private int mPrivilegedAlarmActiveCount = 0;
158     private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
159 
PlaybackActivityMonitor(Context context, int maxAlarmVolume, Consumer<AudioDeviceAttributes> muteTimeoutCallback)160     PlaybackActivityMonitor(Context context, int maxAlarmVolume,
161             Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
162         mContext = context;
163         mMaxAlarmVolume = maxAlarmVolume;
164         PlayMonitorClient.sListenerDeathMonitor = this;
165         AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
166         mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
167         initEventHandler();
168     }
169 
170     //=================================================================
171     private final ArrayList<Integer> mBannedUids = new ArrayList<Integer>();
172 
173     // see AudioManagerInternal.disableAudioForUid(boolean disable, int uid)
disableAudioForUid(boolean disable, int uid)174     public void disableAudioForUid(boolean disable, int uid) {
175         synchronized(mPlayerLock) {
176             final int index = mBannedUids.indexOf(new Integer(uid));
177             if (index >= 0) {
178                 if (!disable) {
179                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
180                         sEventLogger.enqueue(new EventLogger.StringEvent("unbanning uid:" + uid));
181                     }
182                     mBannedUids.remove(index);
183                     // nothing else to do, future playback requests from this uid are ok
184                 } // no else to handle, uid already present, so disabling again is no-op
185             } else {
186                 if (disable) {
187                     for (AudioPlaybackConfiguration apc : mPlayers.values()) {
188                         checkBanPlayer(apc, uid);
189                     }
190                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
191                         sEventLogger.enqueue(new EventLogger.StringEvent("banning uid:" + uid));
192                     }
193                     mBannedUids.add(new Integer(uid));
194                 } // no else to handle, uid already not in list, so enabling again is no-op
195             }
196         }
197     }
198 
checkBanPlayer(@onNull AudioPlaybackConfiguration apc, int uid)199     private boolean checkBanPlayer(@NonNull AudioPlaybackConfiguration apc, int uid) {
200         final boolean toBan = (apc.getClientUid() == uid);
201         if (toBan) {
202             final int piid = apc.getPlayerInterfaceId();
203             try {
204                 Log.v(TAG, "banning player " + piid + " uid:" + uid);
205                 apc.getPlayerProxy().pause();
206             } catch (Exception e) {
207                 Log.e(TAG, "error banning player " + piid + " uid:" + uid, e);
208             }
209         }
210         return toBan;
211     }
212 
213     //=================================================================
214     // Player to ignore (only handling single player, designed for ignoring
215     // in the logs one specific player such as the touch sounds player)
216     @GuardedBy("mPlayerLock")
217     private ArrayList<Integer> mDoNotLogPiidList = new ArrayList<>();
218 
ignorePlayerIId(int doNotLogPiid)219     /*package*/ void ignorePlayerIId(int doNotLogPiid) {
220         synchronized (mPlayerLock) {
221             mDoNotLogPiidList.add(doNotLogPiid);
222         }
223     }
224 
225     //=================================================================
226     // Track players and their states
227     // methods playerAttributes, playerEvent, releasePlayer are all oneway calls
228     //  into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
229     //  all listeners as oneway calls.
230 
trackPlayer(PlayerBase.PlayerIdCard pic)231     public int trackPlayer(PlayerBase.PlayerIdCard pic) {
232         final int newPiid = AudioSystem.newAudioPlayerId();
233         if (DEBUG) { Log.v(TAG, "trackPlayer() new piid=" + newPiid); }
234         final AudioPlaybackConfiguration apc =
235                 new AudioPlaybackConfiguration(pic, newPiid,
236                         Binder.getCallingUid(), Binder.getCallingPid());
237         apc.init();
238         synchronized (mAllowedCapturePolicies) {
239             int uid = apc.getClientUid();
240             if (mAllowedCapturePolicies.containsKey(uid)) {
241                 updateAllowedCapturePolicy(apc, mAllowedCapturePolicies.get(uid));
242             }
243         }
244         sEventLogger.enqueue(new NewPlayerEvent(apc));
245         synchronized(mPlayerLock) {
246             mPlayers.put(newPiid, apc);
247             maybeMutePlayerAwaitingConnection(apc);
248         }
249         return newPiid;
250     }
251 
playerAttributes(int piid, @NonNull AudioAttributes attr, int binderUid)252     public void playerAttributes(int piid, @NonNull AudioAttributes attr, int binderUid) {
253         final boolean change;
254         synchronized (mAllowedCapturePolicies) {
255             if (mAllowedCapturePolicies.containsKey(binderUid)
256                     && attr.getAllowedCapturePolicy() < mAllowedCapturePolicies.get(binderUid)) {
257                 attr = new AudioAttributes.Builder(attr)
258                         .setAllowedCapturePolicy(mAllowedCapturePolicies.get(binderUid)).build();
259             }
260         }
261         synchronized(mPlayerLock) {
262             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
263             if (checkConfigurationCaller(piid, apc, binderUid)) {
264                 sEventLogger.enqueue(new AudioAttrEvent(piid, attr));
265                 change = apc.handleAudioAttributesEvent(attr);
266             } else {
267                 Log.e(TAG, "Error updating audio attributes");
268                 change = false;
269             }
270         }
271         if (change) {
272             dispatchPlaybackChange(false);
273         }
274     }
275 
276     /**
277      * Update player session ID
278      * @param piid Player id to update
279      * @param sessionId The new audio session ID
280      * @param binderUid Calling binder uid
281      */
playerSessionId(int piid, int sessionId, int binderUid)282     public void playerSessionId(int piid, int sessionId, int binderUid) {
283         final boolean change;
284         synchronized (mPlayerLock) {
285             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
286             if (checkConfigurationCaller(piid, apc, binderUid)) {
287                 change = apc.handleSessionIdEvent(sessionId);
288             } else {
289                 Log.e(TAG, "Error updating audio session");
290                 change = false;
291             }
292         }
293         if (change) {
294             dispatchPlaybackChange(false);
295         }
296     }
297 
298     private static final int FLAGS_FOR_SILENCE_OVERRIDE =
299             AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
300             AudioAttributes.FLAG_BYPASS_MUTE;
301 
checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event)302     private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
303         if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID) {
304             return;
305         }
306         if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
307                 apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
308             if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
309                         == FLAGS_FOR_SILENCE_OVERRIDE  &&
310                     apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
311                     mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
312                             apc.getClientPid(), apc.getClientUid()) ==
313                             PackageManager.PERMISSION_GRANTED) {
314                 if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
315                         apc.getPlayerState() != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
316                     if (mPrivilegedAlarmActiveCount++ == 0) {
317                         mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
318                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
319                         AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
320                                 mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
321                     }
322                 } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
323                         apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
324                     if (--mPrivilegedAlarmActiveCount == 0) {
325                         if (AudioSystem.getStreamVolumeIndex(
326                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
327                                 mMaxAlarmVolume) {
328                             AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
329                                     mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
330                         }
331                     }
332                 }
333             }
334         }
335     }
336 
337     /**
338      * Update player event
339      * @param piid Player id to update
340      * @param event The new player event
341      * @param eventValue The value associated with this event
342      * @param binderUid Calling binder uid
343      */
playerEvent(int piid, int event, int eventValue, int binderUid)344     public void playerEvent(int piid, int event, int eventValue, int binderUid) {
345         if (DEBUG) {
346             Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValue=%d)",
347                     piid, AudioPlaybackConfiguration.playerStateToString(event), eventValue));
348         }
349         boolean change;
350         synchronized(mPlayerLock) {
351             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
352             if (apc == null) {
353                 return;
354             }
355 
356             final boolean doNotLog = mDoNotLogPiidList.contains(piid);
357             if (doNotLog && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
358                 // do not log nor dispatch events for "ignored" players other than the release
359                 return;
360             }
361             sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
362 
363             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
364                 mEventHandler.sendMessage(
365                         mEventHandler.obtainMessage(MSG_II_UPDATE_PORT_EVENT, eventValue, piid));
366                 return;
367             } else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
368                 for (Integer uidInteger: mBannedUids) {
369                     if (checkBanPlayer(apc, uidInteger.intValue())) {
370                         // player was banned, do not update its state
371                         sEventLogger.enqueue(new EventLogger.StringEvent(
372                                 "not starting piid:" + piid + " ,is banned"));
373                         return;
374                     }
375                 }
376             }
377             if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
378                     && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
379                 // FIXME SoundPool not ready for state reporting
380                 return;
381             }
382             if (checkConfigurationCaller(piid, apc, binderUid)) {
383                 //TODO add generation counter to only update to the latest state
384                 checkVolumeForPrivilegedAlarm(apc, event);
385                 change = apc.handleStateEvent(event, eventValue);
386             } else {
387                 Log.e(TAG, "Error handling event " + event);
388                 change = false;
389             }
390             if (change) {
391                 if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
392                     mDuckingManager.checkDuck(apc);
393                     mFadingManager.checkFade(apc);
394                 }
395                 if (doNotLog) {
396                     // do not dispatch events for "ignored" players
397                     change = false;
398                 }
399             }
400         }
401         if (change) {
402             dispatchPlaybackChange(event == AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
403         }
404     }
405 
406     /**
407      * Update event for port
408      * @param portId Port id to update
409      * @param event The new port event
410      * @param extras The values associated with this event
411      * @param binderUid Calling binder uid
412      */
portEvent(int portId, int event, @Nullable PersistableBundle extras, int binderUid)413     public void portEvent(int portId, int event, @Nullable PersistableBundle extras,
414             int binderUid) {
415         if (!UserHandle.isCore(binderUid)) {
416             Log.e(TAG, "Forbidden operation from uid " + binderUid);
417             return;
418         }
419 
420         if (DEBUG) {
421             Log.v(TAG, TextUtils.formatSimple("BLA portEvent(portId=%d, event=%s, extras=%s)",
422                     portId, AudioPlaybackConfiguration.playerStateToString(event), extras));
423         }
424 
425         synchronized (mPlayerLock) {
426             int piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
427             if (piid == PLAYER_PIID_INVALID) {
428                 if (DEBUG) {
429                     Log.v(TAG, "No piid assigned for invalid/internal port id " + portId);
430                 }
431                 return;
432             }
433             final AudioPlaybackConfiguration apc = mPlayers.get(piid);
434             if (apc == null) {
435                 if (DEBUG) {
436                     Log.v(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid);
437                 }
438                 return;
439             }
440 
441             if (apc.getPlayerType()
442                     == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
443                 // FIXME SoundPool not ready for state reporting
444                 return;
445             }
446 
447             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED) {
448                 mEventHandler.sendMessage(
449                         mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid,
450                                 portId,
451                                 extras));
452             } else if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) {
453                 mEventHandler.sendMessage(
454                         mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_FORMAT, piid,
455                                 portId,
456                                 extras));
457             }
458         }
459     }
460 
playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid)461     public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
462         // no check on UID yet because this is only for logging at the moment
463         sEventLogger.enqueue(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
464     }
465 
releasePlayer(int piid, int binderUid)466     public void releasePlayer(int piid, int binderUid) {
467         if (DEBUG) { Log.v(TAG, "releasePlayer() for piid=" + piid); }
468         boolean change = false;
469         synchronized(mPlayerLock) {
470             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
471             if (checkConfigurationCaller(piid, apc, binderUid)) {
472                 sEventLogger.enqueue(new EventLogger.StringEvent(
473                         "releasing player piid:" + piid));
474                 mPlayers.remove(new Integer(piid));
475                 mDuckingManager.removeReleased(apc);
476                 mFadingManager.removeReleased(apc);
477                 mMutedPlayersAwaitingConnection.remove(Integer.valueOf(piid));
478                 checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
479                 change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
480                         AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
481 
482                 // remove all port ids mapped to the released player
483                 mEventHandler.sendMessage(
484                         mEventHandler.obtainMessage(MSG_I_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
485 
486                 if (change && mDoNotLogPiidList.contains(piid)) {
487                     // do not dispatch a change for a "do not log" player
488                     change = false;
489                 }
490             }
491         }
492         if (change) {
493             dispatchPlaybackChange(true /*iplayerreleased*/);
494         }
495     }
496 
onAudioServerDied()497     /*package*/ void onAudioServerDied() {
498         sEventLogger.enqueue(
499                 new EventLogger.StringEvent(
500                         "clear port id to piid map"));
501         synchronized (mPlayerLock) {
502             if (DEBUG) {
503                 Log.v(TAG, "clear port id to piid map:\n" + mPortIdToPiid);
504             }
505             mPortIdToPiid.clear();
506         }
507     }
508 
509     /**
510      * A map of uid to capture policy.
511      */
512     private final HashMap<Integer, Integer> mAllowedCapturePolicies =
513             new HashMap<Integer, Integer>();
514 
515     /**
516      * Cache allowed capture policy, which specifies whether the audio played by the app may or may
517      * not be captured by other apps or the system.
518      *
519      * @param uid the uid of requested app
520      * @param capturePolicy one of
521      *     {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL},
522      *     {@link AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM},
523      *     {@link AudioAttributes#ALLOW_CAPTURE_BY_NONE}.
524      */
setAllowedCapturePolicy(int uid, int capturePolicy)525     public void setAllowedCapturePolicy(int uid, int capturePolicy) {
526         synchronized (mAllowedCapturePolicies) {
527             if (capturePolicy == AudioAttributes.ALLOW_CAPTURE_BY_ALL) {
528                 // When the capture policy is ALLOW_CAPTURE_BY_ALL, it is okay to
529                 // remove it from cached capture policy as it is the default value.
530                 mAllowedCapturePolicies.remove(uid);
531                 return;
532             } else {
533                 mAllowedCapturePolicies.put(uid, capturePolicy);
534             }
535         }
536         synchronized (mPlayerLock) {
537             for (AudioPlaybackConfiguration apc : mPlayers.values()) {
538                 if (apc.getClientUid() == uid) {
539                     updateAllowedCapturePolicy(apc, capturePolicy);
540                 }
541             }
542         }
543     }
544 
545     /**
546      * Return the capture policy for given uid.
547      * @param uid the uid to query its cached capture policy.
548      * @return cached capture policy for given uid or AudioAttributes.ALLOW_CAPTURE_BY_ALL
549      *         if there is not cached capture policy.
550      */
getAllowedCapturePolicy(int uid)551     public int getAllowedCapturePolicy(int uid) {
552         return mAllowedCapturePolicies.getOrDefault(uid, AudioAttributes.ALLOW_CAPTURE_BY_ALL);
553     }
554 
555     /**
556      * Return a copy of all cached capture policies.
557      */
getAllAllowedCapturePolicies()558     public HashMap<Integer, Integer> getAllAllowedCapturePolicies() {
559         synchronized (mAllowedCapturePolicies) {
560             return (HashMap<Integer, Integer>) mAllowedCapturePolicies.clone();
561         }
562     }
563 
updateAllowedCapturePolicy(AudioPlaybackConfiguration apc, int capturePolicy)564     private void updateAllowedCapturePolicy(AudioPlaybackConfiguration apc, int capturePolicy) {
565         AudioAttributes attr = apc.getAudioAttributes();
566         if (attr.getAllowedCapturePolicy() >= capturePolicy) {
567             return;
568         }
569         apc.handleAudioAttributesEvent(
570                 new AudioAttributes.Builder(apc.getAudioAttributes())
571                         .setAllowedCapturePolicy(capturePolicy).build());
572     }
573 
574     // Implementation of AudioPlaybackConfiguration.PlayerDeathMonitor
575     @Override
playerDeath(int piid)576     public void playerDeath(int piid) {
577         releasePlayer(piid, 0);
578     }
579 
580     /**
581      * Returns true if a player belonging to the app with given uid is active.
582      *
583      * @param uid the app uid
584      * @return true if a player is active, false otherwise
585      */
isPlaybackActiveForUid(int uid)586     public boolean isPlaybackActiveForUid(int uid) {
587         synchronized (mPlayerLock) {
588             for (AudioPlaybackConfiguration apc : mPlayers.values()) {
589                 if (apc.isActive() && apc.getClientUid() == uid) {
590                     return true;
591                 }
592             }
593         }
594         return false;
595     }
596 
597     /**
598      * Return true if an active playback for media use case is currently routed to
599      * a remote submix device with the supplied address.
600      * @param address
601      */
hasActiveMediaPlaybackOnSubmixWithAddress(@onNull String address)602     public boolean hasActiveMediaPlaybackOnSubmixWithAddress(@NonNull String address) {
603         synchronized (mPlayerLock) {
604             for (AudioPlaybackConfiguration apc : mPlayers.values()) {
605                 AudioDeviceInfo device = apc.getAudioDeviceInfo();
606                 if (apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_MEDIA
607                         && apc.isActive() && device != null
608                         && device.getInternalType() == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
609                         && address.equals(device.getAddress())) {
610                     return true;
611                 }
612             }
613         }
614         return false;
615     }
616 
dump(PrintWriter pw)617     protected void dump(PrintWriter pw) {
618         // players
619         pw.println("\nPlaybackActivityMonitor dump time: "
620                 + DateFormat.getTimeInstance().format(new Date()));
621         synchronized(mPlayerLock) {
622             pw.println("\n  playback listeners:");
623             for (PlayMonitorClient pmc : mClients) {
624                 pw.print(" " + (pmc.isPrivileged() ? "(S)" : "(P)")
625                         + pmc.toString());
626             }
627             pw.println("\n");
628             // all players
629             pw.println("\n  players:");
630             final List<Integer> piidIntList = new ArrayList<Integer>(mPlayers.keySet());
631             Collections.sort(piidIntList);
632             for (Integer piidInt : piidIntList) {
633                 final AudioPlaybackConfiguration apc = mPlayers.get(piidInt);
634                 if (apc != null) {
635                     if (mDoNotLogPiidList.contains(apc.getPlayerInterfaceId())) {
636                         pw.print("(not logged)");
637                     }
638                     apc.dump(pw);
639                 }
640             }
641             // ducked players
642             pw.println("\n  ducked players piids:");
643             mDuckingManager.dump(pw);
644             // faded out players
645             pw.println("\n  faded out players piids:");
646             mFadingManager.dump(pw);
647             // players muted due to the device ringing or being in a call
648             pw.print("\n  muted player piids due to call/ring:");
649             for (int piid : mMutedPlayers) {
650                 pw.print(" " + piid);
651             }
652             pw.println();
653             // banned players:
654             pw.print("\n  banned uids:");
655             for (int uid : mBannedUids) {
656                 pw.print(" " + uid);
657             }
658             pw.println("\n");
659             // muted players:
660             pw.print("\n  muted players (piids) awaiting device connection:");
661             for (int piid : mMutedPlayersAwaitingConnection) {
662                 pw.print(" " + piid);
663             }
664             pw.println("\n");
665             // portId to piid mappings:
666             pw.println("\n  current portId to piid map:");
667             for (int i = 0; i < mPortIdToPiid.size(); ++i) {
668                 pw.println(
669                         "  portId: " + mPortIdToPiid.keyAt(i) + " piid: " + mPortIdToPiid.valueAt(
670                                 i));
671             }
672             pw.println("\n");
673             // log
674             sEventLogger.dump(pw);
675         }
676 
677         synchronized (mAllowedCapturePolicies) {
678             pw.println("\n  allowed capture policies:");
679             for (HashMap.Entry<Integer, Integer> entry : mAllowedCapturePolicies.entrySet()) {
680                 pw.println("  uid: " + entry.getKey() + " policy: " + entry.getValue());
681             }
682         }
683     }
684 
685     /**
686      * Check that piid and uid are valid for the given valid configuration.
687      * @param piid the piid of the player.
688      * @param apc the configuration found for this piid.
689      * @param binderUid actual uid of client trying to signal a player state/event/attributes.
690      * @return true if the call is valid and the change should proceed, false otherwise. Always
691      *      returns false when apc is null.
692      */
checkConfigurationCaller(int piid, final AudioPlaybackConfiguration apc, int binderUid)693     private static boolean checkConfigurationCaller(int piid,
694             final AudioPlaybackConfiguration apc, int binderUid) {
695         if (apc == null) {
696             return false;
697         } else if ((binderUid != 0) && (apc.getClientUid() != binderUid)) {
698             Log.e(TAG, "Forbidden operation from uid " + binderUid + " for player " + piid);
699             return false;
700         }
701         return true;
702     }
703 
704     /**
705      * Sends new list after update of playback configurations
706      * @param iplayerReleased indicates if the change was due to a player being released
707      */
dispatchPlaybackChange(boolean iplayerReleased)708     private void dispatchPlaybackChange(boolean iplayerReleased) {
709         if (DEBUG) { Log.v(TAG, "dispatchPlaybackChange to " + mClients.size() + " clients"); }
710         final List<AudioPlaybackConfiguration> configsSystem;
711         // list of playback configurations for "public consumption". It is computed lazy if there
712         // are non-system playback activity listeners.
713         List<AudioPlaybackConfiguration> configsPublic = null;
714         synchronized (mPlayerLock) {
715             if (mPlayers.isEmpty()) {
716                 return;
717             }
718             configsSystem = new ArrayList<>(mPlayers.values());
719         }
720 
721         final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
722         while (clientIterator.hasNext()) {
723             final PlayMonitorClient pmc = clientIterator.next();
724             // do not spam the logs if there are problems communicating with this client
725             if (!pmc.reachedMaxErrorCount()) {
726                 if (pmc.isPrivileged()) {
727                     pmc.dispatchPlaybackConfigChange(configsSystem,
728                             iplayerReleased);
729                 } else {
730                     if (configsPublic == null) {
731                         configsPublic = anonymizeForPublicConsumption(configsSystem);
732                     }
733                     // non-system clients don't have the control interface IPlayer, so
734                     // they don't need to flush commands when a player was released
735                     pmc.dispatchPlaybackConfigChange(configsPublic, false);
736                 }
737             }
738         }
739     }
740 
anonymizeForPublicConsumption( List<AudioPlaybackConfiguration> sysConfigs)741     private ArrayList<AudioPlaybackConfiguration> anonymizeForPublicConsumption(
742             List<AudioPlaybackConfiguration> sysConfigs) {
743         ArrayList<AudioPlaybackConfiguration> publicConfigs =
744                 new ArrayList<AudioPlaybackConfiguration>();
745         // only add active anonymized configurations,
746         for (AudioPlaybackConfiguration config : sysConfigs) {
747             if (config.isActive()) {
748                 publicConfigs.add(AudioPlaybackConfiguration.anonymizedCopy(config));
749             }
750         }
751         return publicConfigs;
752     }
753 
754 
755     //=================================================================
756     // PlayerFocusEnforcer implementation
757     private final ArrayList<Integer> mMutedPlayers = new ArrayList<Integer>();
758 
759     private final DuckingManager mDuckingManager = new DuckingManager();
760 
761     @Override
duckPlayers(@onNull FocusRequester winner, @NonNull FocusRequester loser, boolean forceDuck)762     public boolean duckPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser,
763                                boolean forceDuck) {
764         if (DEBUG) {
765             Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
766                     winner.getClientUid(), loser.getClientUid()));
767         }
768         synchronized (mPlayerLock) {
769             if (mPlayers.isEmpty()) {
770                 return true;
771             }
772             // check if this UID needs to be ducked (return false if not), and gather list of
773             // eligible players to duck
774             final Iterator<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator();
775             final ArrayList<AudioPlaybackConfiguration> apcsToDuck =
776                     new ArrayList<AudioPlaybackConfiguration>();
777             while (apcIterator.hasNext()) {
778                 final AudioPlaybackConfiguration apc = apcIterator.next();
779                 if (!winner.hasSameUid(apc.getClientUid())
780                         && loser.hasSameUid(apc.getClientUid())
781                         && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
782                 {
783                     if (!forceDuck && (apc.getAudioAttributes().getContentType() ==
784                             AudioAttributes.CONTENT_TYPE_SPEECH)) {
785                         // the player is speaking, ducking will make the speech unintelligible
786                         // so let the app handle it instead
787                         Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
788                                 + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
789                                 + " - SPEECH");
790                         return false;
791                     } else if (ArrayUtils.contains(UNDUCKABLE_PLAYER_TYPES, apc.getPlayerType())) {
792                         Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
793                                 + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
794                                 + " due to type:"
795                                 + AudioPlaybackConfiguration.toLogFriendlyPlayerType(
796                                         apc.getPlayerType()));
797                         return false;
798                     }
799                     apcsToDuck.add(apc);
800                 }
801             }
802             // add the players eligible for ducking to the list, and duck them
803             // (if apcsToDuck is empty, this will at least mark this uid as ducked, so when
804             //  players of the same uid start, they will be ducked by DuckingManager.checkDuck())
805             mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck, reqCausesStrongDuck(winner));
806         }
807         return true;
808     }
809 
reqCausesStrongDuck(FocusRequester requester)810     private boolean reqCausesStrongDuck(FocusRequester requester) {
811         if (requester.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
812             return false;
813         }
814         final int reqUsage = requester.getAudioAttributes().getUsage();
815         if (reqUsage == AudioAttributes.USAGE_ASSISTANT) {
816             return true;
817         }
818         return false;
819     }
820 
821     @Override
restoreVShapedPlayers(@onNull FocusRequester winner)822     public void restoreVShapedPlayers(@NonNull FocusRequester winner) {
823         if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
824         synchronized (mPlayerLock) {
825             mDuckingManager.unduckUid(winner.getClientUid(), mPlayers);
826             mFadingManager.unfadeOutUid(winner.getClientUid(), mPlayers);
827         }
828     }
829 
830     @Override
mutePlayersForCall(int[] usagesToMute)831     public void mutePlayersForCall(int[] usagesToMute) {
832         if (DEBUG) {
833             String log = new String("mutePlayersForCall: usages=");
834             for (int usage : usagesToMute) { log += " " + usage; }
835             Log.v(TAG, log);
836         }
837         synchronized (mPlayerLock) {
838             final Set<Integer> piidSet = mPlayers.keySet();
839             final Iterator<Integer> piidIterator = piidSet.iterator();
840             // find which players to mute
841             while (piidIterator.hasNext()) {
842                 final Integer piid = piidIterator.next();
843                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
844                 if (apc == null) {
845                     continue;
846                 }
847                 final int playerUsage = apc.getAudioAttributes().getUsage();
848                 boolean mute = false;
849                 for (int usageToMute : usagesToMute) {
850                     if (playerUsage == usageToMute) {
851                         mute = true;
852                         break;
853                     }
854                 }
855                 if (mute) {
856                     try {
857                         sEventLogger.enqueue((new EventLogger.StringEvent("call: muting piid:"
858                                 + piid + " uid:" + apc.getClientUid())).printLog(TAG));
859                         apc.getPlayerProxy().setVolume(0.0f);
860                         mMutedPlayers.add(new Integer(piid));
861                     } catch (Exception e) {
862                         Log.e(TAG, "call: error muting player " + piid, e);
863                     }
864                 }
865             }
866         }
867     }
868 
869     @Override
unmutePlayersForCall()870     public void unmutePlayersForCall() {
871         if (DEBUG) {
872             Log.v(TAG, "unmutePlayersForCall()");
873         }
874         synchronized (mPlayerLock) {
875             if (mMutedPlayers.isEmpty()) {
876                 return;
877             }
878             for (int piid : mMutedPlayers) {
879                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
880                 if (apc != null) {
881                     try {
882                         sEventLogger.enqueue(new EventLogger.StringEvent("call: unmuting piid:"
883                                 + piid).printLog(TAG));
884                         apc.getPlayerProxy().setVolume(1.0f);
885                     } catch (Exception e) {
886                         Log.e(TAG, "call: error unmuting player " + piid + " uid:"
887                                 + apc.getClientUid(), e);
888                     }
889                 }
890             }
891             mMutedPlayers.clear();
892         }
893     }
894 
895     private final FadeOutManager mFadingManager = new FadeOutManager();
896 
897     /**
898      *
899      * @param winner the new non-transient focus owner
900      * @param loser the previous focus owner
901      * @return true if there are players being faded out
902      */
903     @Override
fadeOutPlayers(@onNull FocusRequester winner, @NonNull FocusRequester loser)904     public boolean fadeOutPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser) {
905         if (DEBUG) {
906             Log.v(TAG, "fadeOutPlayers: winner=" + winner.getPackageName()
907                     +  " loser=" + loser.getPackageName());
908         }
909         boolean loserHasActivePlayers = false;
910 
911         // find which players to fade out
912         synchronized (mPlayerLock) {
913             if (mPlayers.isEmpty()) {
914                 if (DEBUG) { Log.v(TAG, "no players to fade out"); }
915                 return false;
916             }
917             if (!FadeOutManager.canCauseFadeOut(winner, loser)) {
918                 return false;
919             }
920             // check if this UID needs to be faded out (return false if not), and gather list of
921             // eligible players to fade out
922             final Iterator<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator();
923             final ArrayList<AudioPlaybackConfiguration> apcsToFadeOut =
924                     new ArrayList<AudioPlaybackConfiguration>();
925             while (apcIterator.hasNext()) {
926                 final AudioPlaybackConfiguration apc = apcIterator.next();
927                 if (!winner.hasSameUid(apc.getClientUid())
928                         && loser.hasSameUid(apc.getClientUid())
929                         && apc.getPlayerState()
930                         == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
931                     if (!FadeOutManager.canBeFadedOut(apc)) {
932                         // the player is not eligible to be faded out, bail
933                         Log.v(TAG, "not fading out player " + apc.getPlayerInterfaceId()
934                                 + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
935                                 + " type:"
936                                 + AudioPlaybackConfiguration.toLogFriendlyPlayerType(
937                                         apc.getPlayerType())
938                                 + " attr:" + apc.getAudioAttributes());
939                         return false;
940                     }
941                     loserHasActivePlayers = true;
942                     apcsToFadeOut.add(apc);
943                 }
944             }
945             if (loserHasActivePlayers) {
946                 mFadingManager.fadeOutUid(loser.getClientUid(), apcsToFadeOut);
947             }
948         }
949 
950         return loserHasActivePlayers;
951     }
952 
953     @Override
forgetUid(int uid)954     public void forgetUid(int uid) {
955         final HashMap<Integer, AudioPlaybackConfiguration> players;
956         synchronized (mPlayerLock) {
957             players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone();
958         }
959         mFadingManager.unfadeOutUid(uid, players);
960         mDuckingManager.unduckUid(uid, players);
961     }
962 
963     //=================================================================
964     // Track playback activity listeners
965 
registerPlaybackCallback(IPlaybackConfigDispatcher pcdb, boolean isPrivileged)966     void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
967         if (pcdb == null) {
968             return;
969         }
970         final PlayMonitorClient pmc = new PlayMonitorClient(pcdb, isPrivileged);
971         if (pmc.init()) {
972             mClients.add(pmc);
973         }
974     }
975 
unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb)976     void unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
977         if (pcdb == null) {
978             return;
979         }
980         final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
981         // iterate over the clients to remove the dispatcher
982         while (clientIterator.hasNext()) {
983             PlayMonitorClient pmc = clientIterator.next();
984             if (pmc.equalsDispatcher(pcdb)) {
985                 pmc.release();
986                 clientIterator.remove();
987             }
988         }
989     }
990 
getActivePlaybackConfigurations(boolean isPrivileged)991     List<AudioPlaybackConfiguration> getActivePlaybackConfigurations(boolean isPrivileged) {
992         synchronized (mPlayerLock) {
993             if (isPrivileged) {
994                 return new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
995             } else {
996                 return anonymizeForPublicConsumption(
997                             new ArrayList<AudioPlaybackConfiguration>(mPlayers.values()));
998             }
999         }
1000     }
1001 
1002     /**
1003      * Inner class to track clients that want to be notified of playback updates
1004      */
1005     private static final class PlayMonitorClient implements IBinder.DeathRecipient {
1006 
1007         // can afford to be static because only one PlaybackActivityMonitor ever instantiated
1008         static PlaybackActivityMonitor sListenerDeathMonitor;
1009 
1010         // number of errors after which we don't update this client anymore to not spam the logs
1011         private static final int MAX_ERRORS = 5;
1012 
1013         private final IPlaybackConfigDispatcher mDispatcherCb;
1014 
1015         @GuardedBy("this")
1016         private final boolean mIsPrivileged;
1017         @GuardedBy("this")
1018         private boolean mIsReleased = false;
1019         @GuardedBy("this")
1020         private int mErrorCount = 0;
1021 
PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged)1022         PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
1023             mDispatcherCb = pcdb;
1024             mIsPrivileged = isPrivileged;
1025         }
1026 
1027         @Override
binderDied()1028         public void binderDied() {
1029             Log.w(TAG, "client died");
1030             sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb);
1031         }
1032 
init()1033         synchronized boolean init() {
1034             if (mIsReleased) {
1035                 // Do not init after release
1036                 return false;
1037             }
1038             try {
1039                 mDispatcherCb.asBinder().linkToDeath(this, 0);
1040                 return true;
1041             } catch (RemoteException e) {
1042                 Log.w(TAG, "Could not link to client death", e);
1043                 return false;
1044             }
1045         }
1046 
release()1047         synchronized void release() {
1048             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
1049             mIsReleased = true;
1050         }
1051 
dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs, boolean flush)1052         void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
1053                 boolean flush) {
1054             synchronized (this) {
1055                 if (mIsReleased) {
1056                     // Do not dispatch anything after release
1057                     return;
1058                 }
1059             }
1060             try {
1061                 mDispatcherCb.dispatchPlaybackConfigChange(configs, flush);
1062             } catch (RemoteException e) {
1063                 synchronized (this) {
1064                     mErrorCount++;
1065                     Log.e(TAG, "Error (" + mErrorCount
1066                             + ") trying to dispatch playback config change to " + this, e);
1067                 }
1068             }
1069         }
1070 
isPrivileged()1071         synchronized boolean isPrivileged() {
1072             return mIsPrivileged;
1073         }
1074 
reachedMaxErrorCount()1075         synchronized boolean reachedMaxErrorCount() {
1076             return mErrorCount >= MAX_ERRORS;
1077         }
1078 
equalsDispatcher(IPlaybackConfigDispatcher pcdb)1079         synchronized boolean equalsDispatcher(IPlaybackConfigDispatcher pcdb) {
1080             if (pcdb == null) {
1081                 return false;
1082             }
1083             return pcdb.asBinder().equals(mDispatcherCb.asBinder());
1084         }
1085     }
1086 
1087     //=================================================================
1088     // Class to handle ducking related operations for a given UID
1089     private static final class DuckingManager {
1090         private final HashMap<Integer, DuckedApp> mDuckers = new HashMap<Integer, DuckedApp>();
1091 
duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck, boolean requestCausesStrongDuck)1092         synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck,
1093                 boolean requestCausesStrongDuck) {
1094             if (DEBUG) {  Log.v(TAG, "DuckingManager: duckUid() uid:"+ uid); }
1095             if (!mDuckers.containsKey(uid)) {
1096                 mDuckers.put(uid, new DuckedApp(uid, requestCausesStrongDuck));
1097             }
1098             final DuckedApp da = mDuckers.get(uid);
1099             for (AudioPlaybackConfiguration apc : apcsToDuck) {
1100                 da.addDuck(apc, false /*skipRamp*/);
1101             }
1102         }
1103 
unduckUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players)1104         synchronized void unduckUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
1105             if (DEBUG) {  Log.v(TAG, "DuckingManager: unduckUid() uid:"+ uid); }
1106             final DuckedApp da = mDuckers.remove(uid);
1107             if (da == null) {
1108                 return;
1109             }
1110             da.removeUnduckAll(players);
1111         }
1112 
1113         // pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
checkDuck(@onNull AudioPlaybackConfiguration apc)1114         synchronized void checkDuck(@NonNull AudioPlaybackConfiguration apc) {
1115             if (DEBUG) {  Log.v(TAG, "DuckingManager: checkDuck() player piid:"
1116                     + apc.getPlayerInterfaceId()+ " uid:"+ apc.getClientUid()); }
1117             final DuckedApp da = mDuckers.get(apc.getClientUid());
1118             if (da == null) {
1119                 return;
1120             }
1121             da.addDuck(apc, true /*skipRamp*/);
1122         }
1123 
dump(PrintWriter pw)1124         synchronized void dump(PrintWriter pw) {
1125             for (DuckedApp da : mDuckers.values()) {
1126                 da.dump(pw);
1127             }
1128         }
1129 
removeReleased(@onNull AudioPlaybackConfiguration apc)1130         synchronized void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
1131             final int uid = apc.getClientUid();
1132             if (DEBUG) {  Log.v(TAG, "DuckingManager: removedReleased() player piid: "
1133                     + apc.getPlayerInterfaceId() + " uid:" + uid); }
1134             final DuckedApp da = mDuckers.get(uid);
1135             if (da == null) {
1136                 return;
1137             }
1138             da.removeReleased(apc);
1139         }
1140 
1141         private static final class DuckedApp {
1142             private final int mUid;
1143             /** determines whether ducking is done with DUCK_VSHAPE or STRONG_DUCK_VSHAPE */
1144             private final boolean mUseStrongDuck;
1145             private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
1146 
DuckedApp(int uid, boolean useStrongDuck)1147             DuckedApp(int uid, boolean useStrongDuck) {
1148                 mUid = uid;
1149                 mUseStrongDuck = useStrongDuck;
1150             }
1151 
dump(PrintWriter pw)1152             void dump(PrintWriter pw) {
1153                 pw.print("\t uid:" + mUid + " piids:");
1154                 for (int piid : mDuckedPlayers) {
1155                     pw.print(" " + piid);
1156                 }
1157                 pw.println("");
1158             }
1159 
1160             // pre-conditions:
1161             //  * apc != null
1162             //  * apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
addDuck(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)1163             void addDuck(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
1164                 final int piid = new Integer(apc.getPlayerInterfaceId());
1165                 if (mDuckedPlayers.contains(piid)) {
1166                     if (DEBUG) { Log.v(TAG, "player piid:" + piid + " already ducked"); }
1167                     return;
1168                 }
1169                 try {
1170                     sEventLogger.enqueue((new DuckEvent(apc, skipRamp, mUseStrongDuck))
1171                             .printLog(TAG));
1172                     apc.getPlayerProxy().applyVolumeShaper(
1173                             mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE,
1174                             skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
1175                     mDuckedPlayers.add(piid);
1176                 } catch (Exception e) {
1177                     Log.e(TAG, "Error ducking player piid:" + piid + " uid:" + mUid, e);
1178                 }
1179             }
1180 
removeUnduckAll(HashMap<Integer, AudioPlaybackConfiguration> players)1181             void removeUnduckAll(HashMap<Integer, AudioPlaybackConfiguration> players) {
1182                 for (int piid : mDuckedPlayers) {
1183                     final AudioPlaybackConfiguration apc = players.get(piid);
1184                     if (apc != null) {
1185                         try {
1186                             sEventLogger.enqueue((new EventLogger.StringEvent("unducking piid:"
1187                                     + piid)).printLog(TAG));
1188                             apc.getPlayerProxy().applyVolumeShaper(
1189                                     mUseStrongDuck ? STRONG_DUCK_ID : DUCK_ID,
1190                                     VolumeShaper.Operation.REVERSE);
1191                         } catch (Exception e) {
1192                             Log.e(TAG, "Error unducking player piid:" + piid + " uid:" + mUid, e);
1193                         }
1194                     } else {
1195                         // this piid was in the list of ducked players, but wasn't found
1196                         if (DEBUG) {
1197                             Log.v(TAG, "Error unducking player piid:" + piid
1198                                     + ", player not found for uid " + mUid);
1199                         }
1200                     }
1201                 }
1202                 mDuckedPlayers.clear();
1203             }
1204 
removeReleased(@onNull AudioPlaybackConfiguration apc)1205             void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
1206                 mDuckedPlayers.remove(new Integer(apc.getPlayerInterfaceId()));
1207             }
1208         }
1209     }
1210 
1211     //=================================================================
1212     // For logging
1213     private static final class PlayerEvent extends EventLogger.Event {
1214         // only keeping the player interface ID as it uniquely identifies the player in the event
1215         final int mPlayerIId;
1216         final int mEvent;
1217         final int mEventValue;
1218 
PlayerEvent(int piid, int event, int eventValue)1219         PlayerEvent(int piid, int event, int eventValue) {
1220             mPlayerIId = piid;
1221             mEvent = event;
1222             mEventValue = eventValue;
1223         }
1224 
1225         @Override
eventToString()1226         public String eventToString() {
1227             StringBuilder builder = new StringBuilder("player piid:").append(mPlayerIId).append(
1228                             " event:")
1229                     .append(AudioPlaybackConfiguration.toLogFriendlyPlayerState(mEvent));
1230 
1231             switch (mEvent) {
1232                 case AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID:
1233                     return AudioPlaybackConfiguration.toLogFriendlyPlayerState(mEvent) + " portId:"
1234                             + mEventValue + " mapped to player piid:" + mPlayerIId;
1235                 case AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID:
1236                     if (mEventValue != 0) {
1237                         builder.append(" deviceId:").append(mEventValue);
1238                     }
1239                     return builder.toString();
1240                 case AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED:
1241                     builder.append(" source:");
1242                     if (mEventValue <= 0) {
1243                         builder.append("none ");
1244                     } else {
1245                         if ((mEventValue & MUTED_BY_MASTER) != 0) {
1246                             builder.append("masterMute ");
1247                         }
1248                         if ((mEventValue & MUTED_BY_STREAM_VOLUME) != 0) {
1249                             builder.append("streamVolume ");
1250                         }
1251                         if ((mEventValue & MUTED_BY_STREAM_MUTED) != 0) {
1252                             builder.append("streamMute ");
1253                         }
1254                         if ((mEventValue & MUTED_BY_APP_OPS) != 0) {
1255                             builder.append("appOps ");
1256                         }
1257                         if ((mEventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
1258                             builder.append("clientVolume ");
1259                         }
1260                         if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
1261                             builder.append("volumeShaper ");
1262                         }
1263                     }
1264                     return builder.toString();
1265                 default:
1266                     return builder.toString();
1267             }
1268         }
1269     }
1270 
1271     private static final class PlayerOpPlayAudioEvent extends EventLogger.Event {
1272         // only keeping the player interface ID as it uniquely identifies the player in the event
1273         final int mPlayerIId;
1274         final boolean mHasOp;
1275         final int mUid;
1276 
PlayerOpPlayAudioEvent(int piid, boolean hasOp, int uid)1277         PlayerOpPlayAudioEvent(int piid, boolean hasOp, int uid) {
1278             mPlayerIId = piid;
1279             mHasOp = hasOp;
1280             mUid = uid;
1281         }
1282 
1283         @Override
eventToString()1284         public String eventToString() {
1285             return new StringBuilder("player piid:").append(mPlayerIId)
1286                     .append(" has OP_PLAY_AUDIO:").append(mHasOp)
1287                     .append(" in uid:").append(mUid).toString();
1288         }
1289     }
1290 
1291     private static final class NewPlayerEvent extends EventLogger.Event {
1292         private final int mPlayerIId;
1293         private final int mPlayerType;
1294         private final int mClientUid;
1295         private final int mClientPid;
1296         private final AudioAttributes mPlayerAttr;
1297         private final int mSessionId;
1298 
NewPlayerEvent(AudioPlaybackConfiguration apc)1299         NewPlayerEvent(AudioPlaybackConfiguration apc) {
1300             mPlayerIId = apc.getPlayerInterfaceId();
1301             mPlayerType = apc.getPlayerType();
1302             mClientUid = apc.getClientUid();
1303             mClientPid = apc.getClientPid();
1304             mPlayerAttr = apc.getAudioAttributes();
1305             mSessionId = apc.getSessionId();
1306         }
1307 
1308         @Override
eventToString()1309         public String eventToString() {
1310             return new String("new player piid:" + mPlayerIId + " uid/pid:" + mClientUid + "/"
1311                     + mClientPid + " type:"
1312                     + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
1313                     + " attr:" + mPlayerAttr
1314                     + " session:" + mSessionId);
1315         }
1316     }
1317 
1318     private abstract static class VolumeShaperEvent extends EventLogger.Event {
1319         private final int mPlayerIId;
1320         private final boolean mSkipRamp;
1321         private final int mClientUid;
1322         private final int mClientPid;
1323 
getVSAction()1324         abstract String getVSAction();
1325 
VolumeShaperEvent(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)1326         VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
1327             mPlayerIId = apc.getPlayerInterfaceId();
1328             mSkipRamp = skipRamp;
1329             mClientUid = apc.getClientUid();
1330             mClientPid = apc.getClientPid();
1331         }
1332 
1333         @Override
eventToString()1334         public String eventToString() {
1335             return new StringBuilder(getVSAction()).append(" player piid:").append(mPlayerIId)
1336                     .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
1337                     .append(" skip ramp:").append(mSkipRamp).toString();
1338         }
1339     }
1340 
1341     static final class DuckEvent extends VolumeShaperEvent {
1342         final boolean mUseStrongDuck;
1343 
1344         @Override
getVSAction()1345         String getVSAction() {
1346             return mUseStrongDuck ? "ducking (strong)" : "ducking";
1347         }
1348 
DuckEvent(@onNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck)1349         DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck)
1350         {
1351             super(apc, skipRamp);
1352             mUseStrongDuck = useStrongDuck;
1353         }
1354     }
1355 
1356     static final class FadeOutEvent extends VolumeShaperEvent {
1357         @Override
getVSAction()1358         String getVSAction() {
1359             return "fading out";
1360         }
1361 
FadeOutEvent(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)1362         FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
1363             super(apc, skipRamp);
1364         }
1365     }
1366 
1367     private static final class AudioAttrEvent extends EventLogger.Event {
1368         private final int mPlayerIId;
1369         private final AudioAttributes mPlayerAttr;
1370 
AudioAttrEvent(int piid, AudioAttributes attr)1371         AudioAttrEvent(int piid, AudioAttributes attr) {
1372             mPlayerIId = piid;
1373             mPlayerAttr = attr;
1374         }
1375 
1376         @Override
eventToString()1377         public String eventToString() {
1378             return new String("player piid:" + mPlayerIId + " new AudioAttributes:" + mPlayerAttr);
1379         }
1380     }
1381 
1382     private static final class MuteAwaitConnectionEvent extends EventLogger.Event {
1383         private final @NonNull int[] mUsagesToMute;
1384 
MuteAwaitConnectionEvent(@onNull int[] usagesToMute)1385         MuteAwaitConnectionEvent(@NonNull int[] usagesToMute) {
1386             mUsagesToMute = usagesToMute;
1387         }
1388 
1389         @Override
eventToString()1390         public String eventToString() {
1391             return "muteAwaitConnection muting usages " + Arrays.toString(mUsagesToMute);
1392         }
1393     }
1394 
1395     private static final class PlayerFormatEvent extends EventLogger.Event {
1396         private final int mPlayerIId;
1397         private final AudioPlaybackConfiguration.FormatInfo mFormat;
1398 
PlayerFormatEvent(int piid, AudioPlaybackConfiguration.FormatInfo format)1399         PlayerFormatEvent(int piid, AudioPlaybackConfiguration.FormatInfo format) {
1400             mPlayerIId = piid;
1401             mFormat = format;
1402         }
1403 
1404         @Override
eventToString()1405         public String eventToString() {
1406             return new String("player piid:" + mPlayerIId + " format update:" + mFormat);
1407         }
1408     }
1409 
1410     static final EventLogger
1411             sEventLogger = new EventLogger(100,
1412             "playback activity as reported through PlayerBase");
1413 
1414     //==========================================================================================
1415     // Mute conditional on device connection
1416     //==========================================================================================
muteAwaitConnection(@onNull int[] usagesToMute, @NonNull AudioDeviceAttributes dev, long timeOutMs)1417     void muteAwaitConnection(@NonNull int[] usagesToMute,
1418             @NonNull AudioDeviceAttributes dev, long timeOutMs) {
1419         sEventLogger.enqueueAndLog(
1420                 "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs,
1421                 EventLogger.Event.ALOGI, TAG);
1422         synchronized (mPlayerLock) {
1423             mutePlayersExpectingDevice(usagesToMute);
1424             // schedule timeout (remove previously scheduled first)
1425             mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
1426             mEventHandler.sendMessageDelayed(
1427                     mEventHandler.obtainMessage(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION, dev),
1428                     timeOutMs);
1429         }
1430     }
1431 
cancelMuteAwaitConnection(String source)1432     void cancelMuteAwaitConnection(String source) {
1433         sEventLogger.enqueueAndLog("cancelMuteAwaitConnection() from:" + source,
1434                 EventLogger.Event.ALOGI, TAG);
1435         synchronized (mPlayerLock) {
1436             // cancel scheduled timeout, ignore device, only one expected device at a time
1437             mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
1438             // unmute immediately
1439             unmutePlayersExpectingDevice();
1440         }
1441     }
1442 
1443     /**
1444      * List of the piids of the players that are muted until a specific audio device connects
1445      */
1446     @GuardedBy("mPlayerLock")
1447     private final ArrayList<Integer> mMutedPlayersAwaitingConnection = new ArrayList<Integer>();
1448 
1449     /**
1450      * List of AudioAttributes usages to mute until a specific audio device connects
1451      */
1452     @GuardedBy("mPlayerLock")
1453     private @Nullable int[] mMutedUsagesAwaitingConnection = null;
1454 
1455     @GuardedBy("mPlayerLock")
mutePlayersExpectingDevice(@onNull int[] usagesToMute)1456     private void mutePlayersExpectingDevice(@NonNull int[] usagesToMute) {
1457         sEventLogger.enqueue(new MuteAwaitConnectionEvent(usagesToMute));
1458         mMutedUsagesAwaitingConnection = usagesToMute;
1459         final Set<Integer> piidSet = mPlayers.keySet();
1460         final Iterator<Integer> piidIterator = piidSet.iterator();
1461         // find which players to mute
1462         while (piidIterator.hasNext()) {
1463             final Integer piid = piidIterator.next();
1464             final AudioPlaybackConfiguration apc = mPlayers.get(piid);
1465             if (apc == null) {
1466                 continue;
1467             }
1468             maybeMutePlayerAwaitingConnection(apc);
1469         }
1470     }
1471 
1472     @GuardedBy("mPlayerLock")
maybeMutePlayerAwaitingConnection(@onNull AudioPlaybackConfiguration apc)1473     private void maybeMutePlayerAwaitingConnection(@NonNull AudioPlaybackConfiguration apc) {
1474         if (mMutedUsagesAwaitingConnection == null) {
1475             return;
1476         }
1477         for (int usage : mMutedUsagesAwaitingConnection) {
1478             if (usage == apc.getAudioAttributes().getUsage()) {
1479                 try {
1480                     sEventLogger.enqueue((new EventLogger.StringEvent(
1481                             "awaiting connection: muting piid:"
1482                                     + apc.getPlayerInterfaceId()
1483                                     + " uid:" + apc.getClientUid())).printLog(TAG));
1484                     apc.getPlayerProxy().applyVolumeShaper(
1485                             MUTE_AWAIT_CONNECTION_VSHAPE,
1486                             PLAY_SKIP_RAMP);
1487                     mMutedPlayersAwaitingConnection.add(apc.getPlayerInterfaceId());
1488                 } catch (Exception e) {
1489                     Log.e(TAG, "awaiting connection: error muting player "
1490                             + apc.getPlayerInterfaceId(), e);
1491                 }
1492             }
1493         }
1494     }
1495 
1496     @GuardedBy("mPlayerLock")
unmutePlayersExpectingDevice()1497     private void unmutePlayersExpectingDevice() {
1498         mMutedUsagesAwaitingConnection = null;
1499         for (int piid : mMutedPlayersAwaitingConnection) {
1500             final AudioPlaybackConfiguration apc = mPlayers.get(piid);
1501             if (apc == null) {
1502                 continue;
1503             }
1504             try {
1505                 sEventLogger.enqueue(new EventLogger.StringEvent(
1506                         "unmuting piid:" + piid).printLog(TAG));
1507                 apc.getPlayerProxy().applyVolumeShaper(MUTE_AWAIT_CONNECTION_VSHAPE,
1508                         VolumeShaper.Operation.REVERSE);
1509             } catch (Exception e) {
1510                 Log.e(TAG, "Error unmuting player " + piid + " uid:"
1511                         + apc.getClientUid(), e);
1512             }
1513         }
1514         mMutedPlayersAwaitingConnection.clear();
1515     }
1516 
1517     //=================================================================
1518     // Message handling
1519     private Handler mEventHandler;
1520     private HandlerThread mEventThread;
1521 
1522     /**
1523      * timeout for a mute awaiting a device connection
1524      * args:
1525      *     msg.obj: the audio device being expected
1526      *         type: AudioDeviceAttributes
1527      */
1528     private static final int MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION = 1;
1529 
1530     /**
1531      * assign new port id to piid
1532      * args:
1533      *     msg.arg1: port id
1534      *     msg.arg2: piid
1535      */
1536     private static final int MSG_II_UPDATE_PORT_EVENT = 2;
1537 
1538     /**
1539      * event for player getting muted
1540      * args:
1541      *     msg.arg1: piid
1542      *     msg.arg2: port id
1543      *     msg.obj: extras describing the mute reason
1544      *         type: PersistableBundle
1545      */
1546     private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 3;
1547 
1548     /**
1549      * clear all ports assigned to a given piid
1550      * args:
1551      *     msg.arg1: the piid
1552      */
1553     private static final int MSG_I_CLEAR_PORTS_FOR_PIID = 4;
1554 
1555     /**
1556      * event for player reporting playback format and spatialization status
1557      * args:
1558      *     msg.arg1: piid
1559      *     msg.arg2: port id
1560      *     msg.obj: extras describing the sample rate, channel mask, spatialized
1561      *         type: PersistableBundle
1562      */
1563     private static final int MSG_IIL_UPDATE_PLAYER_FORMAT = 5;
1564 
initEventHandler()1565     private void initEventHandler() {
1566         mEventThread = new HandlerThread(TAG);
1567         mEventThread.start();
1568         mEventHandler = new Handler(mEventThread.getLooper()) {
1569             @Override
1570             public void handleMessage(Message msg) {
1571                 switch (msg.what) {
1572                     case MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION:
1573                         sEventLogger.enqueueAndLog("Timeout for muting waiting for "
1574                                 + (AudioDeviceAttributes) msg.obj + ", unmuting",
1575                                 EventLogger.Event.ALOGI, TAG);
1576                         synchronized (mPlayerLock) {
1577                             unmutePlayersExpectingDevice();
1578                         }
1579                         mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
1580                         break;
1581 
1582                     case MSG_II_UPDATE_PORT_EVENT:
1583                         synchronized (mPlayerLock) {
1584                             mPortIdToPiid.put(/*portId*/msg.arg1, /*piid*/msg.arg2);
1585                         }
1586                         break;
1587                     case MSG_IIL_UPDATE_PLAYER_MUTED_EVENT:
1588                         // TODO: replace PersistableBundle with own struct
1589                         PersistableBundle extras = (PersistableBundle) msg.obj;
1590                         if (extras == null) {
1591                             Log.w(TAG, "Received mute event with no extras");
1592                             break;
1593                         }
1594                         @PlayerMuteEvent int eventValue = extras.getInt(EXTRA_PLAYER_EVENT_MUTE);
1595 
1596                         synchronized (mPlayerLock) {
1597                             int piid = msg.arg1;
1598 
1599                             sEventLogger.enqueue(
1600                                     new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
1601 
1602                             final AudioPlaybackConfiguration apc;
1603                             synchronized (mPlayerLock) {
1604                                 apc = mPlayers.get(piid);
1605                             }
1606                             if (apc == null || !apc.handleMutedEvent(eventValue)) {
1607                                 break;  // do not dispatch
1608                             }
1609                             dispatchPlaybackChange(/* iplayerReleased= */false);
1610                         }
1611                         break;
1612 
1613                     case MSG_I_CLEAR_PORTS_FOR_PIID:
1614                         int piid = msg.arg1;
1615                         if (piid == AudioPlaybackConfiguration.PLAYER_PIID_INVALID) {
1616                             Log.w(TAG, "Received clear ports with invalid piid");
1617                             break;
1618                         }
1619 
1620                         synchronized (mPlayerLock) {
1621                             int portIdx;
1622                             while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
1623                                 mPortIdToPiid.removeAt(portIdx);
1624                             }
1625                         }
1626                         break;
1627 
1628                     case MSG_IIL_UPDATE_PLAYER_FORMAT:
1629                         final PersistableBundle formatExtras = (PersistableBundle) msg.obj;
1630                         if (formatExtras == null) {
1631                             Log.w(TAG, "Received format event with no extras");
1632                             break;
1633                         }
1634                         final boolean spatialized = formatExtras.getBoolean(
1635                                 AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_SPATIALIZED, false);
1636                         final int sampleRate = formatExtras.getInt(
1637                                 AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_SAMPLE_RATE, 0);
1638                         final int nativeChannelMask = formatExtras.getInt(
1639                                 AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_CHANNEL_MASK, 0);
1640                         final FormatInfo format =
1641                                 new FormatInfo(spatialized, nativeChannelMask, sampleRate);
1642 
1643                         sEventLogger.enqueue(new PlayerFormatEvent(msg.arg1, format));
1644 
1645                         final AudioPlaybackConfiguration apc;
1646                         synchronized (mPlayerLock) {
1647                             apc = mPlayers.get(msg.arg1);
1648                         }
1649                         if (apc == null || !apc.handleFormatEvent(format)) {
1650                             break;  // do not dispatch
1651                         }
1652                         // TODO optimize for no dispatch to non-privileged listeners
1653                         dispatchPlaybackChange(/* iplayerReleased= */false);
1654                         break;
1655                     default:
1656                         break;
1657                 }
1658             }
1659         };
1660     }
1661 }
1662