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 android.media; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.ActivityThread; 22 import android.app.AppOpsManager; 23 import android.content.Context; 24 import android.os.IBinder; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.os.Process; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.util.Log; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.app.IAppOpsCallback; 34 import com.android.internal.app.IAppOpsService; 35 36 import java.lang.ref.WeakReference; 37 import java.util.Objects; 38 39 /** 40 * Class to encapsulate a number of common player operations: 41 * - AppOps for OP_PLAY_AUDIO 42 * - more to come (routing, transport control) 43 * @hide 44 */ 45 public abstract class PlayerBase { 46 47 private static final String TAG = "PlayerBase"; 48 /** Debug app ops */ 49 private static final boolean DEBUG_APP_OPS = false; 50 private static final boolean DEBUG = DEBUG_APP_OPS || false; 51 private static IAudioService sService; //lazy initialization, use getService() 52 53 /** if true, only use OP_PLAY_AUDIO monitoring for logging, and rely on muting to happen 54 * in AudioFlinger */ 55 private static final boolean USE_AUDIOFLINGER_MUTING_FOR_OP = true; 56 57 // parameters of the player that affect AppOps 58 protected AudioAttributes mAttributes; 59 60 // volumes of the subclass "player volumes", as seen by the client of the subclass 61 // (e.g. what was passed in AudioTrack.setVolume(float)). The actual volume applied is 62 // the combination of the player volume, and the PlayerBase pan and volume multipliers 63 protected float mLeftVolume = 1.0f; 64 protected float mRightVolume = 1.0f; 65 protected float mAuxEffectSendLevel = 0.0f; 66 67 // NEVER call into AudioService (see getService()) with mLock held: PlayerBase can run in 68 // the same process as AudioService, which can synchronously call back into this class, 69 // causing deadlocks between the two 70 private final Object mLock = new Object(); 71 72 // for AppOps 73 private @Nullable IAppOpsService mAppOps; 74 private @Nullable IAppOpsCallback mAppOpsCallback; 75 @GuardedBy("mLock") 76 private boolean mHasAppOpsPlayAudio = true; 77 78 private final int mImplType; 79 // uniquely identifies the Player Interface throughout the system (P I Id) 80 private int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_INVALID; 81 82 @GuardedBy("mLock") 83 private int mState; 84 @GuardedBy("mLock") 85 private int mStartDelayMs = 0; 86 @GuardedBy("mLock") 87 private float mPanMultiplierL = 1.0f; 88 @GuardedBy("mLock") 89 private float mPanMultiplierR = 1.0f; 90 @GuardedBy("mLock") 91 private float mVolMultiplier = 1.0f; 92 93 /** 94 * Constructor. Must be given audio attributes, as they are required for AppOps. 95 * @param attr non-null audio attributes 96 * @param class non-null class of the implementation of this abstract class 97 */ PlayerBase(@onNull AudioAttributes attr, int implType)98 PlayerBase(@NonNull AudioAttributes attr, int implType) { 99 if (attr == null) { 100 throw new IllegalArgumentException("Illegal null AudioAttributes"); 101 } 102 mAttributes = attr; 103 mImplType = implType; 104 mState = AudioPlaybackConfiguration.PLAYER_STATE_IDLE; 105 }; 106 107 /** 108 * Call from derived class when instantiation / initialization is successful 109 */ baseRegisterPlayer()110 protected void baseRegisterPlayer() { 111 if (!USE_AUDIOFLINGER_MUTING_FOR_OP) { 112 IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); 113 mAppOps = IAppOpsService.Stub.asInterface(b); 114 // initialize mHasAppOpsPlayAudio 115 updateAppOpsPlayAudio(); 116 // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed 117 mAppOpsCallback = new IAppOpsCallbackWrapper(this); 118 try { 119 mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO, 120 ActivityThread.currentPackageName(), mAppOpsCallback); 121 } catch (RemoteException e) { 122 Log.e(TAG, "Error registering appOps callback", e); 123 mHasAppOpsPlayAudio = false; 124 } 125 } 126 try { 127 mPlayerIId = getService().trackPlayer( 128 new PlayerIdCard(mImplType, mAttributes, new IPlayerWrapper(this))); 129 } catch (RemoteException e) { 130 Log.e(TAG, "Error talking to audio service, player will not be tracked", e); 131 } 132 } 133 134 /** 135 * To be called whenever the audio attributes of the player change 136 * @param attr non-null audio attributes 137 */ baseUpdateAudioAttributes(@onNull AudioAttributes attr)138 void baseUpdateAudioAttributes(@NonNull AudioAttributes attr) { 139 if (attr == null) { 140 throw new IllegalArgumentException("Illegal null AudioAttributes"); 141 } 142 try { 143 getService().playerAttributes(mPlayerIId, attr); 144 } catch (RemoteException e) { 145 Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e); 146 } 147 synchronized (mLock) { 148 boolean attributesChanged = (mAttributes != attr); 149 mAttributes = attr; 150 updateAppOpsPlayAudio_sync(attributesChanged); 151 } 152 } 153 updateState(int state)154 private void updateState(int state) { 155 final int piid; 156 synchronized (mLock) { 157 mState = state; 158 piid = mPlayerIId; 159 } 160 try { 161 getService().playerEvent(piid, state); 162 } catch (RemoteException e) { 163 Log.e(TAG, "Error talking to audio service, " 164 + AudioPlaybackConfiguration.toLogFriendlyPlayerState(state) 165 + " state will not be tracked for piid=" + piid, e); 166 } 167 } 168 baseStart()169 void baseStart() { 170 if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); } 171 updateState(AudioPlaybackConfiguration.PLAYER_STATE_STARTED); 172 synchronized (mLock) { 173 if (isRestricted_sync()) { 174 playerSetVolume(true/*muting*/,0, 0); 175 } 176 } 177 } 178 baseSetStartDelayMs(int delayMs)179 void baseSetStartDelayMs(int delayMs) { 180 synchronized(mLock) { 181 mStartDelayMs = Math.max(delayMs, 0); 182 } 183 } 184 getStartDelayMs()185 protected int getStartDelayMs() { 186 synchronized(mLock) { 187 return mStartDelayMs; 188 } 189 } 190 basePause()191 void basePause() { 192 if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); } 193 updateState(AudioPlaybackConfiguration.PLAYER_STATE_PAUSED); 194 } 195 baseStop()196 void baseStop() { 197 if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); } 198 updateState(AudioPlaybackConfiguration.PLAYER_STATE_STOPPED); 199 } 200 baseSetPan(float pan)201 void baseSetPan(float pan) { 202 final float p = Math.min(Math.max(-1.0f, pan), 1.0f); 203 synchronized (mLock) { 204 if (p >= 0.0f) { 205 mPanMultiplierL = 1.0f - p; 206 mPanMultiplierR = 1.0f; 207 } else { 208 mPanMultiplierL = 1.0f; 209 mPanMultiplierR = 1.0f + p; 210 } 211 } 212 updatePlayerVolume(); 213 } 214 updatePlayerVolume()215 private void updatePlayerVolume() { 216 final float finalLeftVol, finalRightVol; 217 final boolean isRestricted; 218 synchronized (mLock) { 219 finalLeftVol = mVolMultiplier * mLeftVolume * mPanMultiplierL; 220 finalRightVol = mVolMultiplier * mRightVolume * mPanMultiplierR; 221 isRestricted = isRestricted_sync(); 222 } 223 playerSetVolume(isRestricted /*muting*/, finalLeftVol, finalRightVol); 224 } 225 setVolumeMultiplier(float vol)226 void setVolumeMultiplier(float vol) { 227 synchronized (mLock) { 228 this.mVolMultiplier = vol; 229 } 230 updatePlayerVolume(); 231 } 232 baseSetVolume(float leftVolume, float rightVolume)233 void baseSetVolume(float leftVolume, float rightVolume) { 234 synchronized (mLock) { 235 mLeftVolume = leftVolume; 236 mRightVolume = rightVolume; 237 } 238 updatePlayerVolume(); 239 } 240 baseSetAuxEffectSendLevel(float level)241 int baseSetAuxEffectSendLevel(float level) { 242 synchronized (mLock) { 243 mAuxEffectSendLevel = level; 244 if (isRestricted_sync()) { 245 return AudioSystem.SUCCESS; 246 } 247 } 248 return playerSetAuxEffectSendLevel(false/*muting*/, level); 249 } 250 251 /** 252 * To be called from a subclass release or finalize method. 253 * Releases AppOps related resources. 254 */ baseRelease()255 void baseRelease() { 256 if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId + " state=" + mState); } 257 boolean releasePlayer = false; 258 synchronized (mLock) { 259 if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) { 260 releasePlayer = true; 261 mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED; 262 } 263 } 264 try { 265 if (releasePlayer) { 266 getService().releasePlayer(mPlayerIId); 267 } 268 } catch (RemoteException e) { 269 Log.e(TAG, "Error talking to audio service, the player will still be tracked", e); 270 } 271 try { 272 if (mAppOps != null) { 273 mAppOps.stopWatchingMode(mAppOpsCallback); 274 } 275 } catch (Exception e) { 276 // nothing to do here, the object is supposed to be released anyway 277 } 278 } 279 updateAppOpsPlayAudio()280 private void updateAppOpsPlayAudio() { 281 synchronized (mLock) { 282 updateAppOpsPlayAudio_sync(false); 283 } 284 } 285 286 /** 287 * To be called whenever a condition that might affect audibility of this player is updated. 288 * Must be called synchronized on mLock. 289 */ updateAppOpsPlayAudio_sync(boolean attributesChanged)290 void updateAppOpsPlayAudio_sync(boolean attributesChanged) { 291 if (USE_AUDIOFLINGER_MUTING_FOR_OP) { 292 return; 293 } 294 boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio; 295 try { 296 int mode = AppOpsManager.MODE_IGNORED; 297 if (mAppOps != null) { 298 mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, 299 mAttributes.getUsage(), 300 Process.myUid(), ActivityThread.currentPackageName()); 301 } 302 mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED); 303 } catch (RemoteException e) { 304 mHasAppOpsPlayAudio = false; 305 } 306 307 // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual 308 // volume used by the player 309 try { 310 if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio || 311 attributesChanged) { 312 getService().playerHasOpPlayAudio(mPlayerIId, mHasAppOpsPlayAudio); 313 if (!isRestricted_sync()) { 314 if (DEBUG_APP_OPS) { 315 Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume 316 + "/" + mRightVolume); 317 } 318 playerSetVolume(false/*muting*/, 319 mLeftVolume * mPanMultiplierL, mRightVolume * mPanMultiplierR); 320 playerSetAuxEffectSendLevel(false/*muting*/, mAuxEffectSendLevel); 321 } else { 322 if (DEBUG_APP_OPS) { 323 Log.v(TAG, "updateAppOpsPlayAudio: muting player"); 324 } 325 playerSetVolume(true/*muting*/, 0.0f, 0.0f); 326 playerSetAuxEffectSendLevel(true/*muting*/, 0.0f); 327 } 328 } 329 } catch (Exception e) { 330 // failing silently, player might not be in right state 331 } 332 } 333 334 /** 335 * To be called by the subclass whenever an operation is potentially restricted. 336 * As the media player-common behavior are incorporated into this class, the subclass's need 337 * to call this method should be removed, and this method could become private. 338 * FIXME can this method be private so subclasses don't have to worry about when to check 339 * the restrictions. 340 * @return 341 */ isRestricted_sync()342 boolean isRestricted_sync() { 343 if (USE_AUDIOFLINGER_MUTING_FOR_OP) { 344 return false; 345 } 346 // check app ops 347 if (mHasAppOpsPlayAudio) { 348 return false; 349 } 350 // check bypass flag 351 if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) { 352 return false; 353 } 354 // check force audibility flag and camera restriction 355 if (((mAttributes.getAllFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED) != 0) 356 && (mAttributes.getUsage() == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)) { 357 boolean cameraSoundForced = false; 358 try { 359 cameraSoundForced = getService().isCameraSoundForced(); 360 } catch (RemoteException e) { 361 Log.e(TAG, "Cannot access AudioService in isRestricted_sync()"); 362 } catch (NullPointerException e) { 363 Log.e(TAG, "Null AudioService in isRestricted_sync()"); 364 } 365 if (cameraSoundForced) { 366 return false; 367 } 368 } 369 return true; 370 } 371 getService()372 private static IAudioService getService() 373 { 374 if (sService != null) { 375 return sService; 376 } 377 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 378 sService = IAudioService.Stub.asInterface(b); 379 return sService; 380 } 381 382 /** 383 * @hide 384 * @param delayMs 385 */ setStartDelayMs(int delayMs)386 public void setStartDelayMs(int delayMs) { 387 baseSetStartDelayMs(delayMs); 388 } 389 390 //===================================================================== 391 // Abstract methods a subclass needs to implement 392 /** 393 * Abstract method for the subclass behavior's for volume and muting commands 394 * @param muting if true, the player is to be muted, and the volume values can be ignored 395 * @param leftVolume the left volume to use if muting is false 396 * @param rightVolume the right volume to use if muting is false 397 */ playerSetVolume(boolean muting, float leftVolume, float rightVolume)398 abstract void playerSetVolume(boolean muting, float leftVolume, float rightVolume); 399 400 /** 401 * Abstract method to apply a {@link VolumeShaper.Configuration} 402 * and a {@link VolumeShaper.Operation} to the Player. 403 * This should be overridden by the Player to call into the native 404 * VolumeShaper implementation. Multiple {@code VolumeShapers} may be 405 * concurrently active for a given Player, each accessible by the 406 * {@code VolumeShaper} id. 407 * 408 * The {@code VolumeShaper} implementation caches the id returned 409 * when applying a fully specified configuration 410 * from {VolumeShaper.Configuration.Builder} to track later 411 * operation changes requested on it. 412 * 413 * @param configuration a {@code VolumeShaper.Configuration} object 414 * created by {@link VolumeShaper.Configuration.Builder} or 415 * an created from a {@code VolumeShaper} id 416 * by the {@link VolumeShaper.Configuration} constructor. 417 * @param operation a {@code VolumeShaper.Operation}. 418 * @return a negative error status or a 419 * non-negative {@code VolumeShaper} id on success. 420 */ playerApplyVolumeShaper( @onNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation)421 /* package */ abstract int playerApplyVolumeShaper( 422 @NonNull VolumeShaper.Configuration configuration, 423 @NonNull VolumeShaper.Operation operation); 424 425 /** 426 * Abstract method to get the current VolumeShaper state. 427 * @param id the {@code VolumeShaper} id returned from 428 * sending a fully specified {@code VolumeShaper.Configuration} 429 * through {@link #playerApplyVolumeShaper} 430 * @return a {@code VolumeShaper.State} object or null if 431 * there is no {@code VolumeShaper} for the id. 432 */ playerGetVolumeShaperState(int id)433 /* package */ abstract @Nullable VolumeShaper.State playerGetVolumeShaperState(int id); 434 playerSetAuxEffectSendLevel(boolean muting, float level)435 abstract int playerSetAuxEffectSendLevel(boolean muting, float level); playerStart()436 abstract void playerStart(); playerPause()437 abstract void playerPause(); playerStop()438 abstract void playerStop(); 439 440 //===================================================================== 441 private static class IAppOpsCallbackWrapper extends IAppOpsCallback.Stub { 442 private final WeakReference<PlayerBase> mWeakPB; 443 IAppOpsCallbackWrapper(PlayerBase pb)444 public IAppOpsCallbackWrapper(PlayerBase pb) { 445 mWeakPB = new WeakReference<PlayerBase>(pb); 446 } 447 448 @Override opChanged(int op, int uid, String packageName)449 public void opChanged(int op, int uid, String packageName) { 450 if (op == AppOpsManager.OP_PLAY_AUDIO) { 451 if (DEBUG_APP_OPS) { Log.v(TAG, "opChanged: op=PLAY_AUDIO pack=" + packageName); } 452 final PlayerBase pb = mWeakPB.get(); 453 if (pb != null) { 454 pb.updateAppOpsPlayAudio(); 455 } 456 } 457 } 458 } 459 460 //===================================================================== 461 /** 462 * Wrapper around an implementation of IPlayer for all subclasses of PlayerBase 463 * that doesn't keep a strong reference on PlayerBase 464 */ 465 private static class IPlayerWrapper extends IPlayer.Stub { 466 private final WeakReference<PlayerBase> mWeakPB; 467 IPlayerWrapper(PlayerBase pb)468 public IPlayerWrapper(PlayerBase pb) { 469 mWeakPB = new WeakReference<PlayerBase>(pb); 470 } 471 472 @Override start()473 public void start() { 474 final PlayerBase pb = mWeakPB.get(); 475 if (pb != null) { 476 pb.playerStart(); 477 } 478 } 479 480 @Override pause()481 public void pause() { 482 final PlayerBase pb = mWeakPB.get(); 483 if (pb != null) { 484 pb.playerPause(); 485 } 486 } 487 488 @Override stop()489 public void stop() { 490 final PlayerBase pb = mWeakPB.get(); 491 if (pb != null) { 492 pb.playerStop(); 493 } 494 } 495 496 @Override setVolume(float vol)497 public void setVolume(float vol) { 498 final PlayerBase pb = mWeakPB.get(); 499 if (pb != null) { 500 pb.setVolumeMultiplier(vol); 501 } 502 } 503 504 @Override setPan(float pan)505 public void setPan(float pan) { 506 final PlayerBase pb = mWeakPB.get(); 507 if (pb != null) { 508 pb.baseSetPan(pan); 509 } 510 } 511 512 @Override setStartDelayMs(int delayMs)513 public void setStartDelayMs(int delayMs) { 514 final PlayerBase pb = mWeakPB.get(); 515 if (pb != null) { 516 pb.baseSetStartDelayMs(delayMs); 517 } 518 } 519 520 @Override applyVolumeShaper( @onNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation)521 public void applyVolumeShaper( 522 @NonNull VolumeShaper.Configuration configuration, 523 @NonNull VolumeShaper.Operation operation) { 524 final PlayerBase pb = mWeakPB.get(); 525 if (pb != null) { 526 pb.playerApplyVolumeShaper(configuration, operation); 527 } 528 } 529 } 530 531 //===================================================================== 532 /** 533 * Class holding all the information about a player that needs to be known at registration time 534 */ 535 public static class PlayerIdCard implements Parcelable { 536 public final int mPlayerType; 537 538 public static final int AUDIO_ATTRIBUTES_NONE = 0; 539 public static final int AUDIO_ATTRIBUTES_DEFINED = 1; 540 public final AudioAttributes mAttributes; 541 public final IPlayer mIPlayer; 542 PlayerIdCard(int type, @NonNull AudioAttributes attr, @NonNull IPlayer iplayer)543 PlayerIdCard(int type, @NonNull AudioAttributes attr, @NonNull IPlayer iplayer) { 544 mPlayerType = type; 545 mAttributes = attr; 546 mIPlayer = iplayer; 547 } 548 549 @Override hashCode()550 public int hashCode() { 551 return Objects.hash(mPlayerType); 552 } 553 554 @Override describeContents()555 public int describeContents() { 556 return 0; 557 } 558 559 @Override writeToParcel(Parcel dest, int flags)560 public void writeToParcel(Parcel dest, int flags) { 561 dest.writeInt(mPlayerType); 562 mAttributes.writeToParcel(dest, 0); 563 dest.writeStrongBinder(mIPlayer == null ? null : mIPlayer.asBinder()); 564 } 565 566 public static final @android.annotation.NonNull Parcelable.Creator<PlayerIdCard> CREATOR 567 = new Parcelable.Creator<PlayerIdCard>() { 568 /** 569 * Rebuilds an PlayerIdCard previously stored with writeToParcel(). 570 * @param p Parcel object to read the PlayerIdCard from 571 * @return a new PlayerIdCard created from the data in the parcel 572 */ 573 public PlayerIdCard createFromParcel(Parcel p) { 574 return new PlayerIdCard(p); 575 } 576 public PlayerIdCard[] newArray(int size) { 577 return new PlayerIdCard[size]; 578 } 579 }; 580 PlayerIdCard(Parcel in)581 private PlayerIdCard(Parcel in) { 582 mPlayerType = in.readInt(); 583 mAttributes = AudioAttributes.CREATOR.createFromParcel(in); 584 // IPlayer can be null if unmarshalling a Parcel coming from who knows where 585 final IBinder b = in.readStrongBinder(); 586 mIPlayer = (b == null ? null : IPlayer.Stub.asInterface(b)); 587 } 588 589 @Override equals(Object o)590 public boolean equals(Object o) { 591 if (this == o) return true; 592 if (o == null || !(o instanceof PlayerIdCard)) return false; 593 594 PlayerIdCard that = (PlayerIdCard) o; 595 596 // FIXME change to the binder player interface once supported as a member 597 return ((mPlayerType == that.mPlayerType) && mAttributes.equals(that.mAttributes)); 598 } 599 } 600 601 //===================================================================== 602 // Utilities 603 604 /** 605 * @hide 606 * Use to generate warning or exception in legacy code paths that allowed passing stream types 607 * to qualify audio playback. 608 * @param streamType the stream type to check 609 * @throws IllegalArgumentException 610 */ deprecateStreamTypeForPlayback(int streamType, @NonNull String className, @NonNull String opName)611 public static void deprecateStreamTypeForPlayback(int streamType, @NonNull String className, 612 @NonNull String opName) throws IllegalArgumentException { 613 // STREAM_ACCESSIBILITY was introduced at the same time the use of stream types 614 // for audio playback was deprecated, so it is not allowed at all to qualify a playback 615 // use case 616 if (streamType == AudioManager.STREAM_ACCESSIBILITY) { 617 throw new IllegalArgumentException("Use of STREAM_ACCESSIBILITY is reserved for " 618 + "volume control"); 619 } 620 Log.w(className, "Use of stream types is deprecated for operations other than " + 621 "volume control"); 622 Log.w(className, "See the documentation of " + opName + " for what to use instead with " + 623 "android.media.AudioAttributes to qualify your playback use case"); 624 } 625 } 626