Home | History | Annotate | Download | only in session
      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 android.media.session;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.annotation.RequiresPermission;
     22 import android.annotation.SystemApi;
     23 import android.annotation.SystemService;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.media.AudioManager;
     27 import android.media.IRemoteVolumeController;
     28 import android.media.session.ISessionManager;
     29 import android.os.Handler;
     30 import android.os.IBinder;
     31 import android.os.RemoteException;
     32 import android.os.ResultReceiver;
     33 import android.os.ServiceManager;
     34 import android.os.UserHandle;
     35 import android.service.notification.NotificationListenerService;
     36 import android.util.ArrayMap;
     37 import android.util.Log;
     38 import android.view.KeyEvent;
     39 
     40 import java.util.ArrayList;
     41 import java.util.List;
     42 
     43 /**
     44  * Provides support for interacting with {@link MediaSession media sessions}
     45  * that applications have published to express their ongoing media playback
     46  * state.
     47  *
     48  * @see MediaSession
     49  * @see MediaController
     50  */
     51 @SystemService(Context.MEDIA_SESSION_SERVICE)
     52 public final class MediaSessionManager {
     53     private static final String TAG = "SessionManager";
     54 
     55     /**
     56      * Used by IOnMediaKeyListener to indicate that the media key event isn't handled.
     57      * @hide
     58      */
     59     public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0;
     60 
     61     /**
     62      * Used by IOnMediaKeyListener to indicate that the media key event is handled.
     63      * @hide
     64      */
     65     public static final int RESULT_MEDIA_KEY_HANDLED = 1;
     66 
     67     private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
     68             = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
     69     private final Object mLock = new Object();
     70     private final ISessionManager mService;
     71 
     72     private Context mContext;
     73 
     74     private CallbackImpl mCallback;
     75     private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
     76     private OnMediaKeyListenerImpl mOnMediaKeyListener;
     77 
     78     /**
     79      * @hide
     80      */
     81     public MediaSessionManager(Context context) {
     82         // Consider rewriting like DisplayManagerGlobal
     83         // Decide if we need context
     84         mContext = context;
     85         IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
     86         mService = ISessionManager.Stub.asInterface(b);
     87     }
     88 
     89     /**
     90      * Create a new session in the system and get the binder for it.
     91      *
     92      * @param tag A short name for debugging purposes.
     93      * @return The binder object from the system
     94      * @hide
     95      */
     96     public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
     97             @NonNull String tag, int userId) throws RemoteException {
     98         return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
     99     }
    100 
    101     /**
    102      * Get a list of controllers for all ongoing sessions. The controllers will
    103      * be provided in priority order with the most important controller at index
    104      * 0.
    105      * <p>
    106      * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL
    107      * permission be held by the calling app. You may also retrieve this list if
    108      * your app is an enabled notification listener using the
    109      * {@link NotificationListenerService} APIs, in which case you must pass the
    110      * {@link ComponentName} of your enabled listener.
    111      *
    112      * @param notificationListener The enabled notification listener component.
    113      *            May be null.
    114      * @return A list of controllers for ongoing sessions.
    115      */
    116     public @NonNull List<MediaController> getActiveSessions(
    117             @Nullable ComponentName notificationListener) {
    118         return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
    119     }
    120 
    121     /**
    122      * Get active sessions for a specific user. To retrieve actions for a user
    123      * other than your own you must hold the
    124      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission
    125      * in addition to any other requirements. If you are an enabled notification
    126      * listener you may only get sessions for the users you are enabled for.
    127      *
    128      * @param notificationListener The enabled notification listener component.
    129      *            May be null.
    130      * @param userId The user id to fetch sessions for.
    131      * @return A list of controllers for ongoing sessions.
    132      * @hide
    133      */
    134     public @NonNull List<MediaController> getActiveSessionsForUser(
    135             @Nullable ComponentName notificationListener, int userId) {
    136         ArrayList<MediaController> controllers = new ArrayList<MediaController>();
    137         try {
    138             List<IBinder> binders = mService.getSessions(notificationListener, userId);
    139             int size = binders.size();
    140             for (int i = 0; i < size; i++) {
    141                 MediaController controller = new MediaController(mContext, ISessionController.Stub
    142                         .asInterface(binders.get(i)));
    143                 controllers.add(controller);
    144             }
    145         } catch (RemoteException e) {
    146             Log.e(TAG, "Failed to get active sessions: ", e);
    147         }
    148         return controllers;
    149     }
    150 
    151     /**
    152      * Add a listener to be notified when the list of active sessions
    153      * changes.This requires the
    154      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
    155      * the calling app. You may also retrieve this list if your app is an
    156      * enabled notification listener using the
    157      * {@link NotificationListenerService} APIs, in which case you must pass the
    158      * {@link ComponentName} of your enabled listener. Updates will be posted to
    159      * the thread that registered the listener.
    160      *
    161      * @param sessionListener The listener to add.
    162      * @param notificationListener The enabled notification listener component.
    163      *            May be null.
    164      */
    165     public void addOnActiveSessionsChangedListener(
    166             @NonNull OnActiveSessionsChangedListener sessionListener,
    167             @Nullable ComponentName notificationListener) {
    168         addOnActiveSessionsChangedListener(sessionListener, notificationListener, null);
    169     }
    170 
    171     /**
    172      * Add a listener to be notified when the list of active sessions
    173      * changes.This requires the
    174      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
    175      * the calling app. You may also retrieve this list if your app is an
    176      * enabled notification listener using the
    177      * {@link NotificationListenerService} APIs, in which case you must pass the
    178      * {@link ComponentName} of your enabled listener. Updates will be posted to
    179      * the handler specified or to the caller's thread if the handler is null.
    180      *
    181      * @param sessionListener The listener to add.
    182      * @param notificationListener The enabled notification listener component.
    183      *            May be null.
    184      * @param handler The handler to post events to.
    185      */
    186     public void addOnActiveSessionsChangedListener(
    187             @NonNull OnActiveSessionsChangedListener sessionListener,
    188             @Nullable ComponentName notificationListener, @Nullable Handler handler) {
    189         addOnActiveSessionsChangedListener(sessionListener, notificationListener,
    190                 UserHandle.myUserId(), handler);
    191     }
    192 
    193     /**
    194      * Add a listener to be notified when the list of active sessions
    195      * changes.This requires the
    196      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
    197      * the calling app. You may also retrieve this list if your app is an
    198      * enabled notification listener using the
    199      * {@link NotificationListenerService} APIs, in which case you must pass the
    200      * {@link ComponentName} of your enabled listener.
    201      *
    202      * @param sessionListener The listener to add.
    203      * @param notificationListener The enabled notification listener component.
    204      *            May be null.
    205      * @param userId The userId to listen for changes on.
    206      * @param handler The handler to post updates on.
    207      * @hide
    208      */
    209     public void addOnActiveSessionsChangedListener(
    210             @NonNull OnActiveSessionsChangedListener sessionListener,
    211             @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) {
    212         if (sessionListener == null) {
    213             throw new IllegalArgumentException("listener may not be null");
    214         }
    215         if (handler == null) {
    216             handler = new Handler();
    217         }
    218         synchronized (mLock) {
    219             if (mListeners.get(sessionListener) != null) {
    220                 Log.w(TAG, "Attempted to add session listener twice, ignoring.");
    221                 return;
    222             }
    223             SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener,
    224                     handler);
    225             try {
    226                 mService.addSessionsListener(wrapper.mStub, notificationListener, userId);
    227                 mListeners.put(sessionListener, wrapper);
    228             } catch (RemoteException e) {
    229                 Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e);
    230             }
    231         }
    232     }
    233 
    234     /**
    235      * Stop receiving active sessions updates on the specified listener.
    236      *
    237      * @param listener The listener to remove.
    238      */
    239     public void removeOnActiveSessionsChangedListener(
    240             @NonNull OnActiveSessionsChangedListener listener) {
    241         if (listener == null) {
    242             throw new IllegalArgumentException("listener may not be null");
    243         }
    244         synchronized (mLock) {
    245             SessionsChangedWrapper wrapper = mListeners.remove(listener);
    246             if (wrapper != null) {
    247                 try {
    248                     mService.removeSessionsListener(wrapper.mStub);
    249                 } catch (RemoteException e) {
    250                     Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e);
    251                 } finally {
    252                     wrapper.release();
    253                 }
    254             }
    255         }
    256     }
    257 
    258     /**
    259      * Set the remote volume controller to receive volume updates on. Only for
    260      * use by system UI.
    261      *
    262      * @param rvc The volume controller to receive updates on.
    263      * @hide
    264      */
    265     public void setRemoteVolumeController(IRemoteVolumeController rvc) {
    266         try {
    267             mService.setRemoteVolumeController(rvc);
    268         } catch (RemoteException e) {
    269             Log.e(TAG, "Error in setRemoteVolumeController.", e);
    270         }
    271     }
    272 
    273     /**
    274      * Send a media key event. The receiver will be selected automatically.
    275      *
    276      * @param keyEvent The KeyEvent to send.
    277      * @hide
    278      */
    279     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
    280         dispatchMediaKeyEvent(keyEvent, false);
    281     }
    282 
    283     /**
    284      * Send a media key event. The receiver will be selected automatically.
    285      *
    286      * @param keyEvent The KeyEvent to send.
    287      * @param needWakeLock True if a wake lock should be held while sending the key.
    288      * @hide
    289      */
    290     public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
    291         try {
    292             mService.dispatchMediaKeyEvent(keyEvent, needWakeLock);
    293         } catch (RemoteException e) {
    294             Log.e(TAG, "Failed to send key event.", e);
    295         }
    296     }
    297 
    298     /**
    299      * Send a volume key event. The receiver will be selected automatically.
    300      *
    301      * @param keyEvent The volume KeyEvent to send.
    302      * @param needWakeLock True if a wake lock should be held while sending the key.
    303      * @hide
    304      */
    305     public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
    306         try {
    307             mService.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
    308         } catch (RemoteException e) {
    309             Log.e(TAG, "Failed to send volume key event.", e);
    310         }
    311     }
    312 
    313     /**
    314      * Dispatch an adjust volume request to the system. It will be sent to the
    315      * most relevant audio stream or media session. The direction must be one of
    316      * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
    317      * {@link AudioManager#ADJUST_SAME}.
    318      *
    319      * @param suggestedStream The stream to fall back to if there isn't a
    320      *            relevant stream
    321      * @param direction The direction to adjust volume in.
    322      * @param flags Any flags to include with the volume change.
    323      * @hide
    324      */
    325     public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
    326         try {
    327             mService.dispatchAdjustVolume(suggestedStream, direction, flags);
    328         } catch (RemoteException e) {
    329             Log.e(TAG, "Failed to send adjust volume.", e);
    330         }
    331     }
    332 
    333     /**
    334      * Check if the global priority session is currently active. This can be
    335      * used to decide if media keys should be sent to the session or to the app.
    336      *
    337      * @hide
    338      */
    339     public boolean isGlobalPriorityActive() {
    340         try {
    341             return mService.isGlobalPriorityActive();
    342         } catch (RemoteException e) {
    343             Log.e(TAG, "Failed to check if the global priority is active.", e);
    344         }
    345         return false;
    346     }
    347 
    348     /**
    349      * Set the volume key long-press listener. While the listener is set, the listener
    350      * gets the volume key long-presses instead of changing volume.
    351      *
    352      * <p>System can only have a single volume key long-press listener.
    353      *
    354      * @param listener The volume key long-press listener. {@code null} to reset.
    355      * @param handler The handler on which the listener should be invoked, or {@code null}
    356      *            if the listener should be invoked on the calling thread's looper.
    357      * @hide
    358      */
    359     @SystemApi
    360     @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER)
    361     public void setOnVolumeKeyLongPressListener(
    362             OnVolumeKeyLongPressListener listener, @Nullable Handler handler) {
    363         synchronized (mLock) {
    364             try {
    365                 if (listener == null) {
    366                     mOnVolumeKeyLongPressListener = null;
    367                     mService.setOnVolumeKeyLongPressListener(null);
    368                 } else {
    369                     if (handler == null) {
    370                         handler = new Handler();
    371                     }
    372                     mOnVolumeKeyLongPressListener =
    373                             new OnVolumeKeyLongPressListenerImpl(listener, handler);
    374                     mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener);
    375                 }
    376             } catch (RemoteException e) {
    377                 Log.e(TAG, "Failed to set volume key long press listener", e);
    378             }
    379         }
    380     }
    381 
    382     /**
    383      * Set the media key listener. While the listener is set, the listener
    384      * gets the media key before any other media sessions but after the global priority session.
    385      * If the listener handles the key (i.e. returns {@code true}),
    386      * other sessions will not get the event.
    387      *
    388      * <p>System can only have a single media key listener.
    389      *
    390      * @param listener The media key listener. {@code null} to reset.
    391      * @param handler The handler on which the listener should be invoked, or {@code null}
    392      *            if the listener should be invoked on the calling thread's looper.
    393      * @hide
    394      */
    395     @SystemApi
    396     @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER)
    397     public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) {
    398         synchronized (mLock) {
    399             try {
    400                 if (listener == null) {
    401                     mOnMediaKeyListener = null;
    402                     mService.setOnMediaKeyListener(null);
    403                 } else {
    404                     if (handler == null) {
    405                         handler = new Handler();
    406                     }
    407                     mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler);
    408                     mService.setOnMediaKeyListener(mOnMediaKeyListener);
    409                 }
    410             } catch (RemoteException e) {
    411                 Log.e(TAG, "Failed to set media key listener", e);
    412             }
    413         }
    414     }
    415 
    416     /**
    417      * Set a {@link Callback}.
    418      *
    419      * <p>System can only have a single callback, and the callback can only be set by
    420      * Bluetooth service process.
    421      *
    422      * @param callback A {@link Callback}. {@code null} to reset.
    423      * @param handler The handler on which the callback should be invoked, or {@code null}
    424      *            if the callback should be invoked on the calling thread's looper.
    425      * @hide
    426      */
    427     public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
    428         synchronized (mLock) {
    429             try {
    430                 if (callback == null) {
    431                     mCallback = null;
    432                     mService.setCallback(null);
    433                 } else {
    434                     if (handler == null) {
    435                         handler = new Handler();
    436                     }
    437                     mCallback = new CallbackImpl(callback, handler);
    438                     mService.setCallback(mCallback);
    439                 }
    440             } catch (RemoteException e) {
    441                 Log.e(TAG, "Failed to set media key callback", e);
    442             }
    443         }
    444     }
    445 
    446     /**
    447      * Listens for changes to the list of active sessions. This can be added
    448      * using {@link #addOnActiveSessionsChangedListener}.
    449      */
    450     public interface OnActiveSessionsChangedListener {
    451         public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
    452     }
    453 
    454     /**
    455      * Listens the volume key long-presses.
    456      * @hide
    457      */
    458     @SystemApi
    459     public interface OnVolumeKeyLongPressListener {
    460         /**
    461          * Called when the volume key is long-pressed.
    462          * <p>This will be called for both down and up events.
    463          */
    464         void onVolumeKeyLongPress(KeyEvent event);
    465     }
    466 
    467     /**
    468      * Listens the media key.
    469      * @hide
    470      */
    471     @SystemApi
    472     public interface OnMediaKeyListener {
    473         /**
    474          * Called when the media key is pressed.
    475          * <p>If the listener consumes the initial down event (i.e. ACTION_DOWN with
    476          * repeat count zero), it must also comsume all following key events.
    477          * (i.e. ACTION_DOWN with repeat count more than zero, and ACTION_UP).
    478          * <p>If it takes more than 1s to return, the key event will be sent to
    479          * other media sessions.
    480          */
    481         boolean onMediaKey(KeyEvent event);
    482     }
    483 
    484     /**
    485      * Callbacks for the media session service.
    486      *
    487      * <p>Called when a media key event is dispatched or the addressed player is changed.
    488      * The addressed player is either the media session or the media button receiver that will
    489      * receive media key events.
    490      * @hide
    491      */
    492     public static abstract class Callback {
    493         /**
    494          * Called when a media key event is dispatched to the media session
    495          * through the media session service.
    496          *
    497          * @param event Dispatched media key event.
    498          * @param sessionToken The media session's token.
    499          */
    500         public abstract void onMediaKeyEventDispatched(KeyEvent event,
    501                 MediaSession.Token sessionToken);
    502 
    503         /**
    504          * Called when a media key event is dispatched to the media button receiver
    505          * through the media session service.
    506          * <p>MediaSessionService may broadcast key events to the media button receiver
    507          * when reviving playback after the media session is released.
    508          *
    509          * @param event Dispatched media key event.
    510          * @param mediaButtonReceiver The media button receiver.
    511          */
    512         public abstract void onMediaKeyEventDispatched(KeyEvent event,
    513                 ComponentName mediaButtonReceiver);
    514 
    515         /**
    516          * Called when the addressed player is changed to a media session.
    517          * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
    518          * {@link #setCallback} if the addressed player exists.
    519          *
    520          * @param sessionToken The media session's token.
    521          */
    522         public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken);
    523 
    524         /**
    525          * Called when the addressed player is changed to the media button receiver.
    526          * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
    527          * {@link #setCallback} if the addressed player exists.
    528          *
    529          * @param mediaButtonReceiver The media button receiver.
    530          */
    531         public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver);
    532     }
    533 
    534     private static final class SessionsChangedWrapper {
    535         private Context mContext;
    536         private OnActiveSessionsChangedListener mListener;
    537         private Handler mHandler;
    538 
    539         public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener,
    540                 Handler handler) {
    541             mContext = context;
    542             mListener = listener;
    543             mHandler = handler;
    544         }
    545 
    546         private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
    547             @Override
    548             public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) {
    549                 if (mHandler != null) {
    550                     mHandler.post(new Runnable() {
    551                         @Override
    552                         public void run() {
    553                             if (mListener != null) {
    554                                 ArrayList<MediaController> controllers
    555                                         = new ArrayList<MediaController>();
    556                                 int size = tokens.size();
    557                                 for (int i = 0; i < size; i++) {
    558                                     controllers.add(new MediaController(mContext, tokens.get(i)));
    559                                 }
    560                                 mListener.onActiveSessionsChanged(controllers);
    561                             }
    562                         }
    563                     });
    564                 }
    565             }
    566         };
    567 
    568         private void release() {
    569             mContext = null;
    570             mListener = null;
    571             mHandler = null;
    572         }
    573     }
    574 
    575     private static final class OnVolumeKeyLongPressListenerImpl
    576             extends IOnVolumeKeyLongPressListener.Stub {
    577         private OnVolumeKeyLongPressListener mListener;
    578         private Handler mHandler;
    579 
    580         public OnVolumeKeyLongPressListenerImpl(
    581                 OnVolumeKeyLongPressListener listener, Handler handler) {
    582             mListener = listener;
    583             mHandler = handler;
    584         }
    585 
    586         @Override
    587         public void onVolumeKeyLongPress(KeyEvent event) {
    588             if (mListener == null || mHandler == null) {
    589                 Log.w(TAG, "Failed to call volume key long-press listener." +
    590                         " Either mListener or mHandler is null");
    591                 return;
    592             }
    593             mHandler.post(new Runnable() {
    594                 @Override
    595                 public void run() {
    596                     mListener.onVolumeKeyLongPress(event);
    597                 }
    598             });
    599         }
    600     }
    601 
    602     private static final class OnMediaKeyListenerImpl extends IOnMediaKeyListener.Stub {
    603         private OnMediaKeyListener mListener;
    604         private Handler mHandler;
    605 
    606         public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) {
    607             mListener = listener;
    608             mHandler = handler;
    609         }
    610 
    611         @Override
    612         public void onMediaKey(KeyEvent event, ResultReceiver result) {
    613             if (mListener == null || mHandler == null) {
    614                 Log.w(TAG, "Failed to call media key listener." +
    615                         " Either mListener or mHandler is null");
    616                 return;
    617             }
    618             mHandler.post(new Runnable() {
    619                 @Override
    620                 public void run() {
    621                     boolean handled = mListener.onMediaKey(event);
    622                     Log.d(TAG, "The media key listener is returned " + handled);
    623                     if (result != null) {
    624                         result.send(
    625                                 handled ? RESULT_MEDIA_KEY_HANDLED : RESULT_MEDIA_KEY_NOT_HANDLED,
    626                                 null);
    627                     }
    628                 }
    629             });
    630         }
    631     }
    632 
    633     private static final class CallbackImpl extends ICallback.Stub {
    634         private final Callback mCallback;
    635         private final Handler mHandler;
    636 
    637         public CallbackImpl(@NonNull Callback callback, @NonNull Handler handler) {
    638             mCallback = callback;
    639             mHandler = handler;
    640         }
    641 
    642         @Override
    643         public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
    644                 MediaSession.Token sessionToken) {
    645             mHandler.post(new Runnable() {
    646                 @Override
    647                 public void run() {
    648                     mCallback.onMediaKeyEventDispatched(event, sessionToken);
    649                 }
    650             });
    651         }
    652 
    653         @Override
    654         public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
    655                 ComponentName mediaButtonReceiver) {
    656             mHandler.post(new Runnable() {
    657                 @Override
    658                 public void run() {
    659                     mCallback.onMediaKeyEventDispatched(event, mediaButtonReceiver);
    660                 }
    661             });
    662         }
    663 
    664         @Override
    665         public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
    666             mHandler.post(new Runnable() {
    667                 @Override
    668                 public void run() {
    669                     mCallback.onAddressedPlayerChanged(sessionToken);
    670                 }
    671             });
    672         }
    673 
    674         @Override
    675         public void onAddressedPlayerChangedToMediaButtonReceiver(
    676                 ComponentName mediaButtonReceiver) {
    677             mHandler.post(new Runnable() {
    678                 @Override
    679                 public void run() {
    680                     mCallback.onAddressedPlayerChanged(mediaButtonReceiver);
    681                 }
    682             });
    683         }
    684     }
    685 }
    686