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