Bluetooth Chat

      在〈Bluetooth Chat〉中尚無留言

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>

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *