天天看點

Android Bluetooth 學習(3)藍牙裝置之間自動配對

前言(android2.3版本,4.0版本由于是随機擷取pin值,沒有研究過):

    1、藍牙裝置之間自動配對,需要兩個裝置都安裝進行配對的apk(網上好多自動配對的文章都沒有說明情況)

    2、在自動比對的時候想通過反射調用BluetoothDevice的setPin、createBond、cancelPairingUserInput實作設定密鑰、配對請求建立、取消密鑰資訊輸入等。

        1)createBond()建立,最終會調到源碼的BluetoothService的createBond(String address)方法,通過對源碼淺顯的了解,createBond主要是寫入比對密鑰(BluetoothService的writeDockPin())以及進入jni注冊回調函數onCreatePairedDeviceResult觀察比對結果

比如:    // Pins did not match, or remote device did not respond to pin

            // request in time

            // We rejected pairing, or the remote side rejected pairing. This

            // happens if either side presses 'cancel' at the pairing dialog.

            // Not sure if this happens 

            // Other device is not responding at all

            // already bonded

等,在jni中建立了進行比對的device("CreatePairedDevice"),這時bluetooth會發送一個ACTION_PAIRING_REQUEST的廣播,隻有目前會出現密鑰框的藍牙裝置收到。寫完密鑰之後,發送廣播給另外一個藍牙裝置接收,然後打開密鑰輸入框進行比對。

        2)setPin()設定密鑰,通過檢視setting源碼,發現在确認輸入密鑰之後會調用setPin()(如果點取消,就會調用cancelPairingUserInput,取消密鑰框),setPin具體通過D-BUS做了什麼沒有去深究,但是在調用setPin的時候會remove掉一個map裡面的鍵值對(address:int),也就是我們在調用setPin之後如果再去調用onCreatePairedDeviceResult,則該方法一定傳回false,并且出現下面的列印提示:cancelUserInputNative(B8:FF:FE:55:EF:D6) called but no native data available, ignoring. Maybe the PasskeyAgent Request was already cancelled by the remote or by bluez.(因為該方法也會remove掉一個鍵值對)

        3)cancelPairingUserInput()取消使用者輸入密鑰框,個人覺得一般情況下不要和setPin(setPasskey、setPairingConfirmation、setRemoteOutOfBandData)一起用,這幾個方法都會remove掉map裡面的key:value(也就是互斥的

Android Bluetooth 學習(3)藍牙裝置之間自動配對

)。

    3、藍牙耳機、搖桿等一些無法手動配置的裝置是如何完成自動配對的。

    在源碼裡面有一個自動配對的方法,也就是把pin值自動設為“0000”

?

1 2 3 4 5 6 7 8 9

synchronized boolean attemptAutoPair(String address) {

if

(!mBondState.hasAutoPairingFailed(address) &&

!mBondState.isAutoPairingBlacklisted(address)) {

mBondState.attempt(address);

setPin(address, BluetoothDevice.convertPinToBytes(

"0000"

));

return

true

;

}

return

false

;

}

該方法是在底層回調到java層的onRequestPinCode方法時被調用,首先 Check if its a dock(正常輸入的密鑰,走正常配對方式,雙方輸入比對值),然後再 try 0000 once if the device looks dumb(涉及到Device.AUDIO_VIDEO相關部分如:耳機,免提等進入自動比對模式)進行自動配對。 

言歸正傳,雖然個人覺得自動配對需要雙方乃至多方藍牙裝置都需要裝上實作自動配對的apk,已經失去了自動配對的意義,但有可能還是會派上用場。下面我們看看現實情況的自動配對是什麼樣的吧。

由于BluetoothDevice配對的方法都是hide的,是以我們需要通過反射調用被隐藏的方法,現在基本都是通用的工具類型了,網上模式基本一樣。

ClsUtils.java

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

package

cn.bluetooth;

import

