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