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