前言(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(也就是互斥的)。
3、蓝牙耳机、手柄等一些无法手动配置的设备是如何完成自动配对的。
在源码里面有一个自动配对的方法,也就是把pin值自动设为“0000”
1
2
3
4
5
6
7
8
9
|
/*package*/
synchronized boolean attemptAutoPair(String address) {
if
(!mBondState.hasAutoPairingFailed(address) &&
!mBondState.isAutoPairingBlacklisted(address)) {
mBondState.attempt(address);
setPin(address, BluetoothDevice.convertPinToBytes(
"0000"
));
return
true
;
}
return
false
;
}
|
言归正传,虽然个人觉得自动配对需要双方乃至多方蓝牙设备都需要装上实现自动配对的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
;
/**
* 与设备配对 参考源码:platform/packages/apps/Settings.git
* /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
*/
@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();
}
/**
* 与设备解除配对 参考源码:platform/packages/apps/Settings.git
* /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
*/
@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();
}
/**
*
* @param clsShow
*/
@SuppressWarnings
(
"unchecked"
)
static
public
void
printAllInform(Class clsShow)
{
try
{
// 取得所有方法
Method[] hideMethod = clsShow.getMethods();
int
i =
0
;
for
(; i < hideMethod.length; i++)
{
//Log.e("method name", hideMethod.getName() + ";and the i is:"
// + i);
}
// 取得所有常量
Field[] allFields = clsShow.getFields();
for
(i =
0
; 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();
}
}
}
|
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 {
/** Called when the activity is first created. */
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.getState() == BluetoothAdapter.STATE_OFF)// 读取蓝牙状态并显示
* tbtnSwitch.setChecked(false); else if (btAdapt.getState() ==
* BluetoothAdapter.STATE_ON) tbtnSwitch.setChecked(true);
*/
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 =
0
; 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 =
0
; 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
;
}
}
|
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);
}
}
}
|
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
>
|
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
>
|