前言

本文监听MediaButton是如何分发,以及分发到对应的监听者中。

本想改个名字,后面想都跟MediaSession有关系就延用。

个人流水账,随便跟一下。

正文

接之前《简单跟一下MediaSession源码1》,我们设置过MediaSession.Callback的监听,今天我们介绍按键上下曲的监听。

//MediaSession.Callback的实现
private final MediaSession.Callback mediaSessionCallback = new MediaSession.Callback() {
    @Override
    public boolean onMediaButtonEvent(@NonNull Intent intent) {
        String action = intent.getAction();
        //判断是否有焦点
        if (action.equals(Intent.ACTION_MEDIA_BUTTON) && !isAudioFocusLoss()) {
            //获取keyevent事件
            KeyEvent keyevent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
            int keyCode = keyevent.getKeyCode();
            int keyAction = keyevent.getAction();
            //弹起响应
            if (keyAction == KeyEvent.ACTION_UP) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_MEDIA_NEXT:
                        //下一曲
                        onSkipToNext();
                        return true;
                    case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
                        //上一曲
                        onSkipToPrevious();
                        return true;
                    case KeyEvent.KEYCODE_MEDIA_PAUSE:
                        //暂停
                        return true;
                    case KeyEvent.KEYCODE_MEDIA_PLAY:
                        //播放
                        return true;
                }
            }
        }
        return super.onMediaButtonEvent(intent);
    }
};

这里我们跟一下上一曲KeyEvent.KEYCODE_MEDIA_PREVIOUS,键值为88。

之前分析说过。

MediaSession mMediaSession = new MediaSession(MusicApp.getContext(), TAG);
//设置MediaSession.Callback监听
mMediaSession.setCallback(mediaSessionCallback);

MediaSession.java

frameworks\base\media\java\android\media\session\MediaSession.java
setCallback()
public void setCallback(@Nullable Callback callback) {
    setCallback(callback, null);
}

public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
    synchronized (mLock) {
        if (mCallback != null) {
            mCallback.mCallback.mSession = null;
            mCallback.removeCallbacksAndMessages(null);
        }
        if (callback == null) {
            mCallback = null;
            return;
        }
        if (handler == null) {
            handler = new Handler();
        }
        callback.mSession = this;
        CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),callback);
        mCallback = msgHandler;
    }
}

注意这里的mCallback是CallbackMessageHandler(重写的Handler)对象。同时把callback (MediaSession.Callback对象)传入到mCallback中。

既然传入到Handler中,那自然看handleMessage中的消息处理。

CallbackMessageHandler.handleMessage()
@Override
public void handleMessage(Message msg) {
    //略
    switch (msg.what) {
        case MSG_COMMAND:
            Command cmd = (Command) obj;
            mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
            break;
        case MSG_MEDIA_BUTTON:
            mCallback.onMediaButtonEvent((Intent) obj);
            break;
    //略
    }
}

找到MSG_MEDIA_BUTTON,也就是这里回调了mCallback。

注意这里的mCallback是CallbackMessageHandler中的遍历,也就是上面传入的MediaSession.Callback。

既然知道发送的MSG_MEDIA_BUTTON消息,那就是找到发送的地方即可。

dispatchMediaButton()
void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
    postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
}

经过跟踪,定位到CallbackStub.onMediaButton()中。

MediaSession.CallbackStub

在看CallbackStub.onMediaButton()之前,先看一下CallbackStub类。

CallbackStub比较重要,很多方法都是这里回调的。

 public static class CallbackStub extends ISessionCallback.Stub {
    private WeakReference<MediaSession> mMediaSession;

    public CallbackStub(MediaSession session) {
        mMediaSession = new WeakReference<>(session);
    }
    //略
 }

记住这里传入的mMediaSession。

CallbackStub.onMediaButton()
@Override
public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
        int sequenceNumber, ResultReceiver cb) {
    //获取构造函数中传入的MediaSession
    MediaSession session = mMediaSession.get();
    try {
        if (session != null) {
            //分发Media Button
            session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid),
                    mediaButtonIntent);
        }
    } finally {
        if (cb != null) {
            cb.send(sequenceNumber, null);
        }
    }
}

看何时创建CallbackStub,也就是mMediaSession的初始化。

CallbackStub初始化是在MediaSession构造函数中。