java.lang.reflect.Field;

import

java.lang.reflect.Method;

import

android.bluetooth.BluetoothAdapter;

import

android.bluetooth.BluetoothDevice;

import

android.util.Log;

public

class

ClsUtils

{

public

static

BluetoothDevice remoteDevice=

null

;

@SuppressWarnings

(

"unchecked"

)

static

public

boolean

createBond(

@SuppressWarnings

(

"rawtypes"

) Class btClass, BluetoothDevice btDevice)

throws

Exception

{

Method createBondMethod = btClass.getMethod(

"createBond"

);

Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);

return

returnValue.booleanValue();

}

@SuppressWarnings

(

"unchecked"

)

static

public

boolean

removeBond(Class btClass, BluetoothDevice btDevice)

throws

Exception

{

Method removeBondMethod = btClass.getMethod(

"removeBond"

);

Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice);

return

returnValue.booleanValue();

}

@SuppressWarnings

(

"unchecked"

)

static

public

boolean

setPin(Class btClass, BluetoothDevice btDevice,

String str)

throws

Exception

{

try

{

Method removeBondMethod = btClass.getDeclaredMethod(

"setPin"

,

new

Class[]

{

byte

[].

class

});

Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice,

new

Object[]

{str.getBytes()});

Log.d(

"returnValue"

,

"setPin is success "

+btDevice.getAddress()+ returnValue.booleanValue());

}

catch

(SecurityException e)

{

// throw new RuntimeException(e.getMessage());

e.printStackTrace();

}

catch

(IllegalArgumentException e)

{

// throw new RuntimeException(e.getMessage());

e.printStackTrace();

}

catch

(Exception e)

{

// TODO Auto-generated catch block

e.printStackTrace();

}

return

true

;

}

// 取消使用者輸入

@SuppressWarnings

(

"unchecked"

)

static

public

boolean

cancelPairingUserInput(Class btClass,

BluetoothDevice device)

throws

Exception

{

Method createBondMethod = btClass.getMethod(

"cancelPairingUserInput"

);

// cancelBondProcess()

Boolean returnValue = (Boolean) createBondMethod.invoke(device);

Log.d(

"returnValue"

,

"cancelPairingUserInput is success "

+ returnValue.booleanValue());

return

returnValue.booleanValue();

}

// 取消配對

@SuppressWarnings

(

"unchecked"

)

static

public

boolean

cancelBondProcess(Class btClass,

BluetoothDevice device)

throws

Exception

{

Method createBondMethod = btClass.getMethod(

"cancelBondProcess"

);

Boolean returnValue = (Boolean) createBondMethod.invoke(device);

return

returnValue.booleanValue();

}

@SuppressWarnings

(

"unchecked"

)

static

public

void

printAllInform(Class clsShow)

