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