MediaSession()
public MediaSession(@NonNull Context context, @NonNull String tag,
        @Nullable Bundle sessionInfo) {
    //略
    //创建CallbackStub对象
    mCbStub = new CallbackStub(this);
    //创建MediaSessionManager对象
    MediaSessionManager manager = (MediaSessionManager) context
            .getSystemService(Context.MEDIA_SESSION_SERVICE);
    try {
        //这里传入了mCbStub
        mBinder = manager.createSession(mCbStub, tag, sessionInfo);
        mSessionToken = new Token(Process.myUid(), mBinder.getController());
        //创建MediaController对象
        mController = new MediaController(context, mSessionToken);
    } catch (RemoteException e) {
        throw new RuntimeException("Remote error creating session.", e);
    }
}

也就是传入CallbackStub的当前MediaSession对象,也就是上面的mMediaSession。

在创建mBinder时,传入的第一个参数为mCbStub(CallbackStub对象),跟上,这个是重点。

mBinder = manager.createSession(mCbStub, tag, sessionInfo);

MediaSessionManager.java

先看MediaSessionManager中的createSession()

createSession()
@NonNull
public ISession createSession(@NonNull MediaSession.CallbackStub cbStub, @NonNull String tag,
        @Nullable Bundle sessionInfo) {
    //略
    try {
        return mService.createSession(mContext.getPackageName(), cbStub, tag, sessionInfo,
                UserHandle.myUserId());
    } catch (RemoteException e) {
        throw new RuntimeException(e);
    }
}

之前介绍过,mService是SessionManagerImpl在MediaSessionManager的代理对象。

SessionManagerImpl在MediaSessionService.java中,其实就是MediaSessionService跟MediaSessionManager沟通的桥梁。

MediaSessionService.java

frameworks\base\services\core\java\com\android\server\media\MediaSessionService.java

我们看SessionManagerImpl中的createSession()

SessionManagerImpl.createSession()

紧跟上面,传入的mCbStub是第二个参数,也就是ISessionCallback。

我们上面也说了CallbackStub是实现于ISessionCallback。

@Override
public ISession createSession(String packageName, ISessionCallback cb, String tag,
        Bundle sessionInfo, int userId) throws RemoteException {
    final int pid = Binder.getCallingPid();
    final int uid = Binder.getCallingUid();
    final long token = Binder.clearCallingIdentity();
    try {
        enforcePackageName(packageName, uid);
        int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);
        if (cb == null) {
            throw new IllegalArgumentException("Controller callback cannot be null");
        }
        //创建MediaSessionRecordImpl
        //第5个参数为cb
        MediaSessionRecord session = createSessionInternal(
                pid, uid, resolvedUserId, packageName, cb, tag, sessionInfo);
        if (session == null) {
            throw new IllegalStateException("Failed to create a new session record");
        }
        ISession sessionBinder = session.getSessionBinder();
        if (sessionBinder == null) {
            throw new IllegalStateException("Invalid session record");
        }
        return sessionBinder;
    } catch (Exception e) {
        throw e;
    } finally {
        Binder.restoreCallingIdentity(token);
    }
}

上面通过createSessionInternal创建了MediaSessionRecord,然后传回了MediaSessionRecord的Binder。

重点关注createSessionInternal(),注意,这里也传入了cb。

createSessionInternal()
private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
        String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
    synchronized (mLock) {
        int policies = 0;
        if (mCustomMediaSessionPolicyProvider != null) {
            policies = mCustomMediaSessionPolicyProvider.getSessionPoliciesForApplication(
                    callerUid, callerPackageName);
        }
        FullUserRecord user = getFullUserRecordLocked(userId);
        if (user == null) {
            throw new RuntimeException("Session request from invalid user.");
        }
        final int sessionCount = user.mUidToSessionCount.get(callerUid, 0);
        if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID
                && !hasMediaControlPermission(callerPid, callerUid)) {
            throw new RuntimeException("Created too many sessions. count="
                    + sessionCount + ")");
        }
        final MediaSessionRecord session;
        try {
            //cb传入了MediaSessionRecord
            session = new MediaSessionRecord(callerPid, callerUid, userId,
                    callerPackageName, cb, tag, sessionInfo, this,
                    mRecordThread.getLooper(), policies);
        } catch (RemoteException e) {
            throw new RuntimeException("Media Session owner died prematurely.", e);
        }
        user.mUidToSessionCount.put(callerUid, sessionCount + 1);
        user.mPriorityStack.addSession(session);
        mHandler.postSessionsChanged(session);
        return session;
    }
}

忽略其他的,暂时关注ISessionCallback对象,传入到MediaSessionRecord中。

MediaSessionRecord.java

MediaSessionRecord()
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
        ISessionCallback cb, String tag, Bundle sessionInfo,
        MediaSessionService service, Looper handlerLooper, int policies)
        throws RemoteException {
    //略
    //ISessionCallback传入到SessionCb
    mSessionCb = new SessionCb(cb);
    //略
}

SessionCb定义在MediaSessionRecord()中。

