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