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