{

try

{

// 取得所有方法

Method[] hideMethod = clsShow.getMethods();

int

i =

;

for

(; i < hideMethod.length; i++)

{

//Log.e("method name", hideMethod.getName() + ";and the i is:"

//      + i);

}

// 取得所有常量

Field[] allFields = clsShow.getFields();

for

(i =

; i < allFields.length; i++)

{

//Log.e("Field name", allFields.getName());

}

}

catch

(SecurityException e)

{

// throw new RuntimeException(e.getMessage());

e.printStackTrace();

}

catch

(IllegalArgumentException e)

{

// throw new RuntimeException(e.getMessage());

e.printStackTrace();

}

catch

(Exception e)

{

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

Bluetooth1.java  主activity,所有界面操作實作地方 

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212

package

cn.bluetooth;

import

java.io.IOException;

import

java.util.ArrayList;

import

java.util.List;

import

android.app.Activity;

import

android.bluetooth.BluetoothAdapter;

import

android.bluetooth.BluetoothDevice;

import

android.bluetooth.BluetoothSocket;

import

android.content.BroadcastReceiver;

import

android.content.Context;

import

android.content.Intent;

import

android.content.IntentFilter;

import

android.os.Bundle;

import

android.util.Log;

import

android.view.Menu;

import

android.view.View;

import

android.widget.AdapterView;

import

android.widget.ArrayAdapter;

import

android.widget.Button;

import

android.widget.ListView;

import

android.widget.Toast;

import

android.widget.ToggleButton;

public

class

Bluetooth1

extends

Activity {

Button btnSearch, btnDis, btnExit;

ToggleButton tbtnSwitch;

ListView lvBTDevices;

ArrayAdapter<String> adtDevices;

List<String> lstDevices =

new

ArrayList<String>();

BluetoothAdapter btAdapt;

public

static

BluetoothSocket btSocket;

@Override

public

void

onCreate(Bundle savedInstanceState) {

super

.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Button 設定

btnSearch = (Button)

this

.findViewById(R.id.btnSearch);

btnSearch.setOnClickListener(

new

ClickEvent());

btnExit = (Button)

this

.findViewById(R.id.btnExit);

btnExit.setOnClickListener(

new

ClickEvent());

btnDis = (Button)

this

.findViewById(R.id.btnDis);

btnDis.setOnClickListener(

new

ClickEvent());

// ToogleButton設定

tbtnSwitch = (ToggleButton)

this

.findViewById(R.id.tbtnSwitch);

tbtnSwitch.setOnClickListener(

new

ClickEvent());

// ListView及其資料源 擴充卡

lvBTDevices = (ListView)

this

.findViewById(R.id.lvDevices);

adtDevices =

new

ArrayAdapter<String>(

this

,

android.R.layout.simple_list_item_1, lstDevices);

lvBTDevices.setAdapter(adtDevices);

lvBTDevices.setOnItemClickListener(

new

ItemClickEvent());

btAdapt = BluetoothAdapter.getDefaultAdapter();

// 初始化本機藍牙功能

// ========================================================

// modified by wiley

if

(btAdapt.isEnabled()) {

tbtnSwitch.setChecked(

false

);

}

else

{

tbtnSwitch.setChecked(

true

);

}

// ============================================================

// 注冊Receiver來擷取藍牙裝置相關的結果

IntentFilter intent =

new

IntentFilter();

intent.addAction(BluetoothDevice.ACTION_FOUND);

// 用BroadcastReceiver來取得搜尋結果

intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);

intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);

intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);

registerReceiver(searchDevices, intent);

}

private

final

BroadcastReceiver searchDevices =

new

BroadcastReceiver() {  

@Override

public

void

onReceive(Context context, Intent intent) {     

String action = intent.getAction();

Bundle b = intent.getExtras();

Object[] lstName = b.keySet().toArray();

// 顯示所有收到的消息及其細節

for

(

int

i =

; i < lstName.length; i++) {

String keyName = lstName.toString();

Log.e(keyName, String.valueOf(b.get(keyName)));

}

BluetoothDevice device =

null

;

// 搜尋裝置時,取得裝置的MAC位址

if

(BluetoothDevice.ACTION_FOUND.equals(action)) {

device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

if

(device.getBondState() == BluetoothDevice.BOND_NONE) {

String str =

"           未配對|"

+ device.getName() +

"|"

+ device.getAddress();

if

(lstDevices.indexOf(str) == -

1

)

// 防止重複添加

lstDevices.add(str);

// 擷取裝置名稱和mac位址

adtDevices.notifyDataSetChanged();

}

}

else

if

(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)){

device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

switch

(device.getBondState()) {

case

BluetoothDevice.BOND_BONDING:

Log.d(

"BlueToothTestActivity"

,

"正在配對......"

);

break

;

case

BluetoothDevice.BOND_BONDED:

Log.d(

"BlueToothTestActivity"

,

"完成配對"

);

//connect(device);//連接配接裝置

break

;

case

BluetoothDevice.BOND_NONE:

Log.d(

"BlueToothTestActivity"

,

"取消配對"

);

default

:

break

;

}

}

}

};