SessionCb()
class SessionCb {
    private final ISessionCallback mCb;

    SessionCb(ISessionCallback cb) {
        mCb = cb;
    }
    public boolean sendMediaButton(String packageName, int pid, int uid,
                boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
        //略
     }
     public void play(String packageName, int pid, int uid) {
        //略
    }
    //略
}

SessionCb中很多方法,都是跟ISessionCallback接口中定义的方法一样。

CallBackStub的的回调是在这里的。当前我们这里重点关注sendMediaButton()。

这里定义了两个sendMediaButton(),经过但两个最终回调的方法不一样。

看第一个即可。

sendMediaButton()
public boolean sendMediaButton(String packageName, int pid, int uid,
        boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
    try {
        if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
            final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction())
                    + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode());
            mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
                    pid, uid, packageName, reason);
        }
        //asSystemService为true,传入的就是true
        if (asSystemService) {
            //走这里
            mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
                    Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
        } else {
            mCb.onMediaButton(packageName, pid, uid,
                    createMediaButtonIntent(keyEvent), sequenceId, cb);
        }
        return true;
    } catch (RemoteException e) {
    }
    return false;
}

MediaSessionService.java

最后发现sendMediaButton()是在MediaSessionService中的SessionManagerImpl.dispatchMediaKeyEventLocked()调用的。

SessionManagerImpl.dispatchMediaKeyEventLocked()
private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
        boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
    //略
    MediaSessionRecord session = null;
    MediaButtonReceiverHolder mediaButtonReceiverHolder = null;
    //略
    //上面创建的临时对象,都为null
    if (session == null && mediaButtonReceiverHolder == null) {
        //获取当前MediaSessionRecord
        //这里重点关注
        session = (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();
        //已经不为null了
        if (session == null) {
            mediaButtonReceiverHolder =
                    mCurrentFullUserRecord.mLastMediaButtonReceiverHolder;
        }
    }
    //不为null咯
    if (session != null) {
        //为true
        if (needWakeLock) {
            mKeyEventReceiver.acquireWakeLockLocked();
        }
        //调用sendMediaButton发送,这就是调用上面的了
        session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
                needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                mKeyEventReceiver);
    } else if (mediaButtonReceiverHolder != null) {
        //略
    }
}

调用分发的已经知道,但到底分发给谁,就需要看获取MediaSessionRecord的方法。

session = (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();

mCurrentFullUserRecord是FullUserRecord对象。

FullUserRecord.getMediaButtonSessionLocked()
private MediaSessionRecordImpl getMediaButtonSessionLocked() {
    boolean gloobal  = isGlobalPriorityActiveLocked();
    return isGlobalPriorityActiveLocked()
            ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
}

这里要判断是否有全局Session,这个优先级高于其他的。

isGlobalPriorityActiveLocked()判断之前有介绍过,当前没有注册全局MediaSession,跳过。

因此返回的是

mPriorityStack.getMediaButtonSession()

mPriorityStack是MediaSessionStack对象。

MediaSessionStack.java

frameworks\base\services\core\java\com\android\server\media\MediaSessionStack.java
getMediaButtonSession()
public MediaSessionRecordImpl getMediaButtonSession() {
    return mMediaButtonSession;
}

这里返回的mMediaButtonSession是当前能收到Media Button的Session。

所以之前分析最新的mMediaButtonSession就是可以接收到Media Button的回应。

MediaSessionService.java

继续回到上面dispatchMediaKeyEventLocked(),看一下哪里分发按键的。

dispatchMediaKeyEventLocked()是在KeyEventHandler.handleKeyEventLocked()中调用的。

跟上。

KeyEventHandler.handleKeyEventLocked()
void handleKeyEventLocked(String packageName, int pid, int uid,
        boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock,
        String opPackageName, int stream, boolean musicOnly) {
    if (keyEvent.isCanceled()) {
        return;
    }
    int overriddenKeyEvents = 0;
    if (mCustomMediaKeyDispatcher != null
            && mCustomMediaKeyDispatcher.getOverriddenKeyEvents() != null) {
        overriddenKeyEvents = mCustomMediaKeyDispatcher.getOverriddenKeyEvents()
                .get(keyEvent.getKeyCode());
    }
    cancelTrackingIfNeeded(packageName, pid, uid, asSystemService, keyEvent,
            needWakeLock, opPackageName, stream, musicOnly, overriddenKeyEvents);
    //判断是否需要Tracking,
    if (!needTracking(keyEvent, overriddenKeyEvents)) {
        //我们暂时只分析down事件,因此进入这里
        //不是音量类型
        if (mKeyType == KEY_TYPE_VOLUME) {
            dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
                    asSystemService, keyEvent, stream, musicOnly);
        } else {
            //走这里
            dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
                    keyEvent, needWakeLock);
        }
        return;
    }
    //下面的暂时不看,略
}

