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.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.os.Binder;
23 import android.os.IBinder;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.io.PrintWriter;
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.Objects;
33 
34 /**
35  * The AudioPlaybackConfiguration class collects the information describing an audio playback
36  * session.
37  */
38 public final class AudioPlaybackConfiguration implements Parcelable {
39     private static final String TAG = new String("AudioPlaybackConfiguration");
40 
41     private static final boolean DEBUG = false;
42 
43     /** @hide */
44     public static final int PLAYER_PIID_INVALID = -1;
45     /** @hide */
46     public static final int PLAYER_PIID_UNASSIGNED = 0;
47     /** @hide */
48     public static final int PLAYER_UPID_INVALID = -1;
49 
50     // information about the implementation
51     /**
52      * @hide
53      * An unknown type of player
54      */
55     @SystemApi
56     public static final int PLAYER_TYPE_UNKNOWN = -1;
57     /**
58      * @hide
59      * Player backed by a java android.media.AudioTrack player
60      */
61     @SystemApi
62     public static final int PLAYER_TYPE_JAM_AUDIOTRACK = 1;
63     /**
64      * @hide
65      * Player backed by a java android.media.MediaPlayer player
66      */
67     @SystemApi
68     public static final int PLAYER_TYPE_JAM_MEDIAPLAYER = 2;
69     /**
70      * @hide
71      * Player backed by a java android.media.SoundPool player
72      */
73     @SystemApi
74     public static final int PLAYER_TYPE_JAM_SOUNDPOOL = 3;
75     /**
76      * @hide
77      * Player backed by a C OpenSL ES AudioPlayer player with a BufferQueue source
78      */
79     @SystemApi
80     public static final int PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE = 11;
81     /**
82      * @hide
83      * Player backed by a C OpenSL ES AudioPlayer player with a URI or FD source
84      */
85     @SystemApi
86     public static final int PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD = 12;
87 
88     /**
89      * @hide
90      * Player backed an AAudio player.
91      * Note this type is not in System API so it will not be returned in public API calls
92      */
93     // TODO unhide for SystemApi, update getPlayerType()
94     public static final int PLAYER_TYPE_AAUDIO = 13;
95 
96     /**
97      * @hide
98      * Player backed a hardware source, whose state is visible in the Android audio policy manager.
99      * Note this type is not in System API so it will not be returned in public API calls
100      */
101     // TODO unhide for SystemApi, update getPlayerType()
102     public static final int PLAYER_TYPE_HW_SOURCE = 14;
103 
104     /**
105      * @hide
106      * Player is a proxy for an audio player whose audio and state doesn't go through the Android
107      * audio framework.
108      * Note this type is not in System API so it will not be returned in public API calls
109      */
110     // TODO unhide for SystemApi, update getPlayerType()
111     public static final int PLAYER_TYPE_EXTERNAL_PROXY = 15;
112 
113     /** @hide */
114     @IntDef({
115         PLAYER_TYPE_UNKNOWN,
116         PLAYER_TYPE_JAM_AUDIOTRACK,
117         PLAYER_TYPE_JAM_MEDIAPLAYER,
118         PLAYER_TYPE_JAM_SOUNDPOOL,
119         PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE,
120         PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD,
121     })
122     @Retention(RetentionPolicy.SOURCE)
123     public @interface PlayerType {}
124 
125     /**
126      * @hide
127      * An unknown player state
128      */
129     @SystemApi
130     public static final int PLAYER_STATE_UNKNOWN = -1;
131     /**
132      * @hide
133      * The resources of the player have been released, it cannot play anymore
134      */
135     @SystemApi
136     public static final int PLAYER_STATE_RELEASED = 0;
137     /**
138      * @hide
139      * The state of a player when it's created
140      */
141     @SystemApi
142     public static final int PLAYER_STATE_IDLE = 1;
143     /**
144      * @hide
145      * The state of a player that is actively playing
146      */
147     @SystemApi
148     public static final int PLAYER_STATE_STARTED = 2;
149     /**
150      * @hide
151      * The state of a player where playback is paused
152      */
153     @SystemApi
154     public static final int PLAYER_STATE_PAUSED = 3;
155     /**
156      * @hide
157      * The state of a player where playback is stopped
158      */
159     @SystemApi
160     public static final int PLAYER_STATE_STOPPED = 4;
161 
162     /** @hide */
163     @IntDef({
164         PLAYER_STATE_UNKNOWN,
165         PLAYER_STATE_RELEASED,
166         PLAYER_STATE_IDLE,
167         PLAYER_STATE_STARTED,
168         PLAYER_STATE_PAUSED,
169         PLAYER_STATE_STOPPED
170     })
171     @Retention(RetentionPolicy.SOURCE)
172     public @interface PlayerState {}
173 
174     // immutable data
175     private final int mPlayerIId;
176 
177     // not final due to anonymization step
178     private int mPlayerType;
179     private int mClientUid;
180     private int mClientPid;
181     // the IPlayer reference and death monitor
182     private IPlayerShell mIPlayerShell;
183 
184     private int mPlayerState;
185     private AudioAttributes mPlayerAttr; // never null
186 
187     /**
188      * Never use without initializing parameters afterwards
189      */
AudioPlaybackConfiguration(int piid)190     private AudioPlaybackConfiguration(int piid) {
191         mPlayerIId = piid;
192         mIPlayerShell = null;
193     }
194 
195     /**
196      * @hide
197      */
AudioPlaybackConfiguration(PlayerBase.PlayerIdCard pic, int piid, int uid, int pid)198     public AudioPlaybackConfiguration(PlayerBase.PlayerIdCard pic, int piid, int uid, int pid) {
199         if (DEBUG) { Log.d(TAG, "new: piid=" + piid + " iplayer=" + pic.mIPlayer); }
200         mPlayerIId = piid;
201         mPlayerType = pic.mPlayerType;
202         mClientUid = uid;
203         mClientPid = pid;
204         mPlayerState = PLAYER_STATE_IDLE;
205         mPlayerAttr = pic.mAttributes;
206         if ((sPlayerDeathMonitor != null) && (pic.mIPlayer != null)) {
207             mIPlayerShell = new IPlayerShell(this, pic.mIPlayer);
208         } else {
209             mIPlayerShell = null;
210         }
211     }
212 
213     /**
214      * @hide
215      */
init()216     public void init() {
217         synchronized (this) {
218             if (mIPlayerShell != null) {
219                 mIPlayerShell.monitorDeath();
220             }
221         }
222     }
223 
224     // Note that this method is called server side, so no "privileged" information is ever sent
225     // to a client that is not supposed to have access to it.
226     /**
227      * @hide
228      * Creates a copy of the playback configuration that is stripped of any data enabling
229      * identification of which application it is associated with ("anonymized").
230      * @param toSanitize
231      */
anonymizedCopy(AudioPlaybackConfiguration in)232     public static AudioPlaybackConfiguration anonymizedCopy(AudioPlaybackConfiguration in) {
233         final AudioPlaybackConfiguration anonymCopy = new AudioPlaybackConfiguration(in.mPlayerIId);
234         anonymCopy.mPlayerState = in.mPlayerState;
235         // do not reuse the full attributes: only usage, content type and public flags are allowed
236         anonymCopy.mPlayerAttr = new AudioAttributes.Builder()
237                 .setUsage(in.mPlayerAttr.getUsage())
238                 .setContentType(in.mPlayerAttr.getContentType())
239                 .setFlags(in.mPlayerAttr.getFlags())
240                 .build();
241         // anonymized data
242         anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
243         anonymCopy.mClientUid = PLAYER_UPID_INVALID;
244         anonymCopy.mClientPid = PLAYER_UPID_INVALID;
245         anonymCopy.mIPlayerShell = null;
246         return anonymCopy;
247     }
248 
249     /**
250      * Return the {@link AudioAttributes} of the corresponding player.
251      * @return the audio attributes of the player
252      */
getAudioAttributes()253     public AudioAttributes getAudioAttributes() {
254         return mPlayerAttr;
255     }
256 
257     /**
258      * @hide
259      * Return the uid of the client application that created this player.
260      * @return the uid of the client
261      */
262     @SystemApi
getClientUid()263     public int getClientUid() {
264         return mClientUid;
265     }
266 
267     /**
268      * @hide
269      * Return the pid of the client application that created this player.
270      * @return the pid of the client
271      */
272     @SystemApi
getClientPid()273     public int getClientPid() {
274         return mClientPid;
275     }
276 
277     /**
278      * @hide
279      * Return the type of player linked to this configuration. The return value is one of
280      * {@link #PLAYER_TYPE_JAM_AUDIOTRACK}, {@link #PLAYER_TYPE_JAM_MEDIAPLAYER},
281      * {@link #PLAYER_TYPE_JAM_SOUNDPOOL}, {@link #PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE},
282      * {@link #PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD}, or {@link #PLAYER_TYPE_UNKNOWN}.
283      * <br>Note that player types not exposed in the system API will be represented as
284      * {@link #PLAYER_TYPE_UNKNOWN}.
285      * @return the type of the player.
286      */
287     @SystemApi
getPlayerType()288     public @PlayerType int getPlayerType() {
289         switch (mPlayerType) {
290             case PLAYER_TYPE_AAUDIO:
291             case PLAYER_TYPE_HW_SOURCE:
292             case PLAYER_TYPE_EXTERNAL_PROXY:
293                 return PLAYER_TYPE_UNKNOWN;
294             default:
295                 return mPlayerType;
296         }
297     }
298 
299     /**
300      * @hide
301      * Return the current state of the player linked to this configuration. The return value is one
302      * of {@link #PLAYER_STATE_IDLE}, {@link #PLAYER_STATE_PAUSED}, {@link #PLAYER_STATE_STARTED},
303      * {@link #PLAYER_STATE_STOPPED}, {@link #PLAYER_STATE_RELEASED} or
304      * {@link #PLAYER_STATE_UNKNOWN}.
305      * @return the state of the player.
306      */
307     @SystemApi
getPlayerState()308     public @PlayerState int getPlayerState() {
309         return mPlayerState;
310     }
311 
312     /**
313      * @hide
314      * Return an identifier unique for the lifetime of the player.
315      * @return a player interface identifier
316      */
317     @SystemApi
getPlayerInterfaceId()318     public int getPlayerInterfaceId() {
319         return mPlayerIId;
320     }
321 
322     /**
323      * @hide
324      * Return a proxy for the player associated with this playback configuration
325      * @return a proxy player
326      */
327     @SystemApi
getPlayerProxy()328     public PlayerProxy getPlayerProxy() {
329         final IPlayerShell ips;
330         synchronized (this) {
331             ips = mIPlayerShell;
332         }
333         return ips == null ? null : new PlayerProxy(this);
334     }
335 
336     /**
337      * @hide
338      * @return the IPlayer interface for the associated player
339      */
getIPlayer()340     IPlayer getIPlayer() {
341         final IPlayerShell ips;
342         synchronized (this) {
343             ips = mIPlayerShell;
344         }
345         return ips == null ? null : ips.getIPlayer();
346     }
347 
348     /**
349      * @hide
350      * Handle a change of audio attributes
351      * @param attr
352      */
handleAudioAttributesEvent(@onNull AudioAttributes attr)353     public boolean handleAudioAttributesEvent(@NonNull AudioAttributes attr) {
354         final boolean changed = !attr.equals(mPlayerAttr);
355         mPlayerAttr = attr;
356         return changed;
357     }
358 
359     /**
360      * @hide
361      * Handle a player state change
362      * @param event
363      * @return true if the state changed, false otherwise
364      */
handleStateEvent(int event)365     public boolean handleStateEvent(int event) {
366         final boolean changed;
367         synchronized (this) {
368             changed = (mPlayerState != event);
369             mPlayerState = event;
370             if (changed && (event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) {
371                 mIPlayerShell.release();
372                 mIPlayerShell = null;
373             }
374         }
375         return changed;
376     }
377 
378     // To report IPlayer death from death recipient
379     /** @hide */
380     public interface PlayerDeathMonitor {
playerDeath(int piid)381         public void playerDeath(int piid);
382     }
383     /** @hide */
384     public static PlayerDeathMonitor sPlayerDeathMonitor;
385 
playerDied()386     private void playerDied() {
387         if (sPlayerDeathMonitor != null) {
388             sPlayerDeathMonitor.playerDeath(mPlayerIId);
389         }
390     }
391 
392     /**
393      * @hide
394      * Returns true if the player is considered "active", i.e. actively playing, and thus
395      * in a state that should make it considered for the list public (sanitized) active playback
396      * configurations
397      * @return true if active
398      */
isActive()399     public boolean isActive() {
400         switch (mPlayerState) {
401             case PLAYER_STATE_STARTED:
402                 return true;
403             case PLAYER_STATE_UNKNOWN:
404             case PLAYER_STATE_RELEASED:
405             case PLAYER_STATE_IDLE:
406             case PLAYER_STATE_PAUSED:
407             case PLAYER_STATE_STOPPED:
408             default:
409                 return false;
410         }
411     }
412 
413     /**
414      * @hide
415      * For AudioService dump
416      * @param pw
417      */
dump(PrintWriter pw)418     public void dump(PrintWriter pw) {
419         pw.println("  " + toLogFriendlyString(this));
420     }
421 
422     /**
423      * @hide
424      */
toLogFriendlyString(AudioPlaybackConfiguration apc)425     public static String toLogFriendlyString(AudioPlaybackConfiguration apc) {
426         return new String("ID:" + apc.mPlayerIId
427                 + " -- type:" + toLogFriendlyPlayerType(apc.mPlayerType)
428                 + " -- u/pid:" + apc.mClientUid +"/" + apc.mClientPid
429                 + " -- state:" + toLogFriendlyPlayerState(apc.mPlayerState)
430                 + " -- attr:" + apc.mPlayerAttr);
431     }
432 
433     public static final Parcelable.Creator<AudioPlaybackConfiguration> CREATOR
434             = new Parcelable.Creator<AudioPlaybackConfiguration>() {
435         /**
436          * Rebuilds an AudioPlaybackConfiguration previously stored with writeToParcel().
437          * @param p Parcel object to read the AudioPlaybackConfiguration from
438          * @return a new AudioPlaybackConfiguration created from the data in the parcel
439          */
440         public AudioPlaybackConfiguration createFromParcel(Parcel p) {
441             return new AudioPlaybackConfiguration(p);
442         }
443         public AudioPlaybackConfiguration[] newArray(int size) {
444             return new AudioPlaybackConfiguration[size];
445         }
446     };
447 
448     @Override
hashCode()449     public int hashCode() {
450         return Objects.hash(mPlayerIId, mPlayerType, mClientUid, mClientPid);
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(mPlayerIId);
461         dest.writeInt(mPlayerType);
462         dest.writeInt(mClientUid);
463         dest.writeInt(mClientPid);
464         dest.writeInt(mPlayerState);
465         mPlayerAttr.writeToParcel(dest, 0);
466         final IPlayerShell ips;
467         synchronized (this) {
468             ips = mIPlayerShell;
469         }
470         dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
471     }
472 
AudioPlaybackConfiguration(Parcel in)473     private AudioPlaybackConfiguration(Parcel in) {
474         mPlayerIId = in.readInt();
475         mPlayerType = in.readInt();
476         mClientUid = in.readInt();
477         mClientPid = in.readInt();
478         mPlayerState = in.readInt();
479         mPlayerAttr = AudioAttributes.CREATOR.createFromParcel(in);
480         final IPlayer p = IPlayer.Stub.asInterface(in.readStrongBinder());
481         mIPlayerShell = (p == null) ? null : new IPlayerShell(null, p);
482     }
483 
484     @Override
equals(Object o)485     public boolean equals(Object o) {
486         if (this == o) return true;
487         if (o == null || !(o instanceof AudioPlaybackConfiguration)) return false;
488 
489         AudioPlaybackConfiguration that = (AudioPlaybackConfiguration) o;
490 
491         return ((mPlayerIId == that.mPlayerIId)
492                 && (mPlayerType == that.mPlayerType)
493                 && (mClientUid == that.mClientUid)
494                 && (mClientPid == that.mClientPid));
495     }
496 
497     //=====================================================================
498     // Inner class for corresponding IPlayer and its death monitoring
499     static final class IPlayerShell implements IBinder.DeathRecipient {
500 
501         final AudioPlaybackConfiguration mMonitor; // never null
502         private volatile IPlayer mIPlayer;
503 
IPlayerShell(@onNull AudioPlaybackConfiguration monitor, @NonNull IPlayer iplayer)504         IPlayerShell(@NonNull AudioPlaybackConfiguration monitor, @NonNull IPlayer iplayer) {
505             mMonitor = monitor;
506             mIPlayer = iplayer;
507         }
508 
monitorDeath()509         synchronized void monitorDeath() {
510             if (mIPlayer == null) {
511                 return;
512             }
513             try {
514                 mIPlayer.asBinder().linkToDeath(this, 0);
515             } catch (RemoteException e) {
516                 if (mMonitor != null) {
517                     Log.w(TAG, "Could not link to client death for piid=" + mMonitor.mPlayerIId, e);
518                 } else {
519                     Log.w(TAG, "Could not link to client death", e);
520                 }
521             }
522         }
523 
getIPlayer()524         IPlayer getIPlayer() {
525             return mIPlayer;
526         }
527 
binderDied()528         public void binderDied() {
529             if (mMonitor != null) {
530                 if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied for piid=" + mMonitor.mPlayerIId);}
531                 mMonitor.playerDied();
532             } else if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied"); }
533         }
534 
release()535         synchronized void release() {
536             if (mIPlayer == null) {
537                 return;
538             }
539             mIPlayer.asBinder().unlinkToDeath(this, 0);
540             mIPlayer = null;
541             Binder.flushPendingCommands();
542         }
543     }
544 
545     //=====================================================================
546     // Utilities
547 
548     /** @hide */
toLogFriendlyPlayerType(int type)549     public static String toLogFriendlyPlayerType(int type) {
550         switch (type) {
551             case PLAYER_TYPE_UNKNOWN: return "unknown";
552             case PLAYER_TYPE_JAM_AUDIOTRACK: return "android.media.AudioTrack";
553             case PLAYER_TYPE_JAM_MEDIAPLAYER: return "android.media.MediaPlayer";
554             case PLAYER_TYPE_JAM_SOUNDPOOL:   return "android.media.SoundPool";
555             case PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE:
556                 return "OpenSL ES AudioPlayer (Buffer Queue)";
557             case PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD:
558                 return "OpenSL ES AudioPlayer (URI/FD)";
559             case PLAYER_TYPE_AAUDIO: return "AAudio";
560             case PLAYER_TYPE_HW_SOURCE: return "hardware source";
561             case PLAYER_TYPE_EXTERNAL_PROXY: return "external proxy";
562             default:
563                 return "unknown player type " + type + " - FIXME";
564         }
565     }
566 
567     /** @hide */
toLogFriendlyPlayerState(int state)568     public static String toLogFriendlyPlayerState(int state) {
569         switch (state) {
570             case PLAYER_STATE_UNKNOWN: return "unknown";
571             case PLAYER_STATE_RELEASED: return "released";
572             case PLAYER_STATE_IDLE: return "idle";
573             case PLAYER_STATE_STARTED: return "started";
574             case PLAYER_STATE_PAUSED: return "paused";
575             case PLAYER_STATE_STOPPED: return "stopped";
576             default:
577                 return "unknown player state - FIXME";
578         }
579     }
580 }
581