Android 關於解決MediaButton學習到的media控制流程程

來源:互聯網
上載者:User

標籤:oid   就是   str   子目錄   目錄   構造   gen   wakelock   exception   

問題背景:話機串連了頭戴式的耳機,在通話過程中短按按鈕是掛斷電話,長按按鈕是通話靜音。客戶需求是把長按改成掛斷功能,短按是靜音功能。

android版本:8.1

在通話中,測試列印資訊,可以看到button的Keycode 是79, 對應著按鍵KEYCODE_HEADSETHOOK。

Phonewindowmanager -->interceptKeyBeforeQueueing() -->case KEYCODE_HEADSETHOOK 

mBroadcastWakeLock.acquire();Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK, new KeyEvent(event));msg.setAsynchronous(true);msg.sendToTarget();

在這裡將message發送出去,在handlemessage裡處理MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK

case MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK:    dispatchMediaKeyWithWakeLock((KeyEvent)msg.obj);    break;

在dispatchMediaKeyWithWakeLock()方法裡,

void dispatchMediaKeyWithWakeLock(KeyEvent event) {    ...    dispatchMediaKeyWithWakeLockToAudioService(event);    ...}

接著在把event傳給了dispatchMediaKeyWithWakeLockToAudioService(event)。

接著調用了

MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(event, true);

繼續傳遞event給了mediasession,MediaSessionLegacyHelper.getHelper(mContext)獲得了一個MediaSessionLegacyHelper對象,接著看MediaSessionLegacyHelper的sendMediaButtonEvent()

public void sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock) {    if (keyEvent == null) {        Log.w(TAG, "Tried to send a null key event. Ignoring.");        return;    }    mSessionManager.dispatchMediaKeyEvent(keyEvent, needWakeLock);    if (DEBUG) {        Log.d(TAG, "dispatched media key " + keyEvent);    }}

又把event傳給了msessionmanager,的dispatchMediaKeyEvent

public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {    try {        mService.dispatchMediaKeyEvent(keyEvent, needWakeLock);    } catch (RemoteException e) {        Log.e(TAG, "Failed to send key event.", e);    }}

這個mService的對象是ISessionManager,發現這個是應用了AIDL的進程通訊方式,ISessionManager只是個介面,它的實作類別是class SessionManagerImpl extends ISessionManager.Stub{},這個SessionManagerImpl是MediaSessionService.java的內部類,MediaSessionService是一個系統服務,控制了很多關於media的功能。

接著看SessionManagerImpl 的dispatchMediaKeyEvent()

@Overridepublic void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {    。。。     if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {        Log.i(TAG, "dispatchMediaKeyEvent: handleVoiceKeyEventLocked");        handleVoiceKeyEventLocked(keyEvent, needWakeLock);     } else {        Log.i(TAG, "dispatchMediaKeyEventLocked");        dispatchMediaKeyEventLocked(keyEvent, needWakeLock);     }    。。。}

 中間省略了一些代碼,在傳遞event的時候,做了個判斷傳遞的方式是否是voicekey, 我們的headset是只有一個按鈕,於是接著走dispatchMediaKeyEventLocked()

private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {    MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();    if (session != null) {        。。。。        // If we don‘t need a wakelock use -1 as the id so we won‘t release it later.session.sendMediaButton(keyEvent,                needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,                mKeyEventReceiver, Process.SYSTEM_UID,                getContext().getPackageName());        。。。。    }}

這裡會把keyevent傳遞個一個session,這個session是什麼呢?我也不知道,應該是類似於一個token之類的,記錄了當前media資訊的一個類MediaSessionRecord.java

進MediaSessionRecord.java看看,其中有許多設定方法,找到sendMediaButton 

public void sendMediaButton(KeyEvent ke, int sequenceId,        ResultReceiver cb, int uid, String packageName) {    updateCallingPackage(uid, packageName);    mSessionCb.sendMediaButton(ke, sequenceId, cb);}

這裡的mSessionCb也是個特殊的類,在這一段,會發現有很多處理序間通訊的痕迹,各種AIDL輸出。

class SessionCb {    private final ISessionCallback mCb;    public SessionCb(ISessionCallback cb) {        mCb = cb;    }    public boolean sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);        mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);        try {            mCb.onMediaButton(mediaButtonIntent, sequenceId, cb);            return true;        } catch (RemoteException e) {            Slog.e(TAG, "Remote failure in sendMediaRequest.", e);        }        return false;    }

在這裡,sendMediaButton又接著把keyevent轉換為一個Intent,傳給了mCb.onMediaButton

這個mCb是個AIDL實現,ISessionCallback是個介面,需要找到真實的繼承它的類,全域搜尋找到

public static class CallbackStub extends ISessionCallback.Stub 實現了這個介面,這裡在MediaSession.java的內部類,看看路徑,會發現MediaSessionRecord.java在framwork/service/子目錄下,而MediaSession.java卻在framwork/base/media/子目錄下,跨進程通訊很明顯必然要用到AIDL。

在onMediaButton裡

