Home | History | Annotate | Download | only in audio
      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 
    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)
    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 
    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 
    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 
    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 
    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.setStreamVolumeIndex(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.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
    215                                     mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
    216                         }
    217                     }
    218                 }
    219             }
    220         }
    221     }
    222 
    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 
    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 
    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
    289     public void playerDeath(int piid) {
    290         releasePlayer(piid, 0);
    291     }
    292 
    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      */
    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      */
    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 
    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
    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
    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
    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
    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 
    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 
    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 
    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 
    621         PlayMonitorClient(IPlaybackConfigDispatcher pcdb, boolean isPrivileged) {
    622             mDispatcherCb = pcdb;
    623             mIsPrivileged = isPrivileged;
    624         }
    625 
    626         public void binderDied() {
    627             Log.w(TAG, "client died");
    628             sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb);
    629         }
    630 
    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 
    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 
    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 
    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
    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 
    682         synchronized void dump(PrintWriter pw) {
    683             for (DuckedApp da : mDuckers.values()) {
    684                 da.dump(pw);
    685             }
    686         }
    687 
    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 
    703             DuckedApp(int uid) {
    704                 mUid = uid;
    705             }
    706 
    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
    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 
    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 
    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 
    772         PlayerEvent(int piid, int state) {
    773             mPlayerIId = piid;
    774             mState = state;
    775         }
    776 
    777         @Override
    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 
    790         PlayerOpPlayAudioEvent(int piid, boolean hasOp, int uid) {
    791             mPlayerIId = piid;
    792             mHasOp = hasOp;
    793             mUid = uid;
    794         }
    795 
    796         @Override
    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 
    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
    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 
    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
    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 
    853         AudioAttrEvent(int piid, AudioAttributes attr) {
    854             mPlayerIId = piid;
    855             mPlayerAttr = attr;
    856         }
    857 
    858         @Override
    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