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