@Overridepublic void onMediaButton(Intent mediaButtonIntent, int sequenceNumber,        ResultReceiver cb) {    MediaSession session = mMediaSession.get();    try {        if (session != null) {            session.dispatchMediaButton(mediaButtonIntent);        }    }}

會繼續走dispatchMediaButton

private void dispatchMediaButton(Intent mediaButtonIntent) {    postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);}

postToCallback就把intent傳給了CallbackMessageHandler

在這個handler裡,處理了msg和intent

handlemessage裡

case MSG_MEDIA_BUTTON:    mCallback.onMediaButtonEvent((Intent) msg.obj);    break;

這個mCallback回調,是在建立CallbackMessageHandler的時候傳來的,

public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {    super(looper, null, true);    mCallback = callback;    mCallback.mHandler = this;}

這裡,需要找到是誰調用了構造方法,才能從callback裡找到那個調用onMediaButtonEvent的地方。

全域搜尋之後,就在MediaSession.java裡的setCallback有調用: 

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

這裡就需要再找找誰調用了mediasession的setcallback方法,全域搜尋,發現只要在HeadsetMediaButton.java裡有調用這個方法,並且這裡是屬於一個叫mMediaSessionHandler的handler裡,

case MSG_MEDIA_SESSION_INITIALIZE: {    MediaSession session = new MediaSession(            mContext,            HeadsetMediaButton.class.getSimpleName());    session.setCallback(mSessionCallback);    session.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY            | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);    session.setPlaybackToLocal(AUDIO_ATTRIBUTES);    mSession = session;    break;}

那就是它了,HeadsetMediaButton.class。

在它的構造方法裡,有個發送message的動作,從一開始建立就存在去產生了Mediasession

public HeadsetMediaButton(        Context context,        CallsManager callsManager,        TelecomSystem.SyncRoot lock) {    mContext = context;    mCallsManager = callsManager;    mLock = lock;    // Create a MediaSession but don‘t enable it yet. This is a    // replacement for MediaButtonReceivermMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_INITIALIZE).sendToTarget();}

在setcallback裡設定的是mSessionCallback,於是繼續看它是什麼。

private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {    @Override    public boolean onMediaButtonEvent(Intent intent) {        KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);        Log.v(this, "SessionCallback.onMediaButton()...  event = %s.", event);        if ((event != null) && ((event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) ||                                (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))) {            synchronized (mLock) {                Log.v(this, "SessionCallback: HEADSETHOOK/MEDIA_PLAY_PAUSE");                boolean consumed = handleCallMediaButton(event);                Log.v(this, "==> handleCallMediaButton(): consumed = %b.", consumed);                return consumed;            }        }        return true;    }};

這個回調,有個我們熟悉的方法,onMediaButtonEvent,

在MediaSession裡處理MSG_MEDIA_BUTTON這個case的時候,就是調用了mcallback的onMediaButtonEvent,而這個回調實現對象就是這裡的mSessionCallback,這裡onMediaButtonEvent把intent給處理了,走到handleCallMediaButton。 

private boolean handleCallMediaButton(KeyEvent event) {    if (event.isLongPress()) {        return mCallsManager.onMediaButton(LONG_PRESS);    } else if (event.getAction() == KeyEvent.ACTION_UP) {        // We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always        // return 0.        // Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {            return mCallsManager.onMediaButton(SHORT_PRESS);        }    }    return true;}

這裡走到了CallManager,是的,這是管理通話控制的地方,在callmanager裡

boolean onMediaButton(int type) {    if (hasAnyCalls()) {        Call ringingCall = getFirstCallWithState(CallState.RINGING);        if (HeadsetMediaButton.SHORT_PRESS == type) {            if (ringingCall == null) {                Call callToHangup = getFirstCallWithState(CallState.RINGING, CallState.DIALING,                        CallState.PULLING, CallState.ACTIVE, CallState.ON_HOLD);                Log.addEvent(callToHangup, LogUtils.Events.INFO,                        "media btn short press - end call.");                if (callToHangup != null) {                    callToHangup.disconnect();                    return true;                }            } else {                ringingCall.answer(VideoProfile.STATE_AUDIO_ONLY);                return true;            }        } else if (HeadsetMediaButton.LONG_PRESS == type) {            if (ringingCall != null) {                Log.addEvent(getForegroundCall(),                        LogUtils.Events.INFO, "media btn long press - reject");                ringingCall.reject(false, null);            } else {                Log.addEvent(getForegroundCall(), LogUtils.Events.INFO,                        "media btn long press - mute");                mCallAudioManager.toggleMute();            }            return true;        }    }    return false;}

就是我們要找的地方,短按,和長按的處理,在這裡,SHORT_PRESS 是接聽和掛斷,LONG_PRESS是拒絕和靜音控制。

於是,我們只需要在這裡更改對應if條件下的控制,就能完成短按和長按的客戶定製功能。

總結:整個流程,從按鍵監聽,到走到callmanager裡,饒了很久,中間遇到了很多Mediasession和處理序間通訊知識,這裡只是記錄一下解bug的過程,感覺像猜謎遊戲一樣,這也是一段技術成長的過程。挺有意義的。

 

Android 關於解決MediaButton學習到的media控制流程程

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.