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