• 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.content.Context;
21 import android.content.pm.PackageManager;
22 import android.media.AudioAttributes;
23 import android.media.AudioManager;
24 import android.media.AudioPlaybackConfiguration;
25 import android.media.AudioSystem;
26 import android.media.IPlaybackConfigDispatcher;
27 import android.media.PlayerBase;
28 import android.media.VolumeShaper;
29 import android.os.Binder;
30 import android.os.IBinder;
31 import android.os.RemoteException;
32 import android.util.Log;
33 
34 import com.android.internal.util.ArrayUtils;
35 
36 import java.io.PrintWriter;
37 import java.text.DateFormat;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Date;
41 import java.util.HashMap;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.Set;
45 
46 /**
47  * Class to receive and dispatch updates from AudioSystem about recording configurations.
48  */
49 public final class PlaybackActivityMonitor
50         implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
51 
52     public static final String TAG = "AudioService.PlaybackActivityMonitor";
53 
54     private static final boolean DEBUG = false;
55     private static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
56 
57     private static final VolumeShaper.Configuration DUCK_VSHAPE =
58             new VolumeShaper.Configuration.Builder()
59                 .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
60                 .setCurve(new float[] { 0.f, 1.f } /* times */,
61                     new float[] { 1.f, 0.2f } /* volumes */)
62                 .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
63                 .setDuration(MediaFocusControl.getFocusRampTimeMs(
64                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
65                     new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
66                             .build()))
67                 .build();
68     private static final VolumeShaper.Configuration DUCK_ID =
69             new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_DUCK_ID);
70     private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
71             new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
72                     .createIfNeeded()
73                     .build();
74 
75     // TODO support VolumeShaper on those players
76     private static final int[] UNDUCKABLE_PLAYER_TYPES = {
77             AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
78             AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL,
79     };
80 
81     // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
82     private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
83             new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
84 
85     private final ArrayList<PlayMonitorClient> mClients = new ArrayList<PlayMonitorClient>();
86     // a public client is one that needs an anonymized version of the playback configurations, we
87     // keep track of whether there is at least one to know when we need to create the list of
88     // playback configurations that do not contain uid/pid/package name information.
89     private boolean mHasPublicClients = false;
90 
91     private final Object mPlayerLock = new Object();
92     private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
93             new HashMap<Integer, AudioPlaybackConfiguration>();
94 
95     private final Context mContext;
96     private int mSavedAlarmVolume = -1;
97     private final int mMaxAlarmVolume;
98     private int mPrivilegedAlarmActiveCount = 0;
99 
PlaybackActivityMonitor(Context context, int maxAlarmVolume)100     PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
101         mContext = context;
102         mMaxAlarmVolume = maxAlarmVolume;
103         PlayMonitorClient.sListenerDeathMonitor = this;
104         AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
105     }
106 
107     //=================================================================
108     private final ArrayList<Integer> mBannedUids = new ArrayList<Integer>();
109 
110     // see AudioManagerInternal.disableAudioForUid(boolean disable, int uid)
disableAudioForUid(boolean disable, int uid)111     public void disableAudioForUid(boolean disable, int uid) {
112         synchronized(mPlayerLock) {
113             final int index = mBannedUids.indexOf(new Integer(uid));
114             if (index >= 0) {
115                 if (!disable) {
116                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
117                         sEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
118                     }
119                     mBannedUids.remove(index);
120                     // nothing else to do, future playback requests from this uid are ok
121                 } // no else to handle, uid already present, so disabling again is no-op
122             } else {
123                 if (disable) {
124                     for (AudioPlaybackConfiguration apc : mPlayers.values()) {
125                         checkBanPlayer(apc, uid);
126                     }
127                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
128                         sEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
129                     }
130                     mBannedUids.add(new Integer(uid));
131                 } // no else to handle, uid already not in list, so enabling again is no-op
132             }
133         }
134     }
135 
checkBanPlayer(@onNull AudioPlaybackConfiguration apc, int uid)136     private boolean checkBanPlayer(@NonNull AudioPlaybackConfiguration apc, int uid) {
137         final boolean toBan = (apc.getClientUid() == uid);
138         if (toBan) {
139             final int piid = apc.getPlayerInterfaceId();
140             try {
141                 Log.v(TAG, "banning player " + piid + " uid:" + uid);
142                 apc.getPlayerProxy().pause();
143             } catch (Exception e) {
144                 Log.e(TAG, "error banning player " + piid + " uid:" + uid, e);
145             }
146         }
147         return toBan;
148     }
149 
150     //=================================================================
151     // Track players and their states
152     // methods playerAttributes, playerEvent, releasePlayer are all oneway calls
153     //  into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
154     //  all listeners as oneway calls.
155 
trackPlayer(PlayerBase.PlayerIdCard pic)156     public int trackPlayer(PlayerBase.PlayerIdCard pic) {
157         final int newPiid = AudioSystem.newAudioPlayerId();
158         if (DEBUG) { Log.v(TAG, "trackPlayer() new piid=" + newPiid); }
159         final AudioPlaybackConfiguration apc =
160                 new AudioPlaybackConfiguration(pic, newPiid,
161                         Binder.getCallingUid(), Binder.getCallingPid());
162         apc.init();
163         sEventLogger.log(new NewPlayerEvent(apc));
164         synchronized(mPlayerLock) {
165             mPlayers.put(newPiid, apc);
166         }
167         return newPiid;
168     }
169 
playerAttributes(int piid, @NonNull AudioAttributes attr, int binderUid)170     public void playerAttributes(int piid, @NonNull AudioAttributes attr, int binderUid) {
171         final boolean change;
172         synchronized(mPlayerLock) {
173             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
174             if (checkConfigurationCaller(piid, apc, binderUid)) {
175                 sEventLogger.log(new AudioAttrEvent(piid, attr));
176                 change = apc.handleAudioAttributesEvent(attr);
177             } else {
178                 Log.e(TAG, "Error updating audio attributes");
179                 change = false;
180             }
181         }
182         if (change) {
183             dispatchPlaybackChange(false);
184         }
185     }
186 
187     private static final int FLAGS_FOR_SILENCE_OVERRIDE =
188             AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
189             AudioAttributes.FLAG_BYPASS_MUTE;
190 
checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event)191     private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
192         if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
193                 apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
194             if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
195                         == FLAGS_FOR_SILENCE_OVERRIDE  &&
196                     apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
197                     mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
198                             apc.getClientPid(), apc.getClientUid()) ==
199                             PackageManager.PERMISSION_GRANTED) {
200                 if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
201                         apc.getPlayerState() != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
202                     if (mPrivilegedAlarmActiveCount++ == 0) {
203                         mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
204                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
205                         AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
206                                 mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
207                     }
208                 } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
209                         apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
210                     if (--mPrivilegedAlarmActiveCount == 0) {
211                         if (AudioSystem.getStreamVolumeIndex(
212                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
213                                 mMaxAlarmVolume) {
214                             AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
215                                     mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
216                         }
217                     }
218                 }
219             }
220         }
221     }
222 
playerEvent(int piid, int event, int binderUid)223     public void playerEvent(int piid, int event, int binderUid) {
224         if (DEBUG) { Log.v(TAG, String.format("playerEvent(piid=%d, event=%d)", piid, event)); }
225         final boolean change;
226         synchronized(mPlayerLock) {
227             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
228             if (apc == null) {
229                 return;
230             }
231             sEventLogger.log(new PlayerEvent(piid, event));
232             if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
233                 for (Integer uidInteger: mBannedUids) {
234                     if (checkBanPlayer(apc, uidInteger.intValue())) {
235                         // player was banned, do not update its state
236                         sEventLogger.log(new AudioEventLogger.StringEvent(
237                                 "not starting piid:" + piid + " ,is banned"));
238                         return;
239                     }
240                 }
241             }
242             if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
243                 // FIXME SoundPool not ready for state reporting
244                 return;
245             }
246             if (checkConfigurationCaller(piid, apc, binderUid)) {
247                 //TODO add generation counter to only update to the latest state
248                 checkVolumeForPrivilegedAlarm(apc, event);
249                 change = apc.handleStateEvent(event);
250             } else {
251                 Log.e(TAG, "Error handling event " + event);
252                 change = false;
253             }
254             if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
255                 mDuckingManager.checkDuck(apc);
256             }
257         }
258         if (change) {
259             dispatchPlaybackChange(event == AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
260         }
261     }
262 
playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid)263     public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
264         // no check on UID yet because this is only for logging at the moment
265         sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
266     }
267 
releasePlayer(int piid, int binderUid)268     public void releasePlayer(int piid, int binderUid) {
269         if (DEBUG) { Log.v(TAG, "releasePlayer() for piid=" + piid); }
270         boolean change = false;
271         synchronized(mPlayerLock) {
272             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
273             if (checkConfigurationCaller(piid, apc, binderUid)) {
274                 sEventLogger.log(new AudioEventLogger.StringEvent(
275                         "releasing player piid:" + piid));
276                 mPlayers.remove(new Integer(piid));
277                 mDuckingManager.removeReleased(apc);
278                 checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
279                 change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
280             }
281         }
282         if (change) {
283             dispatchPlaybackChange(true /*iplayerreleased*/);
284         }
285     }
286 
287     // Implementation of AudioPlaybackConfiguration.PlayerDeathMonitor
288     @Override
playerDeath(int piid)289     public void playerDeath(int piid) {
290         releasePlayer(piid, 0);
291     }
292 
dump(PrintWriter pw)293     protected void dump(PrintWriter pw) {
294         // players
295         pw.println("\nPlaybackActivityMonitor dump time: "
296                 + DateFormat.getTimeInstance().format(new Date()));
297         synchronized(mPlayerLock) {
298             pw.println("\n  playback listeners:");
299             synchronized(mClients) {
300                 for (PlayMonitorClient pmc : mClients) {
301                     pw.print(" " + (pmc.mIsPrivileged ? "(S)" : "(P)")
302                             + pmc.toString());
303                 }
304             }
305             pw.println("\n");
306             // all players
307             pw.println("\n  players:");
308             final List<Integer> piidIntList = new ArrayList<Integer>(mPlayers.keySet());
309             Collections.sort(piidIntList);
310             for (Integer piidInt : piidIntList) {
311                 final AudioPlaybackConfiguration apc = mPlayers.get(piidInt);
312                 if (apc != null) {
313                     apc.dump(pw);
314                 }
315             }
316             // ducked players
317             pw.println("\n  ducked players piids:");
318             mDuckingManager.dump(pw);
319             // players muted due to the device ringing or being in a call
320             pw.print("\n  muted player piids:");
321             for (int piid : mMutedPlayers) {
322                 pw.print(" " + piid);
323             }
324             pw.println();
325             // banned players:
326             pw.print("\n  banned uids:");
327             for (int uid : mBannedUids) {
328                 pw.print(" " + uid);
329             }
330             pw.println("\n");
331             // log
332             sEventLogger.dump(pw);
333         }
334     }
335 
336     /**
337      * Check that piid and uid are valid for the given valid configuration.
338      * @param piid the piid of the player.
339      * @param apc the configuration found for this piid.
340      * @param binderUid actual uid of client trying to signal a player state/event/attributes.
341      * @return true if the call is valid and the change should proceed, false otherwise. Always
342      *      returns false when apc is null.
343      */
checkConfigurationCaller(int piid, final AudioPlaybackConfiguration apc, int binderUid)344     private static boolean checkConfigurationCaller(int piid,
345             final AudioPlaybackConfiguration apc, int binderUid) {
346         if (apc == null) {
347             return false;
348         } else if ((binderUid != 0) && (apc.getClientUid() != binderUid)) {
349             Log.e(TAG, "Forbidden operation from uid " + binderUid + " for player " + piid);
350             return false;
351         }
352         return true;
353     }
354 
355     /**
356      * Sends new list after update of playback configurations
357      * @param iplayerReleased indicates if the change was due to a player being released
358      */
dispatchPlaybackChange(boolean iplayerReleased)359     private void dispatchPlaybackChange(boolean iplayerReleased) {
360         synchronized (mClients) {
361             // typical use case, nobody is listening, don't do any work
362             if (mClients.isEmpty()) {
363                 return;
364             }
365         }
366         if (DEBUG) { Log.v(TAG, "dispatchPlaybackChange to " + mClients.size() + " clients"); }
367         final List<AudioPlaybackConfiguration> configsSystem;
368         // list of playback configurations for "public consumption". It is only computed if there
369         // are non-system playback activity listeners.
370         final List<AudioPlaybackConfiguration> configsPublic;
371         synchronized (mPlayerLock) {
372             if (mPlayers.isEmpty()) {
373                 return;
374             }
375             configsSystem = new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
376         }
377         synchronized (mClients) {
378             // was done at beginning of method, but could have changed
379             if (mClients.isEmpty()) {
380                 return;
381             }
382             configsPublic = mHasPublicClients ? anonymizeForPublicConsumption(configsSystem) : null;
383             final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
384             while (clientIterator.hasNext()) {
385                 final PlayMonitorClient pmc = clientIterator.next();
386                 try {
387                     // do not spam the logs if there are problems communicating with this client
388                     if (pmc.mErrorCount < PlayMonitorClient.MAX_ERRORS) {
389                         if (pmc.mIsPrivileged) {
390                             pmc.mDispatcherCb.dispatchPlaybackConfigChange(configsSystem,
391                                     iplayerReleased);
392                         } else {
393                             // non-system clients don't have the control interface IPlayer, so
394                             // they don't need to flush commands when a player was released
395                             pmc.mDispatcherCb.dispatchPlaybackConfigChange(configsPublic, false);
396                         }
397                     }
398                 } catch (RemoteException e) {
399                     pmc.mErrorCount++;
400                     Log.e(TAG, "Error (" + pmc.mErrorCount +
401                             ") trying to dispatch playback config change to " + pmc, e);
402                 }
403             }
404         }
405     }
406 
anonymizeForPublicConsumption( List<AudioPlaybackConfiguration> sysConfigs)407     private ArrayList<AudioPlaybackConfiguration> anonymizeForPublicConsumption(
408             List<AudioPlaybackConfiguration> sysConfigs) {
409         ArrayList<AudioPlaybackConfiguration> publicConfigs =
410                 new ArrayList<AudioPlaybackConfiguration>();
411         // only add active anonymized configurations,
412         for (AudioPlaybackConfiguration config : sysConfigs) {
413             if (config.isActive()) {
414                 publicConfigs.add(AudioPlaybackConfiguration.anonymizedCopy(config));
415             }
416         }
417         return publicConfigs;
418     }
419 
420 
421     //=================================================================
422     // PlayerFocusEnforcer implementation
423     private final ArrayList<Integer> mMutedPlayers = new ArrayList<Integer>();
424 
425     private final DuckingManager mDuckingManager = new DuckingManager();
426 
427     @Override
duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck)428     public boolean duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck) {
429         if (DEBUG) {
430             Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
431                     winner.getClientUid(), loser.getClientUid()));
432         }
433         synchronized (mPlayerLock) {
434             if (mPlayers.isEmpty()) {
435                 return true;
436             }
437             // check if this UID needs to be ducked (return false if not), and gather list of
438             // eligible players to duck
439             final Iterator<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator();
440             final ArrayList<AudioPlaybackConfiguration> apcsToDuck =
441                     new ArrayList<AudioPlaybackConfiguration>();
442             while (apcIterator.hasNext()) {
443                 final AudioPlaybackConfiguration apc = apcIterator.next();
444                 if (!winner.hasSameUid(apc.getClientUid())
445                         && loser.hasSameUid(apc.getClientUid())
446                         && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
447                 {
448                     if (!forceDuck && (apc.getAudioAttributes().getContentType() ==
449                             AudioAttributes.CONTENT_TYPE_SPEECH)) {
450                         // the player is speaking, ducking will make the speech unintelligible
451                         // so let the app handle it instead
452                         Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
453                                 + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
454                                 + " - SPEECH");
455                         return false;
456                     } else if (ArrayUtils.contains(UNDUCKABLE_PLAYER_TYPES, apc.getPlayerType())) {
457                         Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
458                                 + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
459                                 + " due to type:"
460                                 + AudioPlaybackConfiguration.toLogFriendlyPlayerType(
461                                         apc.getPlayerType()));
462                         return false;
463                     }
464                     apcsToDuck.add(apc);
465                 }
466             }
467             // add the players eligible for ducking to the list, and duck them
468             // (if apcsToDuck is empty, this will at least mark this uid as ducked, so when
469             //  players of the same uid start, they will be ducked by DuckingManager.checkDuck())
470             mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck);
471         }
472         return true;
473     }
474 
475     @Override
unduckPlayers(FocusRequester winner)476     public void unduckPlayers(FocusRequester winner) {
477         if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
478         synchronized (mPlayerLock) {
479             mDuckingManager.unduckUid(winner.getClientUid(), mPlayers);
480         }
481     }
482 
483     @Override
mutePlayersForCall(int[] usagesToMute)484     public void mutePlayersForCall(int[] usagesToMute) {
485         if (DEBUG) {
486             String log = new String("mutePlayersForCall: usages=");
487             for (int usage : usagesToMute) { log += " " + usage; }
488             Log.v(TAG, log);
489         }
490         synchronized (mPlayerLock) {
491             final Set<Integer> piidSet = mPlayers.keySet();
492             final Iterator<Integer> piidIterator = piidSet.iterator();
493             // find which players to mute
494             while (piidIterator.hasNext()) {
495                 final Integer piid = piidIterator.next();
496                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
497                 if (apc == null) {
498                     continue;
499                 }
500                 final int playerUsage = apc.getAudioAttributes().getUsage();
501                 boolean mute = false;
502                 for (int usageToMute : usagesToMute) {
503                     if (playerUsage == usageToMute) {
504                         mute = true;
505                         break;
506                     }
507                 }
508                 if (mute) {
509                     try {
510                         sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
511                                 + piid + " uid:" + apc.getClientUid())).printLog(TAG));
512                         apc.getPlayerProxy().setVolume(0.0f);
513                         mMutedPlayers.add(new Integer(piid));
514                     } catch (Exception e) {
515                         Log.e(TAG, "call: error muting player " + piid, e);
516                     }
517                 }
518             }
519         }
520     }
521 
522     @Override
unmutePlayersForCall()523     public void unmutePlayersForCall() {
524         if (DEBUG) {
525             Log.v(TAG, "unmutePlayersForCall()");
526         }
527         synchronized (mPlayerLock) {
528             if (mMutedPlayers.isEmpty()) {
529                 return;
530             }
531             for (int piid : mMutedPlayers) {
532                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
533                 if (apc != null) {
534                     try {
535                         sEventLogger.log(new AudioEventLogger.StringEvent("call: unmuting piid:"
536                                 + piid).printLog(TAG));
537                         apc.getPlayerProxy().setVolume(1.0f);
538                     } catch (Exception e) {
539                         Log.e(TAG, "call: error unmuting player " + piid + " uid:"
540                                 + apc.getClientUid(), e);
541                     }
542                 }
543             }
544             mMutedPlayers.clear();
545         }
546     }
547 
548     //=================================================================
549     // Track playback activity listeners
550 
registerPlaybackCallback(IPlaybackConfigDispatcher pcdb, boolean isPrivileged)551     void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
552         if (pcdb == null) {
553             return;
554         }
555         synchronized(mClients) {
556             final PlayMonitorClient pmc = new PlayMonitorClient(pcdb, isPrivileged);
557             if (pmc.init()) {
558                 if (!isPrivileged) {
559                     mHasPublicClients = true;
560                 }
561                 mClients.add(pmc);
562             }
563         }
564     }
565 
unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb)566     void unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
567         if (pcdb == null) {
568             return;
569         }
570         synchronized(mClients) {
571             final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
572             boolean hasPublicClients = false;
573             // iterate over the clients to remove the dispatcher to remove, and reevaluate at
574             // the same time if we still have a public client.
575             while (clientIterator.hasNext()) {
576                 PlayMonitorClient pmc = clientIterator.next();
577                 if (pcdb.equals(pmc.mDispatcherCb)) {
578                     pmc.release();
579                     clientIterator.remove();
580                 } else {
581                     if (!pmc.mIsPrivileged) {
582                         hasPublicClients = true;
583                     }
584                 }
585             }
586             mHasPublicClients = hasPublicClients;
587         }
588     }
589 
getActivePlaybackConfigurations(boolean isPrivileged)590     List<AudioPlaybackConfiguration> getActivePlaybackConfigurations(boolean isPrivileged) {
591         synchronized(mPlayers) {
592             if (isPrivileged) {
593                 return new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
594             } else {
595                 final List<AudioPlaybackConfiguration> configsPublic;
596                 synchronized (mPlayerLock) {
597                     configsPublic = anonymizeForPublicConsumption(
598                             new ArrayList<AudioPlaybackConfiguration>(mPlayers.values()));
599                 }
600                 return configsPublic;
601             }
602         }
603     }
604 
605 
606     /**
607      * Inner class to track clients that want to be notified of playback updates
608      */
609     private static final class PlayMonitorClient implements IBinder.DeathRecipient {
610 
611         // can afford to be static because only one PlaybackActivityMonitor ever instantiated
612         static PlaybackActivityMonitor sListenerDeathMonitor;
613 
614         final IPlaybackConfigDispatcher mDispatcherCb;
615         final boolean mIsPrivileged;
616 
617         int mErrorCount = 0;
618         // number of errors after which we don't update this client anymore to not spam the logs
619         static final int MAX_ERRORS = 5;
620 
PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged)621         PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
622             mDispatcherCb = pcdb;
623             mIsPrivileged = isPrivileged;
624         }
625 
binderDied()626         public void binderDied() {
627             Log.w(TAG, "client died");
628             sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb);
629         }
630 
init()631         boolean init() {
632             try {
633                 mDispatcherCb.asBinder().linkToDeath(this, 0);
634                 return true;
635             } catch (RemoteException e) {
636                 Log.w(TAG, "Could not link to client death", e);
637                 return false;
638             }
639         }
640 
release()641         void release() {
642             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
643         }
644     }
645 
646     //=================================================================
647     // Class to handle ducking related operations for a given UID
648     private static final class DuckingManager {
649         private final HashMap<Integer, DuckedApp> mDuckers = new HashMap<Integer, DuckedApp>();
650 
duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck)651         synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck) {
652             if (DEBUG) {  Log.v(TAG, "DuckingManager: duckUid() uid:"+ uid); }
653             if (!mDuckers.containsKey(uid)) {
654                 mDuckers.put(uid, new DuckedApp(uid));
655             }
656             final DuckedApp da = mDuckers.get(uid);
657             for (AudioPlaybackConfiguration apc : apcsToDuck) {
658                 da.addDuck(apc, false /*skipRamp*/);
659             }
660         }
661 
unduckUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players)662         synchronized void unduckUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
663             if (DEBUG) {  Log.v(TAG, "DuckingManager: unduckUid() uid:"+ uid); }
664             final DuckedApp da = mDuckers.remove(uid);
665             if (da == null) {
666                 return;
667             }
668             da.removeUnduckAll(players);
669         }
670 
671         // pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
checkDuck(@onNull AudioPlaybackConfiguration apc)672         synchronized void checkDuck(@NonNull AudioPlaybackConfiguration apc) {
673             if (DEBUG) {  Log.v(TAG, "DuckingManager: checkDuck() player piid:"
674                     + apc.getPlayerInterfaceId()+ " uid:"+ apc.getClientUid()); }
675             final DuckedApp da = mDuckers.get(apc.getClientUid());
676             if (da == null) {
677                 return;
678             }
679             da.addDuck(apc, true /*skipRamp*/);
680         }
681 
dump(PrintWriter pw)682         synchronized void dump(PrintWriter pw) {
683             for (DuckedApp da : mDuckers.values()) {
684                 da.dump(pw);
685             }
686         }
687 
removeReleased(@onNull AudioPlaybackConfiguration apc)688         synchronized void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
689             final int uid = apc.getClientUid();
690             if (DEBUG) {  Log.v(TAG, "DuckingManager: removedReleased() player piid: "
691                     + apc.getPlayerInterfaceId() + " uid:" + uid); }
692             final DuckedApp da = mDuckers.get(uid);
693             if (da == null) {
694                 return;
695             }
696             da.removeReleased(apc);
697         }
698 
699         private static final class DuckedApp {
700             private final int mUid;
701             private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
702 
DuckedApp(int uid)703             DuckedApp(int uid) {
704                 mUid = uid;
705             }
706 
dump(PrintWriter pw)707             void dump(PrintWriter pw) {
708                 pw.print("\t uid:" + mUid + " piids:");
709                 for (int piid : mDuckedPlayers) {
710                     pw.print(" " + piid);
711                 }
712                 pw.println("");
713             }
714 
715             // pre-conditions:
716             //  * apc != null
717             //  * apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
addDuck(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)718             void addDuck(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
719                 final int piid = new Integer(apc.getPlayerInterfaceId());
720                 if (mDuckedPlayers.contains(piid)) {
721                     if (DEBUG) { Log.v(TAG, "player piid:" + piid + " already ducked"); }
722                     return;
723                 }
724                 try {
725                     sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
726                     apc.getPlayerProxy().applyVolumeShaper(
727                             DUCK_VSHAPE,
728                             skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
729                     mDuckedPlayers.add(piid);
730                 } catch (Exception e) {
731                     Log.e(TAG, "Error ducking player piid:" + piid + " uid:" + mUid, e);
732                 }
733             }
734 
removeUnduckAll(HashMap<Integer, AudioPlaybackConfiguration> players)735             void removeUnduckAll(HashMap<Integer, AudioPlaybackConfiguration> players) {
736                 for (int piid : mDuckedPlayers) {
737                     final AudioPlaybackConfiguration apc = players.get(piid);
738                     if (apc != null) {
739                         try {
740                             sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
741                                     + piid)).printLog(TAG));
742                             apc.getPlayerProxy().applyVolumeShaper(
743                                     DUCK_ID,
744                                     VolumeShaper.Operation.REVERSE);
745                         } catch (Exception e) {
746                             Log.e(TAG, "Error unducking player piid:" + piid + " uid:" + mUid, e);
747                         }
748                     } else {
749                         // this piid was in the list of ducked players, but wasn't found
750                         if (DEBUG) {
751                             Log.v(TAG, "Error unducking player piid:" + piid
752                                     + ", player not found for uid " + mUid);
753                         }
754                     }
755                 }
756                 mDuckedPlayers.clear();
757             }
758 
removeReleased(@onNull AudioPlaybackConfiguration apc)759             void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
760                 mDuckedPlayers.remove(new Integer(apc.getPlayerInterfaceId()));
761             }
762         }
763     }
764 
765     //=================================================================
766     // For logging
767     private final static class PlayerEvent extends AudioEventLogger.Event {
768         // only keeping the player interface ID as it uniquely identifies the player in the event
769         final int mPlayerIId;
770         final int mState;
771 
PlayerEvent(int piid, int state)772         PlayerEvent(int piid, int state) {
773             mPlayerIId = piid;
774             mState = state;
775         }
776 
777         @Override
eventToString()778         public String eventToString() {
779             return new StringBuilder("player piid:").append(mPlayerIId).append(" state:")
780                     .append(AudioPlaybackConfiguration.toLogFriendlyPlayerState(mState)).toString();
781         }
782     }
783 
784     private final static class PlayerOpPlayAudioEvent extends AudioEventLogger.Event {
785         // only keeping the player interface ID as it uniquely identifies the player in the event
786         final int mPlayerIId;
787         final boolean mHasOp;
788         final int mUid;
789 
PlayerOpPlayAudioEvent(int piid, boolean hasOp, int uid)790         PlayerOpPlayAudioEvent(int piid, boolean hasOp, int uid) {
791             mPlayerIId = piid;
792             mHasOp = hasOp;
793             mUid = uid;
794         }
795 
796         @Override
eventToString()797         public String eventToString() {
798             return new StringBuilder("player piid:").append(mPlayerIId)
799                     .append(" has OP_PLAY_AUDIO:").append(mHasOp)
800                     .append(" in uid:").append(mUid).toString();
801         }
802     }
803 
804     private final static class NewPlayerEvent extends AudioEventLogger.Event {
805         private final int mPlayerIId;
806         private final int mPlayerType;
807         private final int mClientUid;
808         private final int mClientPid;
809         private final AudioAttributes mPlayerAttr;
810 
NewPlayerEvent(AudioPlaybackConfiguration apc)811         NewPlayerEvent(AudioPlaybackConfiguration apc) {
812             mPlayerIId = apc.getPlayerInterfaceId();
813             mPlayerType = apc.getPlayerType();
814             mClientUid = apc.getClientUid();
815             mClientPid = apc.getClientPid();
816             mPlayerAttr = apc.getAudioAttributes();
817         }
818 
819         @Override
eventToString()820         public String eventToString() {
821             return new String("new player piid:" + mPlayerIId + " uid/pid:" + mClientUid + "/"
822                     + mClientPid + " type:"
823                     + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
824                     + " attr:" + mPlayerAttr);
825         }
826     }
827 
828     private static final class DuckEvent extends AudioEventLogger.Event {
829         private final int mPlayerIId;
830         private final boolean mSkipRamp;
831         private final int mClientUid;
832         private final int mClientPid;
833 
DuckEvent(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)834         DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
835             mPlayerIId = apc.getPlayerInterfaceId();
836             mSkipRamp = skipRamp;
837             mClientUid = apc.getClientUid();
838             mClientPid = apc.getClientPid();
839         }
840 
841         @Override
eventToString()842         public String eventToString() {
843             return new StringBuilder("ducking player piid:").append(mPlayerIId)
844                     .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
845                     .append(" skip ramp:").append(mSkipRamp).toString();
846         }
847     }
848 
849     private static final class AudioAttrEvent extends AudioEventLogger.Event {
850         private final int mPlayerIId;
851         private final AudioAttributes mPlayerAttr;
852 
AudioAttrEvent(int piid, AudioAttributes attr)853         AudioAttrEvent(int piid, AudioAttributes attr) {
854             mPlayerIId = piid;
855             mPlayerAttr = attr;
856         }
857 
858         @Override
eventToString()859         public String eventToString() {
860             return new String("player piid:" + mPlayerIId + " new AudioAttributes:" + mPlayerAttr);
861         }
862     }
863 
864     private static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
865             "playback activity as reported through PlayerBase");
866 }
867