@Override

protected

void

onDestroy() {

this

.unregisterReceiver(searchDevices);

super

.onDestroy();

android.os.Process.killProcess(android.os.Process.myPid());

}

class

ItemClickEvent

implements

AdapterView.OnItemClickListener {

@Override

public

void

onItemClick(AdapterView<?> arg0, View arg1,

int

arg2,

long

arg3) {

if

(btAdapt.isDiscovering())btAdapt.cancelDiscovery();

String str = lstDevices.get(arg2);

String[] values = str.split(

"\\|"

);

String address = values[

2

];

Log.e(

"address"

, values[

2

]);            

BluetoothDevice btDev = btAdapt.getRemoteDevice(address);

try

{

Boolean returnValue =

false

;

if

(btDev.getBondState() == BluetoothDevice.BOND_NONE) {

Toast.makeText(Bluetooth1.

this

,

"遠端裝置發送藍牙配對請求"

,

5000

).show();

//這裡隻需要createBond就行了

ClsUtils.createBond(btDev.getClass(), btDev);  

}

else

if

(btDev.getBondState() == BluetoothDevice.BOND_BONDED){

Toast.makeText(Bluetooth1.

this

, btDev.getBondState()+

" ....正在連接配接.."

,

1000

).show();

}

}

catch

(Exception e) {

e.printStackTrace();

}

}

}

class

ClickEvent

implements

View.OnClickListener {

@Override

public

void

onClick(View v) {

if

(v == btnSearch)

// 搜尋藍牙裝置,在BroadcastReceiver顯示結果

{

if

(btAdapt.getState() == BluetoothAdapter.STATE_OFF) {

// 如果藍牙還沒開啟

Toast.makeText(Bluetooth1.

this

,

"請先打開藍牙"

,

1000

)

.show();

return

;

}

if

(btAdapt.isDiscovering())

btAdapt.cancelDiscovery();

lstDevices.clear();

Object[] lstDevice = btAdapt.getBondedDevices().toArray();

for

(

int

i =

; i < lstDevice.length; i++) {

BluetoothDevice device = (BluetoothDevice) lstDevice[i];

String str =

" 已配對|"

+ device.getName() +

"|"

+ device.getAddress();

lstDevices.add(str);

// 擷取裝置名稱和mac位址

adtDevices.notifyDataSetChanged();

}

setTitle(

"本機:"

+ btAdapt.getAddress());

btAdapt.startDiscovery();

}

else

if

(v == tbtnSwitch) {

// 本機藍牙啟動/關閉

if

(tbtnSwitch.isChecked() ==

false

)

btAdapt.enable();

else

if

(tbtnSwitch.isChecked() ==

true

)

btAdapt.disable();

}

else

if

(v == btnDis)

// 本機可以被搜尋

{

Intent discoverableIntent =

new

Intent(

BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);

discoverableIntent.putExtra(

BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,

300

);

startActivity(discoverableIntent);

}

else

if

(v == btnExit) {

try

{

if

(btSocket !=

null

)

btSocket.close();

}

catch

(IOException e) {

e.printStackTrace();

}

Bluetooth1.

this

.finish();

}

}

}

@Override

public

boolean

onCreateOptionsMenu(Menu menu) {

getMenuInflater().inflate(R.menu.main, menu);

return

true

;

}

}

PairingRequest.java  (重要部分,自動配對主要是這個部分完成,activity隻是建立了一個配對請求) 

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

package

cn.bluetooth;

import

android.bluetooth.BluetoothDevice;

import

android.content.BroadcastReceiver;

import

android.content.Context;

import

android.content.Intent;

import

android.widget.Toast;

public

class

PairingRequest

extends

