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