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>