BroadcastReceiver {

String strPsw =

"0000"

;

final

String ACTION_PAIRING_REQUEST =

"android.bluetooth.device.action.PAIRING_REQUEST"

;

static

BluetoothDevice remoteDevice =

null

;

@Override

public

void

onReceive(Context context, Intent intent) {

if

(intent.getAction().equals(ACTION_PAIRING_REQUEST)) {

BluetoothDevice device = intent

.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

if

(device.getBondState() != BluetoothDevice.BOND_BONDED) {

try

{

ClsUtils.setPin(device.getClass(), device, strPsw);

// 手機和藍牙采集器配對

// ClsUtils.cancelPairingUserInput(device.getClass(),

// device); //一般調用不成功,前言裡面講解過了

Toast.makeText(context,

"配對資訊"

+ device.getName(),

5000

)

.show();

}

catch

(Exception e) {

// TODO Auto-generated catch block

Toast.makeText(context,

"請求連接配接錯誤..."

,

1000

).show();

}

}

// */

// pair(device.getAddress(),strPsw);

}

}

}

AndroidManifest.xml  啟動activity,接收廣播 

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

<

manifest

xmlns:android

=

"http://schemas.android.com/apk/res/android"

package

=

"cn.bluetooth"

android:versionCode

=

"1"

android:versionName

=

"1.0"

>

<

uses-sdk

android:minSdkVersion

=

"8"

android:targetSdkVersion

=

"15"

/>

<

uses-permission

android:name

=

"android.permission.BLUETOOTH_ADMIN"

/>

<

uses-permission

android:name

=

"android.permission.BLUETOOTH"

/>

<

application

android:icon

=

"@drawable/ic_launcher"

android:label

=

"@string/app_name"

android:theme

=

"@style/AppTheme"

>

<

activity

android:name

=

".Bluetooth1"

android:label

=

"@string/title_activity_bluetooth1"

>

<

intent-filter

>

<

action

android:name

=

"android.intent.action.MAIN"

/>

<

category

android:name

=

"android.intent.category.LAUNCHER"

/>

</

intent-filter

>

</

activity

>

<

receiver

android:name

=

".PairingRequest"

>

<

intent-filter

>

<

action

android:name

=

"android.bluetooth.device.action.PAIRING_REQUEST"

/>

</

intent-filter

>

</

receiver

>

</

application

>

</

manifest

>

main.xml   布局 

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

<

LinearLayout

xmlns:android

=

"http://schemas.android.com/apk/res/android"

xmlns:tools

=

"http://schemas.android.com/tools"

android:id

=

"@+id/LinearLayout1"

android:layout_width

=

"match_parent"

android:layout_height

=

"match_parent"

android:orientation

=

"vertical"

>

<

Button

android:layout_width

=

"wrap_content"

android:layout_height

=

"wrap_content"

android:id

=

"@+id/btnSearch"

android:text

=

"btnSearch"

/>

<

Button

android:layout_width

=

"wrap_content"

android:layout_height

=

"wrap_content"

android:id

=

"@+id/btnExit"

android:text

=

"btnExit"

/>

<

Button

android:layout_width

=

"wrap_content"

android:layout_height

=

"wrap_content"

android:id

=

"@+id/btnDis"

android:text

=

"btnDis"

/>

<

ToggleButton

android:layout_width

=

"wrap_content"

android:layout_height

=

"wrap_content"

android:id

=

"@+id/tbtnSwitch"

android:text

=

"tbtnSwitch"

/>

<

ListView

android:layout_width

=

"fill_parent"

android:layout_height

=

"wrap_content"

android:id

=

"@+id/lvDevices"

/>

</

LinearLayout

>

我覺得想要真正意義上的完成藍牙裝置的自動配對,方法還是有的,需要研究一下setting部分的Bluetooth子產品,以及根據藍牙源碼進行深入了解,期待着關于android bluetooth深入淺出的文章,大家有什麼好的文章,留個言大家一起好好學習學習。