• 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     /*package*/ static final boolean DEBUG = false;
55     /*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
56     /*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
57 
58     private static final VolumeShaper.Configuration DUCK_VSHAPE =
59             new VolumeShaper.Configuration.Builder()
60                 .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
61                 .setCurve(new float[] { 0.f, 1.f } /* times */,
62                     new float[] { 1.f, 0.2f } /* volumes */)
63                 .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
64                 .setDuration(MediaFocusControl.getFocusRampTimeMs(
65                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
66                     new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
67                             .build()))
68                 .build();
69     private static final VolumeShaper.Configuration DUCK_ID =
70             new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_DUCK_ID);
71     private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
72             new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
73                     .createIfNeeded()
74                     .build();
75 
76     // TODO support VolumeShaper on those players
77     private static final int[] UNDUCKABLE_PLAYER_TYPES = {
78             AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
79             AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL,
80     };
81 
82     // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
83     private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
84             new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
85 
86     private final ArrayList<PlayMonitorClient> mClients = new ArrayList<PlayMonitorClient>();
87     // a public client is one that needs an anonymized version of the playback configurations, we
88     // keep track of whether there is at least one to know when we need to create the list of
89     // playback configurations that do not contain uid/pid/package name information.
90     private boolean mHasPublicClients = false;
91 
92     private final Object mPlayerLock = new Object();
93     private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
94             new HashMap<Integer, AudioPlaybackConfiguration>();
95 
96     private final Context mContext;
97     private int mSavedAlarmVolume = -1;
98     private final int mMaxAlarmVolume;
99     private int mPrivilegedAlarmActiveCount = 0;
100 
PlaybackActivityMonitor(Context context, int maxAlarmVolume)101     PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
102         mContext = context;
103         mMaxAlarmVolume = maxAlarmVolume;
104         PlayMonitorClient.sListenerDeathMonitor = this;
105         AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
106     }
107 
108     //=================================================================
109     private final ArrayList<Integer> mBannedUids = new ArrayList<Integer>();
110 
111     // see AudioManagerInternal.disableAudioForUid(boolean disable, int uid)
disableAudioForUid(boolean disable, int uid)112     public void disableAudioForUid(boolean disable, int uid) {
113         synchronized(mPlayerLock) {
114             final int index = mBannedUids.indexOf(new Integer(uid));
115             if (index >= 0) {
116                 if (!disable) {
117                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
118                         sEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
119                     }
120                     mBannedUids.remove(index);
121                     // nothing else to do, future playback requests from this uid are ok
122                 } // no else to handle, uid already present, so disabling again is no-op
123             } else {
124                 if (disable) {
125                     for (AudioPlaybackConfiguration apc : mPlayers.values()) {
126                         checkBanPlayer(apc, uid);
127                     }
128                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
129                         sEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
130                     }
131                     mBannedUids.add(new Integer(uid));
132                 } // no else to handle, uid already not in list, so enabling again is no-op
133             }
134         }
135     }
136 
checkBanPlayer(@onNull AudioPlaybackConfiguration apc, int uid)137     private boolean checkBanPlayer(@NonNull AudioPlaybackConfiguration apc, int uid) {
138         final boolean toBan = (apc.getClientUid() == uid);
139         if (toBan) {
140             final int piid = apc.getPlayerInterfaceId();
141             try {
142                 Log.v(TAG, "banning player " + piid + " uid:" + uid);
143                 apc.getPlayerProxy().pause();
144             } catch (Exception e) {
145                 Log.e(TAG, "error banning player " + piid + " uid:" + uid, e);
146             }
147         }
148         return toBan;
149     }
150 
151     //=================================================================
152     // Track players and their states
153     // methods playerAttributes, playerEvent, releasePlayer are all oneway calls
154     //  into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
155     //  all listeners as oneway calls.
156 
trackPlayer(PlayerBase.PlayerIdCard pic)157     public int trackPlayer(PlayerBase.PlayerIdCard pic) {
158         final int newPiid = AudioSystem.newAudioPlayerId();
159         if (DEBUG) { Log.v(TAG, "trackPlayer() new piid=" + newPiid); }
160         final AudioPlaybackConfiguration apc =
161                 new AudioPlaybackConfiguration(pic, newPiid,
162                         Binder.getCallingUid(), Binder.getCallingPid());
163         apc.init();
164         synchronized (mAllowedCapturePolicies) {
165             int uid = apc.getClientUid();
166             if (mAllowedCapturePolicies.containsKey(uid)) {
167                 updateAllowedCapturePolicy(apc, mAllowedCapturePolicies.get(uid));
168             }
169         }
170         sEventLogger.log(new NewPlayerEvent(apc));
171         synchronized(mPlayerLock) {
172             mPlayers.put(newPiid, apc);
173         }
174         return newPiid;
175     }
176 
playerAttributes(int piid, @NonNull AudioAttributes attr, int binderUid)177     public void playerAttributes(int piid, @NonNull AudioAttributes attr, int binderUid) {
178         final boolean change;
179         synchronized (mAllowedCapturePolicies) {
180             if (mAllowedCapturePolicies.containsKey(binderUid)
181                     && attr.getAllowedCapturePolicy() < mAllowedCapturePolicies.get(binderUid)) {
182                 attr = new AudioAttributes.Builder(attr)
183                         .setAllowedCapturePolicy(mAllowedCapturePolicies.get(binderUid)).build();
184             }
185         }
186         synchronized(mPlayerLock) {
187             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
188             if (checkConfigurationCaller(piid, apc, binderUid)) {
189                 sEventLogger.log(new AudioAttrEvent(piid, attr));
190                 change = apc.handleAudioAttributesEvent(attr);
191             } else {
192                 Log.e(TAG, "Error updating audio attributes");
193                 change = false;
194             }
195         }
196         if (change) {
197             dispatchPlaybackChange(false);
198         }
199     }
200 
201     /**
202      * Update player session ID
203      * @param piid Player id to update
204      * @param sessionId The new audio session ID
205      * @param binderUid Calling binder uid
206      */
playerSessionId(int piid, int sessionId, int binderUid)207     public void playerSessionId(int piid, int sessionId, int binderUid) {
208         final boolean change;
209         synchronized (mPlayerLock) {
210             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
211             if (checkConfigurationCaller(piid, apc, binderUid)) {
212                 change = apc.handleSessionIdEvent(sessionId);
213             } else {
214                 Log.e(TAG, "Error updating audio session");
215                 change = false;
216             }
217         }
218         if (change) {
219             dispatchPlaybackChange(false);
220         }
221     }
222 
223     private static final int FLAGS_FOR_SILENCE_OVERRIDE =
224             AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
225             AudioAttributes.FLAG_BYPASS_MUTE;
226 
checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event)227     private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
228         if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
229                 apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
230             if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
231                         == FLAGS_FOR_SILENCE_OVERRIDE  &&
232                     apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
233                     mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
234                             apc.getClientPid(), apc.getClientUid()) ==
235                             PackageManager.PERMISSION_GRANTED) {
236                 if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
237                         apc.getPlayerState() != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
238                     if (mPrivilegedAlarmActiveCount++ == 0) {
239                         mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
240                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
241                         AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
242                                 mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
243                     }
244                 } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
245                         apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
246                     if (--mPrivilegedAlarmActiveCount == 0) {
247                         if (AudioSystem.getStreamVolumeIndex(
248                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
249                                 mMaxAlarmVolume) {
250                             AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
251                                     mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
252                         }
253                     }
254                 }
255             }
256         }
257     }
258 
259     /**
260      * Update player event
261      * @param piid Player id to update
262      * @param event The new player event
263      * @param deviceId The new player device id
264      * @param binderUid Calling binder uid
265      */
playerEvent(int piid, int event, int deviceId, int binderUid)266     public void playerEvent(int piid, int event, int deviceId, int binderUid) {
267         if (DEBUG) {
268             Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%s)",
269                     piid, deviceId, AudioPlaybackConfiguration.playerStateToString(event)));
270         }
271         final boolean change;
272         synchronized(mPlayerLock) {
273             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
274             if (apc == null) {
275                 return;
276             }
277             sEventLogger.log(new PlayerEvent(piid, event, deviceId));
278             if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
279                 for (Integer uidInteger: mBannedUids) {
280                     if (checkBanPlayer(apc, uidInteger.intValue())) {
281                         // player was banned, do not update its state
282                         sEventLogger.log(new AudioEventLogger.StringEvent(
283                                 "not starting piid:" + piid + " ,is banned"));
284                         return;
285                     }
286                 }
287             }
288             if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
289                 // FIXME SoundPool not ready for state reporting
290                 return;
291             }
292             if (checkConfigurationCaller(piid, apc, binderUid)) {
293                 //TODO add generation counter to only update to the latest state
294                 checkVolumeForPrivilegedAlarm(apc, event);
295                 change = apc.handleStateEvent(event, deviceId);
296             } else {
297                 Log.e(TAG, "Error handling event " + event);
298                 change = false;
299             }
300             if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
301                 mDuckingManager.checkDuck(apc);
302                 mFadingManager.checkFade(apc);
303             }
304         }
305         if (change) {
306             dispatchPlaybackChange(event == AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
307         }
308     }
309 
playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid)310     public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
311         // no check on UID yet because this is only for logging at the moment
312         sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
313     }
314 
releasePlayer(int piid, int binderUid)315     public void releasePlayer(int piid, int binderUid) {
316         if (DEBUG) { Log.v(TAG, "releasePlayer() for piid=" + piid); }
317         boolean change = false;
318         synchronized(mPlayerLock) {
319             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
320             if (checkConfigurationCaller(piid, apc, binderUid)) {
321                 sEventLogger.log(new AudioEventLogger.StringEvent(
322                         "releasing player piid:" + piid));
323                 mPlayers.remove(new Integer(piid));
324                 mDuckingManager.removeReleased(apc);
325                 mFadingManager.removeReleased(apc);
326                 checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
327                 change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
328                         AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
329             }
330         }
331         if (change) {
332             dispatchPlaybackChange(true /*iplayerreleased*/);
333         }
334     }
335 
336     /**
337      * A map of uid to capture policy.
338      */
339     private final HashMap<Integer, Integer> mAllowedCapturePolicies =
340             new HashMap<Integer, Integer>();
341 
342     /**
343      * Cache allowed capture policy, which specifies whether the audio played by the app may or may
344      * not be captured by other apps or the system.
345      *
346      * @param uid the uid of requested app
347      * @param capturePolicy one of
348      *     {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL},
349      *     {@link AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM},
350      *     {@link AudioAttributes#ALLOW_CAPTURE_BY_NONE}.
351      */
setAllowedCapturePolicy(int uid, int capturePolicy)352     public void setAllowedCapturePolicy(int uid, int capturePolicy) {
353         synchronized (mAllowedCapturePolicies) {
354             if (capturePolicy == AudioAttributes.ALLOW_CAPTURE_BY_ALL) {
355                 // When the capture policy is ALLOW_CAPTURE_BY_ALL, it is okay to
356                 // remove it from cached capture policy as it is the default value.
357                 mAllowedCapturePolicies.remove(uid);
358                 return;
359             } else {
360                 mAllowedCapturePolicies.put(uid, capturePolicy);
361             }
362         }
363         synchronized (mPlayerLock) {
364             for (AudioPlaybackConfiguration apc : mPlayers.values()) {
365                 if (apc.getClientUid() == uid) {
366                     updateAllowedCapturePolicy(apc, capturePolicy);
367                 }
368             }
369         }
370     }
371 
372     /**
373      * Return the capture policy for given uid.
374      * @param uid the uid to query its cached capture policy.
375      * @return cached capture policy for given uid or AudioAttributes.ALLOW_CAPTURE_BY_ALL
376      *         if there is not cached capture policy.
377      */
getAllowedCapturePolicy(int uid)378     public int getAllowedCapturePolicy(int uid) {
379         return mAllowedCapturePolicies.getOrDefault(uid, AudioAttributes.ALLOW_CAPTURE_BY_ALL);
380     }
381 
382     /**
383      * Return a copy of all cached capture policies.
384      */
getAllAllowedCapturePolicies()385     public HashMap<Integer, Integer> getAllAllowedCapturePolicies() {
386         synchronized (mAllowedCapturePolicies) {
387             return (HashMap<Integer, Integer>) mAllowedCapturePolicies.clone();
388         }
389     }
390 
updateAllowedCapturePolicy(AudioPlaybackConfiguration apc, int capturePolicy)391     private void updateAllowedCapturePolicy(AudioPlaybackConfiguration apc, int capturePolicy) {
392         AudioAttributes attr = apc.getAudioAttributes();
393         if (attr.getAllowedCapturePolicy() >= capturePolicy) {
394             return;
395         }
396         apc.handleAudioAttributesEvent(
397                 new AudioAttributes.Builder(apc.getAudioAttributes())
398                         .setAllowedCapturePolicy(capturePolicy).build());
399     }
400 
401     // Implementation of AudioPlaybackConfiguration.PlayerDeathMonitor
402     @Override
playerDeath(int piid)403     public void playerDeath(int piid) {
404         releasePlayer(piid, 0);
405     }
406 
407     /**
408      * Returns true if a player belonging to the app with given uid is active.
409      *
410      * @param uid the app uid
411      * @return true if a player is active, false otherwise
412      */
isPlaybackActiveForUid(int uid)413     public boolean isPlaybackActiveForUid(int uid) {
414         synchronized (mPlayerLock) {
415             for (AudioPlaybackConfiguration apc : mPlayers.values()) {
416                 if (apc.isActive() && apc.getClientUid() == uid) {
417                     return true;
418                 }
419             }
420         }
421         return false;
422     }
423 
dump(PrintWriter pw)424     protected void dump(PrintWriter pw) {
425         // players
426         pw.println("\nPlaybackActivityMonitor dump time: "
427                 + DateFormat.getTimeInstance().format(new Date()));
428         synchronized(mPlayerLock) {
429             pw.println("\n  playback listeners:");
430             synchronized(mClients) {
431                 for (PlayMonitorClient pmc : mClients) {
432                     pw.print(" " + (pmc.mIsPrivileged ? "(S)" : "(P)")
433                             + pmc.toString());
434                 }
435             }
436             pw.println("\n");
437             // all players
438             pw.println("\n  players:");
439             final List<Integer> piidIntList = new ArrayList<Integer>(mPlayers.keySet());
440             Collections.sort(piidIntList);
441             for (Integer piidInt : piidIntList) {
442                 final AudioPlaybackConfiguration apc = mPlayers.get(piidInt);
443                 if (apc != null) {
444                     apc.dump(pw);
445                 }
446             }
447             // ducked players
448             pw.println("\n  ducked players piids:");
449             mDuckingManager.dump(pw);
450             // faded out players
451             pw.println("\n  faded out players piids:");
452             mFadingManager.dump(pw);
453             // players muted due to the device ringing or being in a call
454             pw.print("\n  muted player piids:");
455             for (int piid : mMutedPlayers) {
456                 pw.print(" " + piid);
457             }
458             pw.println();
459             // banned players:
460             pw.print("\n  banned uids:");
461             for (int uid : mBannedUids) {
462                 pw.print(" " + uid);
463             }
464             pw.println("\n");
465             // log
466             sEventLogger.dump(pw);
467         }
468         synchronized (mAllowedCapturePolicies) {
469             pw.println("\n  allowed capture policies:");
470             for (HashMap.Entry<Integer, Integer> entry : mAllowedCapturePolicies.entrySet()) {
471                 pw.println("  uid: " + entry.getKey() + " policy: " + entry.getValue());
472             }
473         }
474     }
475 
476     /**
477      * Check that piid and uid are valid for the given valid configuration.
478      * @param piid the piid of the player.
479      * @param apc the configuration found for this piid.
480      * @param binderUid actual uid of client trying to signal a player state/event/attributes.
481      * @return true if the call is valid and the change should proceed, false otherwise. Always
482      *      returns false when apc is null.
483      */
checkConfigurationCaller(int piid, final AudioPlaybackConfiguration apc, int binderUid)484     private static boolean checkConfigurationCaller(int piid,
485             final AudioPlaybackConfiguration apc, int binderUid) {
486         if (apc == null) {
487             return false;
488         } else if ((binderUid != 0) && (apc.getClientUid() != binderUid)) {
489             Log.e(TAG, "Forbidden operation from uid " + binderUid + " for player " + piid);
490             return false;
491         }
492         return true;
493     }
494 
495     /**
496      * Sends new list after update of playback configurations
497      * @param iplayerReleased indicates if the change was due to a player being released
498      */
dispatchPlaybackChange(boolean iplayerReleased)499     private void dispatchPlaybackChange(boolean iplayerReleased) {
500         synchronized (mClients) {
501             // typical use case, nobody is listening, don't do any work
502             if (mClients.isEmpty()) {
503                 return;
504             }
505         }
506         if (DEBUG) { Log.v(TAG, "dispatchPlaybackChange to " + mClients.size() + " clients"); }
507         final List<AudioPlaybackConfiguration> configsSystem;
508         // list of playback configurations for "public consumption". It is only computed if there
509         // are non-system playback activity listeners.
510         final List<AudioPlaybackConfiguration> configsPublic;
511         synchronized (mPlayerLock) {
512             if (mPlayers.isEmpty()) {
513                 return;
514             }
515             configsSystem = new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
516         }
517         synchronized (mClients) {
518             // was done at beginning of method, but could have changed
519             if (mClients.isEmpty()) {
520                 return;
521             }
522             configsPublic = mHasPublicClients ? anonymizeForPublicConsumption(configsSystem) : null;
523             final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
524             while (clientIterator.hasNext()) {
525                 final PlayMonitorClient pmc = clientIterator.next();
526                 try {
527                     // do not spam the logs if there are problems communicating with this client
528                     if (pmc.mErrorCount < PlayMonitorClient.MAX_ERRORS) {
529                         if (pmc.mIsPrivileged) {
530                             pmc.mDispatcherCb.dispatchPlaybackConfigChange(configsSystem,
531                                     iplayerReleased);
532                         } else {
533                             // non-system clients don't have the control interface IPlayer, so
534                             // they don't need to flush commands when a player was released
535                             pmc.mDispatcherCb.dispatchPlaybackConfigChange(configsPublic, false);
536                         }
537                     }
538                 } catch (RemoteException e) {
539                     pmc.mErrorCount++;
540                     Log.e(TAG, "Error (" + pmc.mErrorCount +
541                             ") trying to dispatch playback config change to " + pmc, e);
542                 }
543             }
544         }
545     }
546 
anonymizeForPublicConsumption( List<AudioPlaybackConfiguration> sysConfigs)547     private ArrayList<AudioPlaybackConfiguration> anonymizeForPublicConsumption(
548             List<AudioPlaybackConfiguration> sysConfigs) {
549         ArrayList<AudioPlaybackConfiguration> publicConfigs =
550                 new ArrayList<AudioPlaybackConfiguration>();
551         // only add active anonymized configurations,
552         for (AudioPlaybackConfiguration config : sysConfigs) {
553             if (config.isActive()) {
554                 publicConfigs.add(AudioPlaybackConfiguration.anonymizedCopy(config));
555             }
556         }
557         return publicConfigs;
558     }
559 
560 
561     //=================================================================
562     // PlayerFocusEnforcer implementation
563     private final ArrayList<Integer> mMutedPlayers = new ArrayList<Integer>();
564 
565     private final DuckingManager mDuckingManager = new DuckingManager();
566 
567     @Override
duckPlayers(@onNull FocusRequester winner, @NonNull FocusRequester loser, boolean forceDuck)568     public boolean duckPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser,
569                                boolean forceDuck) {
570         if (DEBUG) {
571             Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
572                     winner.getClientUid(), loser.getClientUid()));
573         }
574         synchronized (mPlayerLock) {
575             if (mPlayers.isEmpty()) {
576                 return true;
577             }
578             // check if this UID needs to be ducked (return false if not), and gather list of
579             // eligible players to duck
580             final Iterator<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator();
581             final ArrayList<AudioPlaybackConfiguration> apcsToDuck =
582                     new ArrayList<AudioPlaybackConfiguration>();
583             while (apcIterator.hasNext()) {
584                 final AudioPlaybackConfiguration apc = apcIterator.next();
585                 if (!winner.hasSameUid(apc.getClientUid())
586                         && loser.hasSameUid(apc.getClientUid())
587                         && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
588                 {
589                     if (!forceDuck && (apc.getAudioAttributes().getContentType() ==
590                             AudioAttributes.CONTENT_TYPE_SPEECH)) {
591                         // the player is speaking, ducking will make the speech unintelligible
592                         // so let the app handle it instead
593                         Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
594                                 + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
595                                 + " - SPEECH");
596                         return false;
597                     } else if (ArrayUtils.contains(UNDUCKABLE_PLAYER_TYPES, apc.getPlayerType())) {
598                         Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
599                                 + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
600                                 + " due to type:"
601                                 + AudioPlaybackConfiguration.toLogFriendlyPlayerType(
602                                         apc.getPlayerType()));
603                         return false;
604                     }
605                     apcsToDuck.add(apc);
606                 }
607             }
608             // add the players eligible for ducking to the list, and duck them
609             // (if apcsToDuck is empty, this will at least mark this uid as ducked, so when
610             //  players of the same uid start, they will be ducked by DuckingManager.checkDuck())
611             mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck);
612         }
613         return true;
614     }
615 
616     @Override
restoreVShapedPlayers(@onNull FocusRequester winner)617     public void restoreVShapedPlayers(@NonNull FocusRequester winner) {
618         if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
619         synchronized (mPlayerLock) {
620             mDuckingManager.unduckUid(winner.getClientUid(), mPlayers);
621             mFadingManager.unfadeOutUid(winner.getClientUid(), mPlayers);
622         }
623     }
624 
625     @Override
mutePlayersForCall(int[] usagesToMute)626     public void mutePlayersForCall(int[] usagesToMute) {
627         if (DEBUG) {
628             String log = new String("mutePlayersForCall: usages=");
629             for (int usage : usagesToMute) { log += " " + usage; }
630             Log.v(TAG, log);
631         }
632         synchronized (mPlayerLock) {
633             final Set<Integer> piidSet = mPlayers.keySet();
634             final Iterator<Integer> piidIterator = piidSet.iterator();
635             // find which players to mute
636             while (piidIterator.hasNext()) {
637                 final Integer piid = piidIterator.next();
638                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
639                 if (apc == null) {
640                     continue;
641                 }
642                 final int playerUsage = apc.getAudioAttributes().getUsage();
643                 boolean mute = false;
644                 for (int usageToMute : usagesToMute) {
645                     if (playerUsage == usageToMute) {
646                         mute = true;
647                         break;
648                     }
649                 }
650                 if (mute) {
651                     try {
652                         sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
653                                 + piid + " uid:" + apc.getClientUid())).printLog(TAG));
654                         apc.getPlayerProxy().setVolume(0.0f);
655                         mMutedPlayers.add(new Integer(piid));
656                     } catch (Exception e) {
657                         Log.e(TAG, "call: error muting player " + piid, e);
658                     }
659                 }
660             }
661         }
662     }
663 
664     @Override
unmutePlayersForCall()665     public void unmutePlayersForCall() {
666         if (DEBUG) {
667             Log.v(TAG, "unmutePlayersForCall()");
668         }
669         synchronized (mPlayerLock) {
670             if (mMutedPlayers.isEmpty()) {
671                 return;
672             }
673             for (int piid : mMutedPlayers) {
674                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
675                 if (apc != null) {
676                     try {
677                         sEventLogger.log(new AudioEventLogger.StringEvent("call: unmuting piid:"
678                                 + piid).printLog(TAG));
679                         apc.getPlayerProxy().setVolume(1.0f);
680                     } catch (Exception e) {
681                         Log.e(TAG, "call: error unmuting player " + piid + " uid:"
682                                 + apc.getClientUid(), e);
683                     }
684                 }
685             }
686             mMutedPlayers.clear();
687         }
688     }
689 
690     private final FadeOutManager mFadingManager = new FadeOutManager();
691 
692     /**
693      *
694      * @param winner the new non-transient focus owner
695      * @param loser the previous focus owner
696      * @return true if there are players being faded out
697      */
698     @Override
fadeOutPlayers(@onNull FocusRequester winner, @NonNull FocusRequester loser)699     public boolean fadeOutPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser) {
700         if (DEBUG) {
701             Log.v(TAG, "fadeOutPlayers: winner=" + winner.getPackageName()
702                     +  " loser=" + loser.getPackageName());
703         }
704         boolean loserHasActivePlayers = false;
705 
706         // find which players to fade out
707         synchronized (mPlayerLock) {
708             if (mPlayers.isEmpty()) {
709                 if (DEBUG) { Log.v(TAG, "no players to fade out"); }
710                 return false;
711             }
712             if (!FadeOutManager.canCauseFadeOut(winner, loser)) {
713                 return false;
714             }
715             // check if this UID needs to be faded out (return false if not), and gather list of
716             // eligible players to fade out
717             final Iterator<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator();
718             final ArrayList<AudioPlaybackConfiguration> apcsToFadeOut =
719                     new ArrayList<AudioPlaybackConfiguration>();
720             while (apcIterator.hasNext()) {
721                 final AudioPlaybackConfiguration apc = apcIterator.next();
722                 if (!winner.hasSameUid(apc.getClientUid())
723                         && loser.hasSameUid(apc.getClientUid())
724                         && apc.getPlayerState()
725                         == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
726                     if (!FadeOutManager.canBeFadedOut(apc)) {
727                         // the player is not eligible to be faded out, bail
728                         Log.v(TAG, "not fading out player " + apc.getPlayerInterfaceId()
729                                 + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
730                                 + " type:"
731                                 + AudioPlaybackConfiguration.toLogFriendlyPlayerType(
732                                         apc.getPlayerType())
733                                 + " attr:" + apc.getAudioAttributes());
734                         return false;
735                     }
736                     loserHasActivePlayers = true;
737                     apcsToFadeOut.add(apc);
738                 }
739             }
740             if (loserHasActivePlayers) {
741                 mFadingManager.fadeOutUid(loser.getClientUid(), apcsToFadeOut);
742             }
743         }
744 
745         return loserHasActivePlayers;
746     }
747 
748     @Override
forgetUid(int uid)749     public void forgetUid(int uid) {
750         mFadingManager.forgetUid(uid);
751     }
752 
753     //=================================================================
754     // Track playback activity listeners
755 
registerPlaybackCallback(IPlaybackConfigDispatcher pcdb, boolean isPrivileged)756     void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
757         if (pcdb == null) {
758             return;
759         }
760         synchronized(mClients) {
761             final PlayMonitorClient pmc = new PlayMonitorClient(pcdb, isPrivileged);
762             if (pmc.init()) {
763                 if (!isPrivileged) {
764                     mHasPublicClients = true;
765                 }
766                 mClients.add(pmc);
767             }
768         }
769     }
770 
unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb)771     void unregisterPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
772         if (pcdb == null) {
773             return;
774         }
775         synchronized(mClients) {
776             final Iterator<PlayMonitorClient> clientIterator = mClients.iterator();
777             boolean hasPublicClients = false;
778             // iterate over the clients to remove the dispatcher to remove, and reevaluate at
779             // the same time if we still have a public client.
780             while (clientIterator.hasNext()) {
781                 PlayMonitorClient pmc = clientIterator.next();
782                 if (pcdb.equals(pmc.mDispatcherCb)) {
783                     pmc.release();
784                     clientIterator.remove();
785                 } else {
786                     if (!pmc.mIsPrivileged) {
787                         hasPublicClients = true;
788                     }
789                 }
790             }
791             mHasPublicClients = hasPublicClients;
792         }
793     }
794 
getActivePlaybackConfigurations(boolean isPrivileged)795     List<AudioPlaybackConfiguration> getActivePlaybackConfigurations(boolean isPrivileged) {
796         synchronized(mPlayers) {
797             if (isPrivileged) {
798                 return new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
799             } else {
800                 final List<AudioPlaybackConfiguration> configsPublic;
801                 synchronized (mPlayerLock) {
802                     configsPublic = anonymizeForPublicConsumption(
803                             new ArrayList<AudioPlaybackConfiguration>(mPlayers.values()));
804                 }
805                 return configsPublic;
806             }
807         }
808     }
809 
810 
811     /**
812      * Inner class to track clients that want to be notified of playback updates
813      */
814     private static final class PlayMonitorClient implements IBinder.DeathRecipient {
815 
816         // can afford to be static because only one PlaybackActivityMonitor ever instantiated
817         static PlaybackActivityMonitor sListenerDeathMonitor;
818 
819         final IPlaybackConfigDispatcher mDispatcherCb;
820         final boolean mIsPrivileged;
821 
822         int mErrorCount = 0;
823         // number of errors after which we don't update this client anymore to not spam the logs
824         static final int MAX_ERRORS = 5;
825 
PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged)826         PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
827             mDispatcherCb = pcdb;
828             mIsPrivileged = isPrivileged;
829         }
830 
binderDied()831         public void binderDied() {
832             Log.w(TAG, "client died");
833             sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb);
834         }
835 
init()836         boolean init() {
837             try {
838                 mDispatcherCb.asBinder().linkToDeath(this, 0);
839                 return true;
840             } catch (RemoteException e) {
841                 Log.w(TAG, "Could not link to client death", e);
842                 return false;
843             }
844         }
845 
release()846         void release() {
847             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
848         }
849     }
850 
851     //=================================================================
852     // Class to handle ducking related operations for a given UID
853     private static final class DuckingManager {
854         private final HashMap<Integer, DuckedApp> mDuckers = new HashMap<Integer, DuckedApp>();
855 
duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck)856         synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck) {
857             if (DEBUG) {  Log.v(TAG, "DuckingManager: duckUid() uid:"+ uid); }
858             if (!mDuckers.containsKey(uid)) {
859                 mDuckers.put(uid, new DuckedApp(uid));
860             }
861             final DuckedApp da = mDuckers.get(uid);
862             for (AudioPlaybackConfiguration apc : apcsToDuck) {
863                 da.addDuck(apc, false /*skipRamp*/);
864             }
865         }
866 
unduckUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players)867         synchronized void unduckUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
868             if (DEBUG) {  Log.v(TAG, "DuckingManager: unduckUid() uid:"+ uid); }
869             final DuckedApp da = mDuckers.remove(uid);
870             if (da == null) {
871                 return;
872             }
873             da.removeUnduckAll(players);
874         }
875 
876         // pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
checkDuck(@onNull AudioPlaybackConfiguration apc)877         synchronized void checkDuck(@NonNull AudioPlaybackConfiguration apc) {
878             if (DEBUG) {  Log.v(TAG, "DuckingManager: checkDuck() player piid:"
879                     + apc.getPlayerInterfaceId()+ " uid:"+ apc.getClientUid()); }
880             final DuckedApp da = mDuckers.get(apc.getClientUid());
881             if (da == null) {
882                 return;
883             }
884             da.addDuck(apc, true /*skipRamp*/);
885         }
886 
dump(PrintWriter pw)887         synchronized void dump(PrintWriter pw) {
888             for (DuckedApp da : mDuckers.values()) {
889                 da.dump(pw);
890             }
891         }
892 
removeReleased(@onNull AudioPlaybackConfiguration apc)893         synchronized void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
894             final int uid = apc.getClientUid();
895             if (DEBUG) {  Log.v(TAG, "DuckingManager: removedReleased() player piid: "
896                     + apc.getPlayerInterfaceId() + " uid:" + uid); }
897             final DuckedApp da = mDuckers.get(uid);
898             if (da == null) {
899                 return;
900             }
901             da.removeReleased(apc);
902         }
903 
904         private static final class DuckedApp {
905             private final int mUid;
906             private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
907 
DuckedApp(int uid)908             DuckedApp(int uid) {
909                 mUid = uid;
910             }
911 
dump(PrintWriter pw)912             void dump(PrintWriter pw) {
913                 pw.print("\t uid:" + mUid + " piids:");
914                 for (int piid : mDuckedPlayers) {
915                     pw.print(" " + piid);
916                 }
917                 pw.println("");
918             }
919 
920             // pre-conditions:
921             //  * apc != null
922             //  * apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
addDuck(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)923             void addDuck(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
924                 final int piid = new Integer(apc.getPlayerInterfaceId());
925                 if (mDuckedPlayers.contains(piid)) {
926                     if (DEBUG) { Log.v(TAG, "player piid:" + piid + " already ducked"); }
927                     return;
928                 }
929                 try {
930                     sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
931                     apc.getPlayerProxy().applyVolumeShaper(
932                             DUCK_VSHAPE,
933                             skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
934                     mDuckedPlayers.add(piid);
935                 } catch (Exception e) {
936                     Log.e(TAG, "Error ducking player piid:" + piid + " uid:" + mUid, e);
937                 }
938             }
939 
removeUnduckAll(HashMap<Integer, AudioPlaybackConfiguration> players)940             void removeUnduckAll(HashMap<Integer, AudioPlaybackConfiguration> players) {
941                 for (int piid : mDuckedPlayers) {
942                     final AudioPlaybackConfiguration apc = players.get(piid);
943                     if (apc != null) {
944                         try {
945                             sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
946                                     + piid)).printLog(TAG));
947                             apc.getPlayerProxy().applyVolumeShaper(
948                                     DUCK_ID,
949                                     VolumeShaper.Operation.REVERSE);
950                         } catch (Exception e) {
951                             Log.e(TAG, "Error unducking player piid:" + piid + " uid:" + mUid, e);
952                         }
953                     } else {
954                         // this piid was in the list of ducked players, but wasn't found
955                         if (DEBUG) {
956                             Log.v(TAG, "Error unducking player piid:" + piid
957                                     + ", player not found for uid " + mUid);
958                         }
959                     }
960                 }
961                 mDuckedPlayers.clear();
962             }
963 
removeReleased(@onNull AudioPlaybackConfiguration apc)964             void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
965                 mDuckedPlayers.remove(new Integer(apc.getPlayerInterfaceId()));
966             }
967         }
968     }
969 
970     //=================================================================
971     // For logging
972     private final static class PlayerEvent extends AudioEventLogger.Event {
973         // only keeping the player interface ID as it uniquely identifies the player in the event
974         final int mPlayerIId;
975         final int mState;
976         final int mDeviceId;
977 
PlayerEvent(int piid, int state, int deviceId)978         PlayerEvent(int piid, int state, int deviceId) {
979             mPlayerIId = piid;
980             mState = state;
981             mDeviceId = deviceId;
982         }
983 
984         @Override
eventToString()985         public String eventToString() {
986             return new StringBuilder("player piid:").append(mPlayerIId).append(" state:")
987                     .append(AudioPlaybackConfiguration.toLogFriendlyPlayerState(mState))
988                     .append(" DeviceId:").append(mDeviceId).toString();
989         }
990     }
991 
992     private final static class PlayerOpPlayAudioEvent extends AudioEventLogger.Event {
993         // only keeping the player interface ID as it uniquely identifies the player in the event
994         final int mPlayerIId;
995         final boolean mHasOp;
996         final int mUid;
997 
PlayerOpPlayAudioEvent(int piid, boolean hasOp, int uid)998         PlayerOpPlayAudioEvent(int piid, boolean hasOp, int uid) {
999             mPlayerIId = piid;
1000             mHasOp = hasOp;
1001             mUid = uid;
1002         }
1003 
1004         @Override
eventToString()1005         public String eventToString() {
1006             return new StringBuilder("player piid:").append(mPlayerIId)
1007                     .append(" has OP_PLAY_AUDIO:").append(mHasOp)
1008                     .append(" in uid:").append(mUid).toString();
1009         }
1010     }
1011 
1012     private final static class NewPlayerEvent extends AudioEventLogger.Event {
1013         private final int mPlayerIId;
1014         private final int mPlayerType;
1015         private final int mClientUid;
1016         private final int mClientPid;
1017         private final AudioAttributes mPlayerAttr;
1018         private final int mSessionId;
1019 
NewPlayerEvent(AudioPlaybackConfiguration apc)1020         NewPlayerEvent(AudioPlaybackConfiguration apc) {
1021             mPlayerIId = apc.getPlayerInterfaceId();
1022             mPlayerType = apc.getPlayerType();
1023             mClientUid = apc.getClientUid();
1024             mClientPid = apc.getClientPid();
1025             mPlayerAttr = apc.getAudioAttributes();
1026             mSessionId = apc.getSessionId();
1027         }
1028 
1029         @Override
eventToString()1030         public String eventToString() {
1031             return new String("new player piid:" + mPlayerIId + " uid/pid:" + mClientUid + "/"
1032                     + mClientPid + " type:"
1033                     + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
1034                     + " attr:" + mPlayerAttr
1035                     + " session:" + mSessionId);
1036         }
1037     }
1038 
1039     private abstract static class VolumeShaperEvent extends AudioEventLogger.Event {
1040         private final int mPlayerIId;
1041         private final boolean mSkipRamp;
1042         private final int mClientUid;
1043         private final int mClientPid;
1044 
getVSAction()1045         abstract String getVSAction();
1046 
VolumeShaperEvent(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)1047         VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
1048             mPlayerIId = apc.getPlayerInterfaceId();
1049             mSkipRamp = skipRamp;
1050             mClientUid = apc.getClientUid();
1051             mClientPid = apc.getClientPid();
1052         }
1053 
1054         @Override
eventToString()1055         public String eventToString() {
1056             return new StringBuilder(getVSAction()).append(" player piid:").append(mPlayerIId)
1057                     .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
1058                     .append(" skip ramp:").append(mSkipRamp).toString();
1059         }
1060     }
1061 
1062     static final class DuckEvent extends VolumeShaperEvent {
1063         @Override
getVSAction()1064         String getVSAction() {
1065             return "ducking";
1066         }
1067 
DuckEvent(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)1068         DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
1069             super(apc, skipRamp);
1070         }
1071     }
1072 
1073     static final class FadeOutEvent extends VolumeShaperEvent {
1074         @Override
getVSAction()1075         String getVSAction() {
1076             return "fading out";
1077         }
1078 
FadeOutEvent(@onNull AudioPlaybackConfiguration apc, boolean skipRamp)1079         FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
1080             super(apc, skipRamp);
1081         }
1082     }
1083 
1084     private static final class AudioAttrEvent extends AudioEventLogger.Event {
1085         private final int mPlayerIId;
1086         private final AudioAttributes mPlayerAttr;
1087 
AudioAttrEvent(int piid, AudioAttributes attr)1088         AudioAttrEvent(int piid, AudioAttributes attr) {
1089             mPlayerIId = piid;
1090             mPlayerAttr = attr;
1091         }
1092 
1093         @Override
eventToString()1094         public String eventToString() {
1095             return new String("player piid:" + mPlayerIId + " new AudioAttributes:" + mPlayerAttr);
1096         }
1097     }
1098 
1099     static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
1100             "playback activity as reported through PlayerBase");
1101 }
1102