MahalBluetooth.java
package com.asuscomm.mahaljsp.mahalbtchat;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public class MahalBluetooth {
BluetoothAdapter adapter;
static final String SPP_UUID = "abcd0000-1234-1234-1234-abcd12345678";
public UUID uuid = UUID.fromString(SPP_UUID);
public MahalBluetoothListener listener;
public List<HashMap<String, String>> bondList=new ArrayList<>();
public List<HashMap<String, String>> unbondList=new ArrayList<>();
public List<BluetoothDevice> bondDeviceList=new ArrayList<>();
public List<BluetoothDevice> unbondDeviceList=new ArrayList<>();
BluetoothServerSocket serverSocket;
BluetoothSocket clientSocket;
public OutputStream outputStream;
public InputStream inputStream;
static Context context;
boolean receiveDataFlag=true;
public MahalBluetooth(final Context context, final MahalBluetoothListener listener)throws IOException {
this.context=context;
this.listener=listener;
adapter=BluetoothAdapter.getDefaultAdapter();
if(adapter!=null){
if (!adapter.isEnabled()) {
adapter.enable();
}
for (BluetoothDevice device : adapter.getBondedDevices()) {
HashMap<String, String> h = new HashMap<>();
h.put("Name", device.getName());
h.put("Mac", device.getAddress());
bondList.add(h);
bondDeviceList.add(device);
}
serverSocket = adapter.listenUsingRfcommWithServiceRecord("Bluetooth_Socket", uuid);
new Thread(receivedDataRunnable).start();
}
else {
throw(new IOException("None bluetooth on your device"));
}
}
public void discovery(){
IntentFilter filter=new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
context.registerReceiver(receiver, filter);
adapter.startDiscovery();
}
public void bondDevice(String mac){
try{
Method bondMethod=BluetoothDevice.class.getMethod("createBond");
BluetoothDevice device=adapter.getRemoteDevice(mac);
bondMethod.invoke(device);
}
catch(NoSuchMethodException e){}
catch (IllegalAccessException e){}
catch (InvocationTargetException e) {}
}
public void connectDevice(String mac){
new Thread(()->{
try {
BluetoothDevice device=adapter.getRemoteDevice(mac);
clientSocket = device.createRfcommSocketToServiceRecord(uuid);
clientSocket.connect();
outputStream = clientSocket.getOutputStream();
inputStream = clientSocket.getInputStream();
serverSocket.close();//當裝置為client時, 要中斷下面執行緒裏的 serverSocket.accept() blocking call
serverSocket=null;
((Activity)context).runOnUiThread(()->{listener.connectedDevice();});
} catch (IOException e) {}
}).start();
}
BroadcastReceiver receiver=new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
BluetoothDevice device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch (intent.getAction()){
case BluetoothDevice.ACTION_FOUND:
if(!bondDeviceList.contains(device)) {
((Activity) context).runOnUiThread(() -> {
HashMap<String, String> h= new HashMap<>();
h.put("Name", device.getName());
h.put("Mac", device.getAddress());
unbondList.add(h);
unbondDeviceList.add(device);
listener.foundDevice();
});
}
break;
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
int currentBondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.BOND_NONE);
if(currentBondState==BluetoothDevice.BOND_BONDED){
HashMap<String, String>hashMap=new HashMap<>();
hashMap.put("Name", device.getName());
hashMap.put("Mac", device.getAddress());
bondList.add(hashMap);
bondDeviceList.add(device);
unbondDeviceList.remove(device);
for (HashMap<String, String >h : unbondList){
if(h.get("Mac").equals(device.getAddress())){
unbondList.remove(h);
break;
}
}
listener.bondedDevice();
}
break;
}
}
};
Runnable receivedDataRunnable=new Runnable(){
@Override
public void run() {
while(receiveDataFlag) {
try {
//Starting blocking call
clientSocket = serverSocket.accept();
inputStream = clientSocket.getInputStream();
outputStream = clientSocket.getOutputStream();
} catch (Exception e) {
}
try {
while (true) {
byte[] buffer = new byte[1024];
int count = inputStream.read(buffer);
String strMsg = new String(buffer, 0, count, "utf-8");
((Activity) context).runOnUiThread(() -> {
listener.receivedMessage(strMsg);
});
}
} catch (Exception e) {
Log.d("Thomas", "close receive");
//Server端斷線後, 客戶端因serverSocket close, 會一直跑空迴圈, 所以從建serverSocket
//凡是任何中斷inputStream.read的事件, 都重建serverSocket, 然後再次等待連線
try {
serverSocket.close();//一定要先關閉, 才能正確產生新的serverSocket
serverSocket=adapter.listenUsingRfcommWithServiceRecord("Bluetooth_Socket", uuid);
} catch (IOException e1) {}
}
}
Log.d("Thomas", "close Thread");
}
};
public void close(){
try {
receiveDataFlag=false;
serverSocket.close();
inputStream.close();
outputStream.close();
clientSocket.close();//clientSocket關閉後, 遠端就會結束inputStream blocking
adapter.cancelDiscovery();
} catch (IOException e) {}
}
}
MahalBluetoothListener.java
package com.asuscomm.mahaljsp.mahalbtchat;
public interface MahalBluetoothListener {
void foundDevice();
void bondedDevice();
void connectedDevice();
void receivedMessage(String strMsg);
}
MainActivity.java
package com.asuscomm.mahaljsp.mahalbtchat;
import android.Manifest;
import android.bluetooth.BluetoothDevice;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
MahalBluetooth mahalBluetooth;
ListView bondListView, unbondListView;
SimpleAdapter bondListViewAdapter, unbondListViewAdapter;
TextView txtMsg;
EditText editMsg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bondListView=(ListView)findViewById(R.id.bondListView);
unbondListView=(ListView)findViewById(R.id.unbondListView);
txtMsg=(TextView)findViewById(R.id.txtMsg);
editMsg=(EditText)findViewById(R.id.editMsg);
try {
mahalBluetooth = new MahalBluetooth(this, btListener);
bondListViewAdapter=new SimpleAdapter(this, mahalBluetooth.bondList, R.layout.listview_layout,
new String[]{"Name","Mac"},
new int[]{R.id.txtName, R.id.txtMac});
bondListView.setAdapter(bondListViewAdapter);
bondListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String mac=mahalBluetooth.bondList.get(position).get("Mac");
Toast.makeText(MainActivity.this, "Connecting....", Toast.LENGTH_SHORT).show();
mahalBluetooth.connectDevice(mac);
}
});
unbondListViewAdapter=new SimpleAdapter(this, mahalBluetooth.unbondList, R.layout.listview_layout,
new String[]{"Name","Mac"},
new int[]{R.id.txtName, R.id.txtMac});
unbondListView.setAdapter(unbondListViewAdapter);
unbondListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String mac=mahalBluetooth.unbondList.get(position).get("Mac");
Log.d("Thomas", "MainActivity bondDevice");
Toast.makeText(MainActivity.this, "開始配對", Toast.LENGTH_LONG);
mahalBluetooth.bondDevice(mac);
}
});
processPermission();
}
catch(IOException e){
new AlertDialog.Builder(MainActivity.this)
.setTitle("Bluetooth Test")
.setMessage(("裝置沒有藍芽設備, 即將結束"))
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.show();
}
}
private void init(){
mahalBluetooth.discovery();
}
private void processPermission(){
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
int hasPermission=checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
if(hasPermission!= PackageManager.PERMISSION_GRANTED){
requestPermissions(
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},200);
}
else{
init();
}
}
else init();
}
@Override
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
if(requestCode==200){
if (grantResults[0]==PackageManager.PERMISSION_GRANTED)init();
else Toast.makeText(this, "No permission", Toast.LENGTH_LONG).show();
}
else super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
MahalBluetoothListener btListener=new MahalBluetoothListener() {
@Override
public void foundDevice() {
unbondListViewAdapter.notifyDataSetChanged();
}
@Override
public void bondedDevice() {
bondListViewAdapter.notifyDataSetChanged();
unbondListViewAdapter.notifyDataSetChanged();
Toast.makeText(MainActivity.this, "已配對", Toast.LENGTH_SHORT).show();
}
@Override
public void connectedDevice() {
Toast.makeText(MainActivity.this, "Connected", Toast.LENGTH_SHORT).show();
}
@Override
public void receivedMessage(String strmsg) {
txtMsg.setText(txtMsg.getText()+"\n"+strmsg);
}
};
public void btnSendClick(View view){
try {
mahalBluetooth.outputStream.write(editMsg.getText().toString().getBytes("utf-8"));
editMsg.setText("");
} catch (IOException e) {
Log.d("Thomas", "Send error : "+e.getMessage());
}
catch(Exception e){
Log.d("Thomas", e.getMessage());
}
}
@Override
protected void onDestroy() {
mahalBluetooth.close();
super.onDestroy();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:text="已配對裝置"
android:background="#bbbbbb"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ListView
android:id="@+id/bondListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<TextView
android:text="搜尋裝置"
android:background="#bbbbbb"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ListView
android:id="@+id/unbondListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<TextView
android:id="@+id/txtMsg"
android:layout_weight="1"
android:background="#00ff00"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<EditText
android:id="@+id/editMsg"
android:layout_width="match_parent"
android:layout_height="50dp" />
<Button
android:id="@+id/btnSend"
android:text="Send"
android:onClick="btnSendClick"
android:layout_width="match_parent"
android:layout_height="80dp" />
</LinearLayout>
listview_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txtName"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/txtMac"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.asuscomm.mahaljsp.mahalbtchat">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
