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