当前我这只分析down事件,up事件一样流程。

KeyEventHandler.handleMediaKeyEventLocked()

这里调用了上面的handleKeyEventLocked()

 void handleMediaKeyEventLocked(String packageName, int pid, int uid,
         boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
     handleKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, needWakeLock,
             null, 0, false);
 }

接下来看哪里调用了handleMediaKeyEventLocked()。

回调的地方在SessionManagerImpl.dispatchMediaKeyEvent()中。

SessionManagerImpl.dispatchMediaKeyEvent()
@Override
public void dispatchMediaKeyEvent(String packageName, boolean asSystemService,
        KeyEvent keyEvent, boolean needWakeLock) {
    //略
    try {
        //开机引导是否完成
        //之前写过过上下曲收不到就是这个原因
        if (!isUserSetupComplete()) {
            return;
        }
        synchronized (mLock) {
            boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
            //没有全局的Session,而且全局的都是系统进程
            if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
                return;
            }
            //略
            if (isGlobalPriorityActive) {
                dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
                        keyEvent, needWakeLock);
            } else {
                //走这里
                //mMediaKeyEventHandler是KeyEventHandler对象
                mMediaKeyEventHandler.handleMediaKeyEventLocked(packageName, pid, uid,
                        asSystemService, keyEvent, needWakeLock);
            }
        }
    } finally {
        Binder.restoreCallingIdentity(token);
    }
}

我们知道MediaSessionManager中的mSerivce就是SessionManagerImpl的代理。

MediaSessionManager.java

dispatchMediaKeyEventInternal()

这里调用了SessionManagerImpl中的dispatchMediaKeyEvent()

private void dispatchMediaKeyEventInternal(KeyEvent keyEvent, boolean asSystemService,
        boolean needWakeLock) {
    try {
        mService.dispatchMediaKeyEvent(mContext.getPackageName(), asSystemService, keyEvent,needWakeLock);
    } catch (RemoteException e) {
    }
}

在dispatchMediaKeyEventAsSystemService中有调用了dispatchMediaKeyEventInternal()。

dispatchMediaKeyEventAsSystemService()
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void dispatchMediaKeyEventAsSystemService(@NonNull KeyEvent keyEvent) {
    dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/true, /*needWakeLock=*/true);
}

asSystemService这里默认传入的asSystemService就是为true

dispatchMediaKeyEventAsSystemService()的调用是在PhoneFallbackEventHandler()中。

PhoneFallbackEventHandler.java

frameworks\base\core\java\com\android\internal\policy\PhoneFallbackEventHandler.java
handleMediaKeyEvent()
private void handleMediaKeyEvent(KeyEvent keyEvent) {
    getMediaSessionManager().dispatchMediaKeyEventAsSystemService(keyEvent);
}

handleMediaKeyEvent()的调用分down和up,我们这里暂时看down。

onKeyUp()
@UnsupportedAppUsage
boolean onKeyDown(int keyCode, KeyEvent event) {
    final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
    switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_UP:
        case KeyEvent.KEYCODE_VOLUME_DOWN:
        case KeyEvent.KEYCODE_VOLUME_MUTE: {
            //处理音量
            handleVolumeKeyEvent(event);
            return true;
        }
        case KeyEvent.KEYCODE_MEDIA_PLAY:
        case KeyEvent.KEYCODE_MEDIA_PAUSE:
        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
        case KeyEvent.KEYCODE_MUTE:
        case KeyEvent.KEYCODE_HEADSETHOOK:
        case KeyEvent.KEYCODE_MEDIA_STOP:
        case KeyEvent.KEYCODE_MEDIA_NEXT:
        case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
        case KeyEvent.KEYCODE_MEDIA_REWIND:
        case KeyEvent.KEYCODE_MEDIA_RECORD:
        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
        case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
            //这里处理大部分媒体按键
            handleMediaKeyEvent(event);
            return true;
        }
        //略
    }
    return false;
}
dispatchKeyEvent()
public boolean dispatchKeyEvent(KeyEvent event) {
    final int action = event.getAction();
    final int keyCode = event.getKeyCode();
    if (action == KeyEvent.ACTION_DOWN) {
        return onKeyDown(keyCode, event);
    } else {
        return onKeyUp(keyCode, event);
    }
}

dispatchKeyEvent()的调用是在ViewRootImpl.java中,暂时跟到这里。

后续继续跟源码。

参考文章

相关文章

暂无评论

none
暂无评论...