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