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