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