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