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