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