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