Android之Bluetooth配对

前言

我们关注蓝牙建立连接的过程
1.通信的建立一定是异步的过程,自然涉及回调
2.如果有回调,一定有一处代码进行分发处理

apk -- jni -- hal 
apk的监听一定来自jni,我们关注jni的注册

解读JniCallbacks

/*
 * Copyright (C) 2012-2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth.btservice;

import android.util.Log;

final class JniCallbacks {

    private RemoteDevices mRemoteDevices;
    private AdapterProperties mAdapterProperties;
    private AdapterState mAdapterStateMachine;
    private BondStateMachine mBondStateMachine;

    JniCallbacks(AdapterState adapterStateMachine,AdapterProperties adapterProperties) {
        mAdapterStateMachine = adapterStateMachine;
        mAdapterProperties = adapterProperties;
    }

    void init(BondStateMachine bondStateMachine, RemoteDevices remoteDevices) {
        mRemoteDevices = remoteDevices;
        mBondStateMachine = bondStateMachine;
    }

    void cleanup() {
        mRemoteDevices = null;
        mAdapterProperties = null;
        mAdapterStateMachine = null;
        mBondStateMachine = null;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
            int passkey) {//蓝牙芯片通知配对确认
        mBondStateMachine.sspRequestCallback(address, name, cod, pairingVariant,
            passkey);
    }
    void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] val) {//用于记录蓝牙设备的数据,主要涉及的类为RemoteDevices.DeviceProperties。例如蓝牙地址 蓝牙uuid
        mRemoteDevices.devicePropertyChangedCallback(address, types, val);
    }

    void deviceFoundCallback(byte[] address) {//扫描设备的回调
        mRemoteDevices.deviceFoundCallback(address);
    }

    void pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits) {
        mBondStateMachine.pinRequestCallback(address, name, cod, min16Digits);
    }

    void bondStateChangeCallback(int status, byte[] address, int newState) {//配对状态更改
        mBondStateMachine.bondStateChangeCallback(status, address, newState);
    }

    void aclStateChangeCallback(int status, byte[] address, int newState) {//配置的设备状态更改,例如远程设备关闭了蓝牙,会通知出来,具体的时间是多久?
        mRemoteDevices.aclStateChangeCallback(status, address, newState);
    }

    void stateChangeCallback(int status) {//本设备的蓝牙打开和关闭通知
        mAdapterStateMachine.stateChangeCallback(status);
    }

    void discoveryStateChangeCallback(int state) {//扫描开始和扫描结束,开始是1 结束是0
        mAdapterProperties.discoveryStateChangeCallback(state);
    }

    void adapterPropertyChangedCallback(int[] types, byte[][] val) {//不是很清楚用来做什么
        mAdapterProperties.adapterPropertyChangedCallback(types, val);
    }

}

场景:配对建立连接过程

设备端:
UI端点击配对 -- 系统服务 -- Bluetooth.apk -- jni -- hal ---->  接受方


接受方:
接受方 -- 发送pin指令进行配对 --> 设备端

设备端
设备端收到指令hal -- jni -- Bluetooth.apk -- UI端接受点击确认
JniCallbacks.sspRequestCallback 接收到回调之后,进行事件分发

案例:蓝牙配对连接
注:
配对的前提,设备已知,代码分析要从设备信息出发

1.UI调用方法
BluetoothDevice.createBond

    public boolean createBond() {
        ······
        try {
            return sService.createBond(this, TRANSPORT_AUTO);//sService为Bluetooth.apk中的服务
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        return false;
    }
    注:
    sService的获取来自系统服务BluetoothManagerService

2.蓝牙app及jni
1)AdapterService.AdapterServiceBinder.createBond

AdapterService.AdapterServiceBinder.createBond --> AdapterService.createBond

     boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
        ······
        // Pairing is unreliable while scanning, so cancel discovery
        // Note, remove this when native stack improves
        cancelDiscoveryNative();//注意配对的时候需要取消扫描

        Message msg = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
        msg.obj = device;
        msg.arg1 = transport;

        if (oobData != null) {
            Bundle oobDataBundle = new Bundle();
            oobDataBundle.putParcelable(BondStateMachine.OOBDATA, oobData);
            msg.setData(oobDataBundle);
        }
        mBondStateMachine.sendMessage(msg);//mBondStateMachine为状态机。要熟悉状态机的运用
        return true;
    }

2)BondStateMachine.StableState接受来自消息CREATE_BOND

mBondStateMachine的默认状态为mStableState
    private BondStateMachine(AdapterService service,
            AdapterProperties prop, RemoteDevices remoteDevices) {
        super("BondStateMachine:");
        addState(mStableState);
        addState(mPendingCommandState);
        ······
        setInitialState(mStableState);
    }
    
    private class StableState extends State {
        ······
        @Override
        public boolean processMessage(Message msg) {
            ·······
            switch(msg.what) {
              ·······
              case CREATE_BOND:
                  OobData oobData = null;
                  if (msg.getData() != null)
                      oobData = msg.getData().getParcelable(OOBDATA);

                  createBond(dev, msg.arg1, oobData, true);
                  break;
              ······
            }
            ······
        }
    }
    
    private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
                               boolean transition) {
        if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
            infoLog("Bond address is:" + dev);
            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
            boolean result;
            if (oobData != null) {
                result = mAdapterService.createBondOutOfBandNative(addr, transport, oobData);
            } else {
                result = mAdapterService.createBondNative(addr, transport);//调用jni进行通信
            }

            if (!result) {
                sendIntent(dev, BluetoothDevice.BOND_NONE,
                           BluetoothDevice.UNBOND_REASON_REMOVED);
                return false;
            } else if (transition) {
                transitionTo(mPendingCommandState);//同步把状态切换成带处理状态(mPendingCommandState)
            }
            return true;
        }
        return false;
    }

3)AdapterService.createBondNative

/*package*/ native boolean createBondNative(byte[] address, int transport);

