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