• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 com.android.server.media;
18 
19 import android.app.PendingIntent;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ParceledListSlice;
23 import android.media.AudioManager;
24 import android.media.AudioManagerInternal;
25 import android.media.AudioSystem;
26 import android.media.MediaDescription;
27 import android.media.MediaMetadata;
28 import android.media.Rating;
29 import android.media.VolumeProvider;
30 import android.media.session.ISession;
31 import android.media.session.ISessionCallback;
32 import android.media.session.ISessionController;
33 import android.media.session.ISessionControllerCallback;
34 import android.media.session.MediaController;
35 import android.media.session.MediaController.PlaybackInfo;
36 import android.media.session.MediaSession;
37 import android.media.session.ParcelableVolumeInfo;
38 import android.media.session.PlaybackState;
39 import android.media.AudioAttributes;
40 import android.net.Uri;
41 import android.os.Binder;
42 import android.os.Bundle;
43 import android.os.DeadObjectException;
44 import android.os.Handler;
45 import android.os.IBinder;
46 import android.os.Looper;
47 import android.os.Message;
48 import android.os.RemoteException;
49 import android.os.ResultReceiver;
50 import android.os.SystemClock;
51 import android.util.Log;
52 import android.util.Slog;
53 import android.view.KeyEvent;
54 
55 import com.android.server.LocalServices;
56 
57 import java.io.PrintWriter;
58 import java.util.ArrayList;
59 
60 /**
61  * This is the system implementation of a Session. Apps will interact with the
62  * MediaSession wrapper class instead.
63  */
64 public class MediaSessionRecord implements IBinder.DeathRecipient {
65     private static final String TAG = "MediaSessionRecord";
66     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
67 
68     /**
69      * The length of time a session will still be considered active after
70      * pausing in ms.
71      */
72     private static final int ACTIVE_BUFFER = 30000;
73 
74     /**
75      * The amount of time we'll send an assumed volume after the last volume
76      * command before reverting to the last reported volume.
77      */
78     private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
79 
80     private static final int UID_NOT_SET = -1;
81 
82     private final MessageHandler mHandler;
83 
84     private final int mOwnerPid;
85     private final int mOwnerUid;
86     private final int mUserId;
87     private final String mPackageName;
88     private final String mTag;
89     private final ControllerStub mController;
90     private final SessionStub mSession;
91     private final SessionCb mSessionCb;
92     private final MediaSessionService mService;
93 
94     private final Object mLock = new Object();
95     private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
96             new ArrayList<ISessionControllerCallback>();
97 
98     private long mFlags;
99     private PendingIntent mMediaButtonReceiver;
100     private PendingIntent mLaunchIntent;
101 
102     // TransportPerformer fields
103 
104     private Bundle mExtras;
105     private MediaMetadata mMetadata;
106     private PlaybackState mPlaybackState;
107     private ParceledListSlice mQueue;
108     private CharSequence mQueueTitle;
109     private int mRatingType;
110     private long mLastActiveTime;
111     // End TransportPerformer fields
112 
113     // Volume handling fields
114     private AudioAttributes mAudioAttrs;
115     private AudioManager mAudioManager;
116     private AudioManagerInternal mAudioManagerInternal;
117     private int mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
118     private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
119     private int mMaxVolume = 0;
120     private int mCurrentVolume = 0;
121     private int mOptimisticVolume = -1;
122     // End volume handling fields
123 
124     private boolean mIsActive = false;
125     private boolean mDestroyed = false;
126 
127     private int mCallingUid = UID_NOT_SET;
128     private String mCallingPackage;
129 
MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, ISessionCallback cb, String tag, MediaSessionService service, Handler handler)130     public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
131             ISessionCallback cb, String tag, MediaSessionService service, Handler handler) {
132         mOwnerPid = ownerPid;
133         mOwnerUid = ownerUid;
134         mUserId = userId;
135         mPackageName = ownerPackageName;
136         mTag = tag;
137         mController = new ControllerStub();
138         mSession = new SessionStub();
139         mSessionCb = new SessionCb(cb);
140         mService = service;
141         mHandler = new MessageHandler(handler.getLooper());
142         mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
143         mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
144         mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
145     }
146 
147     /**
148      * Get the binder for the {@link MediaSession}.
149      *
150      * @return The session binder apps talk to.
151      */
getSessionBinder()152     public ISession getSessionBinder() {
153         return mSession;
154     }
155 
156     /**
157      * Get the binder for the {@link MediaController}.
158      *
159      * @return The controller binder apps talk to.
160      */
getControllerBinder()161     public ISessionController getControllerBinder() {
162         return mController;
163     }
164 
165     /**
166      * Get the info for this session.
167      *
168      * @return Info that identifies this session.
169      */
getPackageName()170     public String getPackageName() {
171         return mPackageName;
172     }
173 
174     /**
175      * Get the tag for the session.
176      *
177      * @return The session's tag.
178      */
getTag()179     public String getTag() {
180         return mTag;
181     }
182 
183     /**
184      * Get the intent the app set for their media button receiver.
185      *
186      * @return The pending intent set by the app or null.
187      */
getMediaButtonReceiver()188     public PendingIntent getMediaButtonReceiver() {
189         return mMediaButtonReceiver;
190     }
191 
192     /**
193      * Get this session's flags.
194      *
195      * @return The flags for this session.
196      */
getFlags()197     public long getFlags() {
198         return mFlags;
199     }
200 
201     /**
202      * Check if this session has the specified flag.
203      *
204      * @param flag The flag to check.
205      * @return True if this session has that flag set, false otherwise.
206      */
hasFlag(int flag)207     public boolean hasFlag(int flag) {
208         return (mFlags & flag) != 0;
209     }
210 
211     /**
212      * Get the user id this session was created for.
213      *
214      * @return The user id for this session.
215      */
getUserId()216     public int getUserId() {
217         return mUserId;
218     }
219 
220     /**
221      * Check if this session has system priorty and should receive media buttons
222      * before any other sessions.
223      *
224      * @return True if this is a system priority session, false otherwise
225      */
isSystemPriority()226     public boolean isSystemPriority() {
227         return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
228     }
229 
230     /**
231      * Send a volume adjustment to the session owner. Direction must be one of
232      * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
233      * {@link AudioManager#ADJUST_SAME}.
234      *
235      * @param direction The direction to adjust volume in.
236      * @param flags Any of the flags from {@link AudioManager}.
237      * @param packageName The package that made the original volume request.
238      * @param uid The uid that made the original volume request.
239      * @param useSuggested True to use adjustSuggestedStreamVolume instead of
240      *            adjustStreamVolume.
241      */
adjustVolume(int direction, int flags, String packageName, int uid, boolean useSuggested)242     public void adjustVolume(int direction, int flags, String packageName, int uid,
243             boolean useSuggested) {
244         int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
245         if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
246             flags &= ~AudioManager.FLAG_PLAY_SOUND;
247         }
248         if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
249             // Adjust the volume with a handler not to be blocked by other system service.
250             int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
251             postAdjustLocalVolume(stream, direction, flags, packageName, uid, useSuggested,
252                     previousFlagPlaySound);
253         } else {
254             if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
255                 // Nothing to do, the volume cannot be changed
256                 return;
257             }
258             if (direction == AudioManager.ADJUST_TOGGLE_MUTE
259                     || direction == AudioManager.ADJUST_MUTE
260                     || direction == AudioManager.ADJUST_UNMUTE) {
261                 Log.w(TAG, "Muting remote playback is not supported");
262                 return;
263             }
264             mSessionCb.adjustVolume(direction);
265 
266             int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
267             mOptimisticVolume = volumeBefore + direction;
268             mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
269             mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
270             mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
271             if (volumeBefore != mOptimisticVolume) {
272                 pushVolumeUpdate();
273             }
274             mService.notifyRemoteVolumeChanged(flags, this);
275 
276             if (DEBUG) {
277                 Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
278                         + mMaxVolume);
279             }
280         }
281     }
282 
setVolumeTo(int value, int flags, String packageName, int uid)283     public void setVolumeTo(int value, int flags, String packageName, int uid) {
284         if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
285             int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
286             mAudioManagerInternal.setStreamVolumeForUid(stream, value, flags, packageName, uid);
287         } else {
288             if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
289                 // Nothing to do. The volume can't be set directly.
290                 return;
291             }
292             value = Math.max(0, Math.min(value, mMaxVolume));
293             mSessionCb.setVolumeTo(value);
294 
295             int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
296             mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
297             mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
298             mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
299             if (volumeBefore != mOptimisticVolume) {
300                 pushVolumeUpdate();
301             }
302             mService.notifyRemoteVolumeChanged(flags, this);
303 
304             if (DEBUG) {
305                 Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
306                         + mMaxVolume);
307             }
308         }
309     }
310 
311     /**
312      * Check if this session has been set to active by the app.
313      *
314      * @return True if the session is active, false otherwise.
315      */
isActive()316     public boolean isActive() {
317         return mIsActive && !mDestroyed;
318     }
319 
320     /**
321      * Check if the session is currently performing playback. This will also
322      * return true if the session was recently paused.
323      *
324      * @param includeRecentlyActive True if playback that was recently paused
325      *            should count, false if it shouldn't.
326      * @return True if the session is performing playback, false otherwise.
327      */
isPlaybackActive(boolean includeRecentlyActive)328     public boolean isPlaybackActive(boolean includeRecentlyActive) {
329         int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
330         if (MediaSession.isActiveState(state)) {
331             return true;
332         }
333         if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
334             long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
335             if (inactiveTime < ACTIVE_BUFFER) {
336                 return true;
337             }
338         }
339         return false;
340     }
341 
342     /**
343      * Get the type of playback, either local or remote.
344      *
345      * @return The current type of playback.
346      */
getPlaybackType()347     public int getPlaybackType() {
348         return mVolumeType;
349     }
350 
351     /**
352      * Get the local audio stream being used. Only valid if playback type is
353      * local.
354      *
355      * @return The audio stream the session is using.
356      */
getAudioAttributes()357     public AudioAttributes getAudioAttributes() {
358         return mAudioAttrs;
359     }
360 
361     /**
362      * Get the type of volume control. Only valid if playback type is remote.
363      *
364      * @return The volume control type being used.
365      */
getVolumeControl()366     public int getVolumeControl() {
367         return mVolumeControlType;
368     }
369 
370     /**
371      * Get the max volume that can be set. Only valid if playback type is
372      * remote.
373      *
374      * @return The max volume that can be set.
375      */
getMaxVolume()376     public int getMaxVolume() {
377         return mMaxVolume;
378     }
379 
380     /**
381      * Get the current volume for this session. Only valid if playback type is
382      * remote.
383      *
384      * @return The current volume of the remote playback.
385      */
getCurrentVolume()386     public int getCurrentVolume() {
387         return mCurrentVolume;
388     }
389 
390     /**
391      * Get the volume we'd like it to be set to. This is only valid for a short
392      * while after a call to adjust or set volume.
393      *
394      * @return The current optimistic volume or -1.
395      */
getOptimisticVolume()396     public int getOptimisticVolume() {
397         return mOptimisticVolume;
398     }
399 
isTransportControlEnabled()400     public boolean isTransportControlEnabled() {
401         return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
402     }
403 
404     @Override
binderDied()405     public void binderDied() {
406         mService.sessionDied(this);
407     }
408 
409     /**
410      * Finish cleaning up this session, including disconnecting if connected and
411      * removing the death observer from the callback binder.
412      */
onDestroy()413     public void onDestroy() {
414         synchronized (mLock) {
415             if (mDestroyed) {
416                 return;
417             }
418             mDestroyed = true;
419             mHandler.post(MessageHandler.MSG_DESTROYED);
420         }
421     }
422 
getCallback()423     public ISessionCallback getCallback() {
424         return mSessionCb.mCb;
425     }
426 
sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb, int uid, String packageName)427     public void sendMediaButton(KeyEvent ke, int sequenceId,
428             ResultReceiver cb, int uid, String packageName) {
429         updateCallingPackage(uid, packageName);
430         mSessionCb.sendMediaButton(ke, sequenceId, cb);
431     }
432 
dump(PrintWriter pw, String prefix)433     public void dump(PrintWriter pw, String prefix) {
434         pw.println(prefix + mTag + " " + this);
435 
436         final String indent = prefix + "  ";
437         pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
438                 + ", userId=" + mUserId);
439         pw.println(indent + "package=" + mPackageName);
440         pw.println(indent + "launchIntent=" + mLaunchIntent);
441         pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver);
442         pw.println(indent + "active=" + mIsActive);
443         pw.println(indent + "flags=" + mFlags);
444         pw.println(indent + "rating type=" + mRatingType);
445         pw.println(indent + "controllers: " + mControllerCallbacks.size());
446         pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
447         pw.println(indent + "audioAttrs=" + mAudioAttrs);
448         pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
449                 + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
450         pw.println(indent + "metadata:" + getShortMetadataString());
451         pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
452                 + (mQueue == null ? 0 : mQueue.getList().size()));
453     }
454 
455     @Override
toString()456     public String toString() {
457         return mPackageName + "/" + mTag + " (uid=" + mUserId + ")";
458     }
459 
postAdjustLocalVolume(final int stream, final int direction, final int flags, final String packageName, final int uid, final boolean useSuggested, final int previousFlagPlaySound)460     private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
461             final String packageName, final int uid, final boolean useSuggested,
462             final int previousFlagPlaySound) {
463         mHandler.post(new Runnable() {
464             @Override
465             public void run() {
466                 if (useSuggested) {
467                     if (AudioSystem.isStreamActive(stream, 0)) {
468                         mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
469                                 flags, packageName, uid);
470                     } else {
471                         mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
472                                 AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
473                                 flags | previousFlagPlaySound, packageName, uid);
474                     }
475                 } else {
476                     mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
477                             packageName, uid);
478                 }
479             }
480         });
481     }
482 
getShortMetadataString()483     private String getShortMetadataString() {
484         int fields = mMetadata == null ? 0 : mMetadata.size();
485         MediaDescription description = mMetadata == null ? null : mMetadata
486                 .getDescription();
487         return "size=" + fields + ", description=" + description;
488     }
489 
pushPlaybackStateUpdate()490     private void pushPlaybackStateUpdate() {
491         synchronized (mLock) {
492             if (mDestroyed) {
493                 return;
494             }
495             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
496                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
497                 try {
498                     cb.onPlaybackStateChanged(mPlaybackState);
499                 } catch (DeadObjectException e) {
500                     mControllerCallbacks.remove(i);
501                     Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e);
502                 } catch (RemoteException e) {
503                     Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e);
504                 }
505             }
506         }
507     }
508 
pushMetadataUpdate()509     private void pushMetadataUpdate() {
510         synchronized (mLock) {
511             if (mDestroyed) {
512                 return;
513             }
514             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
515                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
516                 try {
517                     cb.onMetadataChanged(mMetadata);
518                 } catch (DeadObjectException e) {
519                     Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e);
520                     mControllerCallbacks.remove(i);
521                 } catch (RemoteException e) {
522                     Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e);
523                 }
524             }
525         }
526     }
527 
pushQueueUpdate()528     private void pushQueueUpdate() {
529         synchronized (mLock) {
530             if (mDestroyed) {
531                 return;
532             }
533             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
534                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
535                 try {
536                     cb.onQueueChanged(mQueue);
537                 } catch (DeadObjectException e) {
538                     mControllerCallbacks.remove(i);
539                     Log.w(TAG, "Removed dead callback in pushQueueUpdate.", e);
540                 } catch (RemoteException e) {
541                     Log.w(TAG, "unexpected exception in pushQueueUpdate.", e);
542                 }
543             }
544         }
545     }
546 
pushQueueTitleUpdate()547     private void pushQueueTitleUpdate() {
548         synchronized (mLock) {
549             if (mDestroyed) {
550                 return;
551             }
552             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
553                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
554                 try {
555                     cb.onQueueTitleChanged(mQueueTitle);
556                 } catch (DeadObjectException e) {
557                     mControllerCallbacks.remove(i);
558                     Log.w(TAG, "Removed dead callback in pushQueueTitleUpdate.", e);
559                 } catch (RemoteException e) {
560                     Log.w(TAG, "unexpected exception in pushQueueTitleUpdate.", e);
561                 }
562             }
563         }
564     }
565 
pushExtrasUpdate()566     private void pushExtrasUpdate() {
567         synchronized (mLock) {
568             if (mDestroyed) {
569                 return;
570             }
571             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
572                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
573                 try {
574                     cb.onExtrasChanged(mExtras);
575                 } catch (DeadObjectException e) {
576                     mControllerCallbacks.remove(i);
577                     Log.w(TAG, "Removed dead callback in pushExtrasUpdate.", e);
578                 } catch (RemoteException e) {
579                     Log.w(TAG, "unexpected exception in pushExtrasUpdate.", e);
580                 }
581             }
582         }
583     }
584 
pushVolumeUpdate()585     private void pushVolumeUpdate() {
586         synchronized (mLock) {
587             if (mDestroyed) {
588                 return;
589             }
590             ParcelableVolumeInfo info = mController.getVolumeAttributes();
591             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
592                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
593                 try {
594                     cb.onVolumeInfoChanged(info);
595                 } catch (DeadObjectException e) {
596                     Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e);
597                 } catch (RemoteException e) {
598                     Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e);
599                 }
600             }
601         }
602     }
603 
pushEvent(String event, Bundle data)604     private void pushEvent(String event, Bundle data) {
605         synchronized (mLock) {
606             if (mDestroyed) {
607                 return;
608             }
609             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
610                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
611                 try {
612                     cb.onEvent(event, data);
613                 } catch (DeadObjectException e) {
614                     Log.w(TAG, "Removing dead callback in pushEvent.", e);
615                     mControllerCallbacks.remove(i);
616                 } catch (RemoteException e) {
617                     Log.w(TAG, "unexpected exception in pushEvent.", e);
618                 }
619             }
620         }
621     }
622 
pushSessionDestroyed()623     private void pushSessionDestroyed() {
624         synchronized (mLock) {
625             // This is the only method that may be (and can only be) called
626             // after the session is destroyed.
627             if (!mDestroyed) {
628                 return;
629             }
630             for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
631                 ISessionControllerCallback cb = mControllerCallbacks.get(i);
632                 try {
633                     cb.onSessionDestroyed();
634                 } catch (DeadObjectException e) {
635                     Log.w(TAG, "Removing dead callback in pushEvent.", e);
636                     mControllerCallbacks.remove(i);
637                 } catch (RemoteException e) {
638                     Log.w(TAG, "unexpected exception in pushEvent.", e);
639                 }
640             }
641             // After notifying clear all listeners
642             mControllerCallbacks.clear();
643         }
644     }
645 
getStateWithUpdatedPosition()646     private PlaybackState getStateWithUpdatedPosition() {
647         PlaybackState state;
648         long duration = -1;
649         synchronized (mLock) {
650             state = mPlaybackState;
651             if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
652                 duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
653             }
654         }
655         PlaybackState result = null;
656         if (state != null) {
657             if (state.getState() == PlaybackState.STATE_PLAYING
658                     || state.getState() == PlaybackState.STATE_FAST_FORWARDING
659                     || state.getState() == PlaybackState.STATE_REWINDING) {
660                 long updateTime = state.getLastPositionUpdateTime();
661                 long currentTime = SystemClock.elapsedRealtime();
662                 if (updateTime > 0) {
663                     long position = (long) (state.getPlaybackSpeed()
664                             * (currentTime - updateTime)) + state.getPosition();
665                     if (duration >= 0 && position > duration) {
666                         position = duration;
667                     } else if (position < 0) {
668                         position = 0;
669                     }
670                     PlaybackState.Builder builder = new PlaybackState.Builder(state);
671                     builder.setState(state.getState(), position, state.getPlaybackSpeed(),
672                             currentTime);
673                     result = builder.build();
674                 }
675             }
676         }
677         return result == null ? state : result;
678     }
679 
getControllerCbIndexForCb(ISessionControllerCallback cb)680     private int getControllerCbIndexForCb(ISessionControllerCallback cb) {
681         IBinder binder = cb.asBinder();
682         for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
683             if (binder.equals(mControllerCallbacks.get(i).asBinder())) {
684                 return i;
685             }
686         }
687         return -1;
688     }
689 
updateCallingPackage()690     private void updateCallingPackage() {
691         updateCallingPackage(UID_NOT_SET, null);
692     }
693 
updateCallingPackage(int uid, String packageName)694     private void updateCallingPackage(int uid, String packageName) {
695         if (uid == UID_NOT_SET) {
696             uid = Binder.getCallingUid();
697         }
698         synchronized (mLock) {
699             if (mCallingUid == UID_NOT_SET || mCallingUid != uid) {
700                 mCallingUid = uid;
701                 mCallingPackage = packageName;
702                 if (mCallingPackage != null) {
703                     return;
704                 }
705                 Context context = mService.getContext();
706                 if (context == null) {
707                     return;
708                 }
709                 String[] packages = context.getPackageManager().getPackagesForUid(uid);
710                 if (packages != null && packages.length > 0) {
711                     mCallingPackage = packages[0];
712                 }
713             }
714         }
715     }
716 
717     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
718         @Override
719         public void run() {
720             boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
721             mOptimisticVolume = -1;
722             if (needUpdate) {
723                 pushVolumeUpdate();
724             }
725         }
726     };
727 
728     private final class SessionStub extends ISession.Stub {
729         @Override
destroy()730         public void destroy() {
731             mService.destroySession(MediaSessionRecord.this);
732         }
733 
734         @Override
sendEvent(String event, Bundle data)735         public void sendEvent(String event, Bundle data) {
736             mHandler.post(MessageHandler.MSG_SEND_EVENT, event,
737                     data == null ? null : new Bundle(data));
738         }
739 
740         @Override
getController()741         public ISessionController getController() {
742             return mController;
743         }
744 
745         @Override
setActive(boolean active)746         public void setActive(boolean active) {
747             mIsActive = active;
748             mService.updateSession(MediaSessionRecord.this);
749             mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
750         }
751 
752         @Override
setFlags(int flags)753         public void setFlags(int flags) {
754             if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
755                 int pid = getCallingPid();
756                 int uid = getCallingUid();
757                 mService.enforcePhoneStatePermission(pid, uid);
758             }
759             mFlags = flags;
760             mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
761         }
762 
763         @Override
setMediaButtonReceiver(PendingIntent pi)764         public void setMediaButtonReceiver(PendingIntent pi) {
765             mMediaButtonReceiver = pi;
766         }
767 
768         @Override
setLaunchPendingIntent(PendingIntent pi)769         public void setLaunchPendingIntent(PendingIntent pi) {
770             mLaunchIntent = pi;
771         }
772 
773         @Override
setMetadata(MediaMetadata metadata)774         public void setMetadata(MediaMetadata metadata) {
775             synchronized (mLock) {
776                 MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata)
777                         .build();
778                 // This is to guarantee that the underlying bundle is unparceled
779                 // before we set it to prevent concurrent reads from throwing an
780                 // exception
781                 if (temp != null) {
782                     temp.size();
783                 }
784                 mMetadata = temp;
785             }
786             mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
787         }
788 
789         @Override
setPlaybackState(PlaybackState state)790         public void setPlaybackState(PlaybackState state) {
791             int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
792             int newState = state == null ? 0 : state.getState();
793             if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
794                 mLastActiveTime = SystemClock.elapsedRealtime();
795             }
796             synchronized (mLock) {
797                 mPlaybackState = state;
798             }
799             mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
800             mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
801         }
802 
803         @Override
setQueue(ParceledListSlice queue)804         public void setQueue(ParceledListSlice queue) {
805             synchronized (mLock) {
806                 mQueue = queue;
807             }
808             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
809         }
810 
811         @Override
setQueueTitle(CharSequence title)812         public void setQueueTitle(CharSequence title) {
813             mQueueTitle = title;
814             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE);
815         }
816 
817         @Override
setExtras(Bundle extras)818         public void setExtras(Bundle extras) {
819             synchronized (mLock) {
820                 mExtras = extras == null ? null : new Bundle(extras);
821             }
822             mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS);
823         }
824 
825         @Override
setRatingType(int type)826         public void setRatingType(int type) {
827             mRatingType = type;
828         }
829 
830         @Override
setCurrentVolume(int volume)831         public void setCurrentVolume(int volume) {
832             mCurrentVolume = volume;
833             mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
834         }
835 
836         @Override
setPlaybackToLocal(AudioAttributes attributes)837         public void setPlaybackToLocal(AudioAttributes attributes) {
838             boolean typeChanged;
839             synchronized (mLock) {
840                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
841                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
842                 if (attributes != null) {
843                     mAudioAttrs = attributes;
844                 } else {
845                     Log.e(TAG, "Received null audio attributes, using existing attributes");
846                 }
847             }
848             if (typeChanged) {
849                 mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
850                 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
851             }
852         }
853 
854         @Override
setPlaybackToRemote(int control, int max)855         public void setPlaybackToRemote(int control, int max) {
856             boolean typeChanged;
857             synchronized (mLock) {
858                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
859                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
860                 mVolumeControlType = control;
861                 mMaxVolume = max;
862             }
863             if (typeChanged) {
864                 mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
865                 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
866             }
867         }
868 
869         @Override
getCallingPackage()870         public String getCallingPackage() {
871             return mCallingPackage;
872         }
873     }
874 
875     class SessionCb {
876         private final ISessionCallback mCb;
877 
SessionCb(ISessionCallback cb)878         public SessionCb(ISessionCallback cb) {
879             mCb = cb;
880         }
881 
sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb)882         public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
883             Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
884             mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
885             try {
886                 mCb.onMediaButton(mediaButtonIntent, sequenceId, cb);
887                 return true;
888             } catch (RemoteException e) {
889                 Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
890             }
891             return false;
892         }
893 
sendCommand(String command, Bundle args, ResultReceiver cb)894         public void sendCommand(String command, Bundle args, ResultReceiver cb) {
895             try {
896                 mCb.onCommand(command, args, cb);
897             } catch (RemoteException e) {
898                 Slog.e(TAG, "Remote failure in sendCommand.", e);
899             }
900         }
901 
sendCustomAction(String action, Bundle args)902         public void sendCustomAction(String action, Bundle args) {
903             try {
904                 mCb.onCustomAction(action, args);
905             } catch (RemoteException e) {
906                 Slog.e(TAG, "Remote failure in sendCustomAction.", e);
907             }
908         }
909 
prepare()910         public void prepare() {
911             try {
912                 mCb.onPrepare();
913             } catch (RemoteException e) {
914                 Slog.e(TAG, "Remote failure in prepare.", e);
915             }
916         }
917 
prepareFromMediaId(String mediaId, Bundle extras)918         public void prepareFromMediaId(String mediaId, Bundle extras) {
919             try {
920                 mCb.onPrepareFromMediaId(mediaId, extras);
921             } catch (RemoteException e) {
922                 Slog.e(TAG, "Remote failure in prepareFromMediaId.", e);
923             }
924         }
925 
prepareFromSearch(String query, Bundle extras)926         public void prepareFromSearch(String query, Bundle extras) {
927             try {
928                 mCb.onPrepareFromSearch(query, extras);
929             } catch (RemoteException e) {
930                 Slog.e(TAG, "Remote failure in prepareFromSearch.", e);
931             }
932         }
933 
prepareFromUri(Uri uri, Bundle extras)934         public void prepareFromUri(Uri uri, Bundle extras) {
935             try {
936                 mCb.onPrepareFromUri(uri, extras);
937             } catch (RemoteException e) {
938                 Slog.e(TAG, "Remote failure in prepareFromUri.", e);
939             }
940         }
941 
play()942         public void play() {
943             try {
944                 mCb.onPlay();
945             } catch (RemoteException e) {
946                 Slog.e(TAG, "Remote failure in play.", e);
947             }
948         }
949 
playFromMediaId(String mediaId, Bundle extras)950         public void playFromMediaId(String mediaId, Bundle extras) {
951             try {
952                 mCb.onPlayFromMediaId(mediaId, extras);
953             } catch (RemoteException e) {
954                 Slog.e(TAG, "Remote failure in playFromMediaId.", e);
955             }
956         }
957 
playFromSearch(String query, Bundle extras)958         public void playFromSearch(String query, Bundle extras) {
959             try {
960                 mCb.onPlayFromSearch(query, extras);
961             } catch (RemoteException e) {
962                 Slog.e(TAG, "Remote failure in playFromSearch.", e);
963             }
964         }
965 
playFromUri(Uri uri, Bundle extras)966         public void playFromUri(Uri uri, Bundle extras) {
967             try {
968                 mCb.onPlayFromUri(uri, extras);
969             } catch (RemoteException e) {
970                 Slog.e(TAG, "Remote failure in playFromUri.", e);
971             }
972         }
973 
skipToTrack(long id)974         public void skipToTrack(long id) {
975             try {
976                 mCb.onSkipToTrack(id);
977             } catch (RemoteException e) {
978                 Slog.e(TAG, "Remote failure in skipToTrack", e);
979             }
980         }
981 
pause()982         public void pause() {
983             try {
984                 mCb.onPause();
985             } catch (RemoteException e) {
986                 Slog.e(TAG, "Remote failure in pause.", e);
987             }
988         }
989 
stop()990         public void stop() {
991             try {
992                 mCb.onStop();
993             } catch (RemoteException e) {
994                 Slog.e(TAG, "Remote failure in stop.", e);
995             }
996         }
997 
next()998         public void next() {
999             try {
1000                 mCb.onNext();
1001             } catch (RemoteException e) {
1002                 Slog.e(TAG, "Remote failure in next.", e);
1003             }
1004         }
1005 
previous()1006         public void previous() {
1007             try {
1008                 mCb.onPrevious();
1009             } catch (RemoteException e) {
1010                 Slog.e(TAG, "Remote failure in previous.", e);
1011             }
1012         }
1013 
fastForward()1014         public void fastForward() {
1015             try {
1016                 mCb.onFastForward();
1017             } catch (RemoteException e) {
1018                 Slog.e(TAG, "Remote failure in fastForward.", e);
1019             }
1020         }
1021 
rewind()1022         public void rewind() {
1023             try {
1024                 mCb.onRewind();
1025             } catch (RemoteException e) {
1026                 Slog.e(TAG, "Remote failure in rewind.", e);
1027             }
1028         }
1029 
seekTo(long pos)1030         public void seekTo(long pos) {
1031             try {
1032                 mCb.onSeekTo(pos);
1033             } catch (RemoteException e) {
1034                 Slog.e(TAG, "Remote failure in seekTo.", e);
1035             }
1036         }
1037 
rate(Rating rating)1038         public void rate(Rating rating) {
1039             try {
1040                 mCb.onRate(rating);
1041             } catch (RemoteException e) {
1042                 Slog.e(TAG, "Remote failure in rate.", e);
1043             }
1044         }
1045 
adjustVolume(int direction)1046         public void adjustVolume(int direction) {
1047             try {
1048                 mCb.onAdjustVolume(direction);
1049             } catch (RemoteException e) {
1050                 Slog.e(TAG, "Remote failure in adjustVolume.", e);
1051             }
1052         }
1053 
setVolumeTo(int value)1054         public void setVolumeTo(int value) {
1055             try {
1056                 mCb.onSetVolumeTo(value);
1057             } catch (RemoteException e) {
1058                 Slog.e(TAG, "Remote failure in setVolumeTo.", e);
1059             }
1060         }
1061     }
1062 
1063     class ControllerStub extends ISessionController.Stub {
1064         @Override
sendCommand(String command, Bundle args, ResultReceiver cb)1065         public void sendCommand(String command, Bundle args, ResultReceiver cb)
1066                 throws RemoteException {
1067             updateCallingPackage();
1068             mSessionCb.sendCommand(command, args, cb);
1069         }
1070 
1071         @Override
sendMediaButton(KeyEvent mediaButtonIntent)1072         public boolean sendMediaButton(KeyEvent mediaButtonIntent) {
1073             updateCallingPackage();
1074             return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
1075         }
1076 
1077         @Override
registerCallbackListener(ISessionControllerCallback cb)1078         public void registerCallbackListener(ISessionControllerCallback cb) {
1079             synchronized (mLock) {
1080                 // If this session is already destroyed tell the caller and
1081                 // don't add them.
1082                 if (mDestroyed) {
1083                     try {
1084                         cb.onSessionDestroyed();
1085                     } catch (Exception e) {
1086                         // ignored
1087                     }
1088                     return;
1089                 }
1090                 if (getControllerCbIndexForCb(cb) < 0) {
1091                     mControllerCallbacks.add(cb);
1092                     if (DEBUG) {
1093                         Log.d(TAG, "registering controller callback " + cb);
1094                     }
1095                 }
1096             }
1097         }
1098 
1099         @Override
unregisterCallbackListener(ISessionControllerCallback cb)1100         public void unregisterCallbackListener(ISessionControllerCallback cb)
1101                 throws RemoteException {
1102             synchronized (mLock) {
1103                 int index = getControllerCbIndexForCb(cb);
1104                 if (index != -1) {
1105                     mControllerCallbacks.remove(index);
1106                 }
1107                 if (DEBUG) {
1108                     Log.d(TAG, "unregistering callback " + cb + ". index=" + index);
1109                 }
1110             }
1111         }
1112 
1113         @Override
getPackageName()1114         public String getPackageName() {
1115             return mPackageName;
1116         }
1117 
1118         @Override
getTag()1119         public String getTag() {
1120             return mTag;
1121         }
1122 
1123         @Override
getLaunchPendingIntent()1124         public PendingIntent getLaunchPendingIntent() {
1125             return mLaunchIntent;
1126         }
1127 
1128         @Override
getFlags()1129         public long getFlags() {
1130             return mFlags;
1131         }
1132 
1133         @Override
getVolumeAttributes()1134         public ParcelableVolumeInfo getVolumeAttributes() {
1135             int volumeType;
1136             AudioAttributes attributes;
1137             synchronized (mLock) {
1138                 if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
1139                     int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
1140                     return new ParcelableVolumeInfo(
1141                             mVolumeType, mAudioAttrs, mVolumeControlType, mMaxVolume, current);
1142                 }
1143                 volumeType = mVolumeType;
1144                 attributes = mAudioAttrs;
1145             }
1146             int stream = AudioAttributes.toLegacyStreamType(attributes);
1147             int max = mAudioManager.getStreamMaxVolume(stream);
1148             int current = mAudioManager.getStreamVolume(stream);
1149             return new ParcelableVolumeInfo(
1150                     volumeType, attributes, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max, current);
1151         }
1152 
1153         @Override
adjustVolume(int direction, int flags, String packageName)1154         public void adjustVolume(int direction, int flags, String packageName) {
1155             updateCallingPackage();
1156             int uid = Binder.getCallingUid();
1157             final long token = Binder.clearCallingIdentity();
1158             try {
1159                 MediaSessionRecord.this.adjustVolume(direction, flags, packageName, uid, false);
1160             } finally {
1161                 Binder.restoreCallingIdentity(token);
1162             }
1163         }
1164 
1165         @Override
setVolumeTo(int value, int flags, String packageName)1166         public void setVolumeTo(int value, int flags, String packageName) {
1167             updateCallingPackage();
1168             int uid = Binder.getCallingUid();
1169             final long token = Binder.clearCallingIdentity();
1170             try {
1171                 MediaSessionRecord.this.setVolumeTo(value, flags, packageName, uid);
1172             } finally {
1173                 Binder.restoreCallingIdentity(token);
1174             }
1175         }
1176 
1177         @Override
prepare()1178         public void prepare() throws RemoteException {
1179             updateCallingPackage();
1180             mSessionCb.prepare();
1181         }
1182 
1183         @Override
prepareFromMediaId(String mediaId, Bundle extras)1184         public void prepareFromMediaId(String mediaId, Bundle extras)
1185                 throws RemoteException {
1186             updateCallingPackage();
1187             mSessionCb.prepareFromMediaId(mediaId, extras);
1188         }
1189 
1190         @Override
prepareFromSearch(String query, Bundle extras)1191         public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
1192             updateCallingPackage();
1193             mSessionCb.prepareFromSearch(query, extras);
1194         }
1195 
1196         @Override
prepareFromUri(Uri uri, Bundle extras)1197         public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
1198             updateCallingPackage();
1199             mSessionCb.prepareFromUri(uri, extras);
1200         }
1201 
1202         @Override
play()1203         public void play() throws RemoteException {
1204             updateCallingPackage();
1205             mSessionCb.play();
1206         }
1207 
1208         @Override
playFromMediaId(String mediaId, Bundle extras)1209         public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
1210             updateCallingPackage();
1211             mSessionCb.playFromMediaId(mediaId, extras);
1212         }
1213 
1214         @Override
playFromSearch(String query, Bundle extras)1215         public void playFromSearch(String query, Bundle extras) throws RemoteException {
1216             updateCallingPackage();
1217             mSessionCb.playFromSearch(query, extras);
1218         }
1219 
1220         @Override
playFromUri(Uri uri, Bundle extras)1221         public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
1222             updateCallingPackage();
1223             mSessionCb.playFromUri(uri, extras);
1224         }
1225 
1226         @Override
skipToQueueItem(long id)1227         public void skipToQueueItem(long id) {
1228             updateCallingPackage();
1229             mSessionCb.skipToTrack(id);
1230         }
1231 
1232         @Override
pause()1233         public void pause() throws RemoteException {
1234             updateCallingPackage();
1235             mSessionCb.pause();
1236         }
1237 
1238         @Override
stop()1239         public void stop() throws RemoteException {
1240             updateCallingPackage();
1241             mSessionCb.stop();
1242         }
1243 
1244         @Override
next()1245         public void next() throws RemoteException {
1246             updateCallingPackage();
1247             mSessionCb.next();
1248         }
1249 
1250         @Override
previous()1251         public void previous() throws RemoteException {
1252             updateCallingPackage();
1253             mSessionCb.previous();
1254         }
1255 
1256         @Override
fastForward()1257         public void fastForward() throws RemoteException {
1258             updateCallingPackage();
1259             mSessionCb.fastForward();
1260         }
1261 
1262         @Override
rewind()1263         public void rewind() throws RemoteException {
1264             updateCallingPackage();
1265             mSessionCb.rewind();
1266         }
1267 
1268         @Override
seekTo(long pos)1269         public void seekTo(long pos) throws RemoteException {
1270             updateCallingPackage();
1271             mSessionCb.seekTo(pos);
1272         }
1273 
1274         @Override
rate(Rating rating)1275         public void rate(Rating rating) throws RemoteException {
1276             updateCallingPackage();
1277             mSessionCb.rate(rating);
1278         }
1279 
1280         @Override
sendCustomAction(String action, Bundle args)1281         public void sendCustomAction(String action, Bundle args)
1282                 throws RemoteException {
1283             updateCallingPackage();
1284             mSessionCb.sendCustomAction(action, args);
1285         }
1286 
1287 
1288         @Override
getMetadata()1289         public MediaMetadata getMetadata() {
1290             synchronized (mLock) {
1291                 return mMetadata;
1292             }
1293         }
1294 
1295         @Override
getPlaybackState()1296         public PlaybackState getPlaybackState() {
1297             return getStateWithUpdatedPosition();
1298         }
1299 
1300         @Override
getQueue()1301         public ParceledListSlice getQueue() {
1302             synchronized (mLock) {
1303                 return mQueue;
1304             }
1305         }
1306 
1307         @Override
getQueueTitle()1308         public CharSequence getQueueTitle() {
1309             return mQueueTitle;
1310         }
1311 
1312         @Override
getExtras()1313         public Bundle getExtras() {
1314             synchronized (mLock) {
1315                 return mExtras;
1316             }
1317         }
1318 
1319         @Override
getRatingType()1320         public int getRatingType() {
1321             return mRatingType;
1322         }
1323 
1324         @Override
isTransportControlEnabled()1325         public boolean isTransportControlEnabled() {
1326             return MediaSessionRecord.this.isTransportControlEnabled();
1327         }
1328     }
1329 
1330     private class MessageHandler extends Handler {
1331         private static final int MSG_UPDATE_METADATA = 1;
1332         private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
1333         private static final int MSG_UPDATE_QUEUE = 3;
1334         private static final int MSG_UPDATE_QUEUE_TITLE = 4;
1335         private static final int MSG_UPDATE_EXTRAS = 5;
1336         private static final int MSG_SEND_EVENT = 6;
1337         private static final int MSG_UPDATE_SESSION_STATE = 7;
1338         private static final int MSG_UPDATE_VOLUME = 8;
1339         private static final int MSG_DESTROYED = 9;
1340 
MessageHandler(Looper looper)1341         public MessageHandler(Looper looper) {
1342             super(looper);
1343         }
1344         @Override
handleMessage(Message msg)1345         public void handleMessage(Message msg) {
1346             switch (msg.what) {
1347                 case MSG_UPDATE_METADATA:
1348                     pushMetadataUpdate();
1349                     break;
1350                 case MSG_UPDATE_PLAYBACK_STATE:
1351                     pushPlaybackStateUpdate();
1352                     break;
1353                 case MSG_UPDATE_QUEUE:
1354                     pushQueueUpdate();
1355                     break;
1356                 case MSG_UPDATE_QUEUE_TITLE:
1357                     pushQueueTitleUpdate();
1358                     break;
1359                 case MSG_UPDATE_EXTRAS:
1360                     pushExtrasUpdate();
1361                     break;
1362                 case MSG_SEND_EVENT:
1363                     pushEvent((String) msg.obj, msg.getData());
1364                     break;
1365                 case MSG_UPDATE_SESSION_STATE:
1366                     // TODO add session state
1367                     break;
1368                 case MSG_UPDATE_VOLUME:
1369                     pushVolumeUpdate();
1370                     break;
1371                 case MSG_DESTROYED:
1372                     pushSessionDestroyed();
1373             }
1374         }
1375 
post(int what)1376         public void post(int what) {
1377             post(what, null);
1378         }
1379 
post(int what, Object obj)1380         public void post(int what, Object obj) {
1381             obtainMessage(what, obj).sendToTarget();
1382         }
1383 
post(int what, Object obj, Bundle data)1384         public void post(int what, Object obj, Bundle data) {
1385             Message msg = obtainMessage(what, obj);
1386             msg.setData(data);
1387             msg.sendToTarget();
1388         }
1389     }
1390 
1391 }
1392