com_android_bluetooth_btservice_AdapterService.cpp
static jboolean createBondNative(JNIEnv* env, jobject obj, jbyteArray address, jint transport) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr;
    jboolean result = JNI_FALSE;

    if (!sBluetoothInterface) return result;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    int ret = sBluetoothInterface->create_bond((bt_bdaddr_t *)addr, transport);//通过jni调进蓝牙协议栈中
    env->ReleaseByteArrayElements(address, addr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

这里是没有对应的回调传给jni,那自然是jni主动被调用,这样才能把配对的状态告知app

3.蓝牙协议栈--省略

4.Bluetooth.apk接收回调
1)关注JniCallbacks接收回调的地方

    void bondStateChangeCallback(int status, byte[] address, int newState) {//配对状态更改
        mBondStateMachine.bondStateChangeCallback(status, address, newState);
    }
    
    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
            int passkey) {//蓝牙芯片通知配对确认
        mBondStateMachine.sspRequestCallback(address, name, cod, pairingVariant,
            passkey);
    }

2)BondStateMachine
a)关注配对状态的回调

    void bondStateChangeCallback(int status, byte[] address, int newState) {
        ······
        Message msg = obtainMessage(BONDING_STATE_CHANGE);//状态机目前处于mPendingCommandState
        msg.obj = device;

        if (newState == BOND_STATE_BONDED)
            msg.arg1 = BluetoothDevice.BOND_BONDED;
        else if (newState == BOND_STATE_BONDING)
            msg.arg1 = BluetoothDevice.BOND_BONDING;
        else
            msg.arg1 = BluetoothDevice.BOND_NONE;
        msg.arg2 = status;

        sendMessage(msg);
    }
    
    private class PendingCommandState extends State {
        ······
        @Override
        public boolean processMessage(Message msg) {
            ······
            switch (msg.what) {
                ······
                case BONDING_STATE_CHANGE:
                    int newState = msg.arg1;
                    int reason = getUnbondReasonFromHALCode(msg.arg2);
                    sendIntent(dev, newState, reason);//发送广播出去
                    ······
                    break;
                ······
            }
            ······
        }
    }
    
    private void sendIntent(BluetoothDevice device, int newState, int reason) {
        DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
        int oldState = BluetoothDevice.BOND_NONE;
        if (devProp != null) {
            oldState = devProp.getBondState();
        }
        if (oldState == newState) return;
        mAdapterProperties.onBondStateChanged(device, newState);

        Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//关注广播
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
        intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
        if (newState == BluetoothDevice.BOND_NONE)
            intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
                AdapterService.BLUETOOTH_PERM);
        infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
                + " NewState: " + newState);
    }
    
    总结:
    针对配对的状态,其他应用关注广播:BluetoothDevice.ACTION_BOND_STATE_CHANGED

b)关注配置过程确认pin码

    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
            int passkey) {
        ······
        Message msg = obtainMessage(SSP_REQUEST);//状态机目前处于mPendingCommandState
        msg.obj = device;
        if(displayPasskey)
            msg.arg1 = passkey;
        msg.arg2 = variant;
        sendMessage(msg);
    }
    
    private class PendingCommandState extends State {
        ······
        @Override
        public boolean processMessage(Message msg) {
            ······
            switch (msg.what) {
                ······
                case SSP_REQUEST:
                    int passkey = msg.arg1;
                    int variant = msg.arg2;
                    sendDisplayPinIntent(devProp.getAddress(), passkey, variant);//发送广播
                    break;
                ······
            }
            ······
        }
    }
    
    private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
        Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);//关注此广播
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
        if (pin != 0) {
            intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
        }
        intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant);
        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
    }
    
    总结:
    针对配对过程获取pin码,其他应用关注广播:BluetoothDevice.ACTION_PAIRING_REQUEST
    

参考学习

https://www.jianshu.com/p/a150d55e29ca
https://blog.csdn.net/WHB20081815/article/details/88653177
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 175,490评论 5 419
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 74,060评论 2 335
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 124,407评论 0 291
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 47,741评论 0 248
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 56,543评论 3 329
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 43,040评论 1 246
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 34,107评论 3 358
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 32,646评论 0 229
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 36,694评论 1 271
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 32,398评论 2 279
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 33,987评论 1 288
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 30,097评论 3 285
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 35,298评论 3 282
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 27,278评论 0 14
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 28,413评论 1 232
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 38,397评论 2 309
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 38,099评论 2 314

推荐阅读更多精彩内容