• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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