Clase auxiliare

Vom construi urmatoarele clase: ConnectThread, AcceptThread si ConnectedThread. Aceste clase gestionează conexiunile Bluetooth și comunicarea dintre dispozitive. Vom detalia fiecare clasă, explicând rolul, implementarea și cum funcționează în contextul aplicației.

AcceptThread

Scop:

Gestionează acceptarea conexiunilor Bluetooth. Funcționează ca un server care așteaptă conexiuni de la alte dispozitive.

AcceptThread așteaptă și acceptă conexiuni de la alte dispozitive Bluetooth. AcceptThread este un simplu Thread: private class AcceptThread extends Thread.

Constructorul creează un BluetoothServerSocket, utilizat pentru a asculta conexiunile. Verifică permisiunile pentru Bluetooth înainte de a iniția socket-ul.

    public AcceptThread(MainActivity activity, BluetoothAdapter adapter, UUID uuid) {
        this.mainActivity = activity;

        BluetoothServerSocket tempSocket = null;
        try {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
                if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                    Log.d("AcceptThread", "BLUETOOTH_CONNECT permission not granted.");
                    return;
                }
            }
            tempSocket = adapter.listenUsingRfcommWithServiceRecord("BluetoothChatApp", uuid);
        } catch (IOException e) {
            Log.e("AcceptThread", "Error creating server socket: " + e.getMessage());
        }
        this.serverSocket = tempSocket;
    }

Metoda Run()

Rulează un loop care așteaptă conexiuni. Când o conexiune este acceptată, o gestionează prin metoda manageConnectedSocket.

    @Override
    public void run() {
        if (serverSocket == null) {
            Log.d(TAG, "ServerSocket is null, cannot accept connections.");
            return;
        }

        BluetoothSocket socket;
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // Așteaptă o conexiune
                socket = serverSocket.accept();
                if (socket != null) {
                    Log.d(TAG, "Connection accepted. Managing socket...");

                     // Predă conexiunea aplicației
                    mainActivity.manageConnectedSocket(socket);
                    closeServerSocket();
                    break;
                }
            } catch (IOException e) {
                Log.e(TAG, "Error while accepting connection: " + e.getMessage());
                break;
            }
        }
        Log.d(TAG, "AcceptThread exiting.");
    }

Metoda Cancel()

Închide serverSocket, oprind acceptarea de conexiuni noi.

    public void cancel() {
        Log.d(TAG, "Canceling AcceptThread...");
        closeServerSocket();
    }

    private void closeServerSocket() {
        try {
            if (serverSocket != null) {
                serverSocket.close();
                Log.d(TAG, "ServerSocket closed successfully.");
            }
        } catch (IOException e) {
            Log.e(TAG, "Error closing ServerSocket: " + e.getMessage());
        }
    }

Clasa ConnectThread

ConnectThread încearcă să stabilească o conexiune cu un dispozitiv Bluetooth specificat. Creează un canal de comunicație (BluetoothSocket) către un alt dispozitiv Bluetooth.

    public ConnectThread(MainActivity activity, BluetoothAdapter adapter, BluetoothDevice device, UUID uuid) {
        this.mainActivity = activity;
        this.bluetoothAdapter = adapter;

        BluetoothSocket tmp = null;
        try {
            if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            tmp = device.createRfcommSocketToServiceRecord(uuid);
        } catch (IOException e) {
            Log.d("Connect->Constructor", e.toString());
        }
        socket = tmp;
    }

Metoda run()

Încearcă să stabilească o conexiune. Dacă reușește, predă conexiunea aplicației.

    public void run() {
        if (ActivityCompat.checkSelfPermission(mainActivity, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        if (socket == null) {
            Log.d("ConnectThread", "Socket is null, cannot connect.");
            return;
        }
        bluetoothAdapter.cancelDiscovery(); // Oprește căutarea altor dispozitive
        try {
            socket.connect(); // Se conectează la dispozitiv
            mainActivity.manageConnectedSocket(socket);
        } catch (IOException connectException) {
            mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, "Connection failed.", Toast.LENGTH_SHORT).show());
            try {
                socket.close();
            } catch (IOException closeException) {
                Log.d("Connect->Run", closeException.toString());
            }
        }
    }

Metoda cancel()

Închide conexiunea în caz de eroare sau la cererea utilizatorului.

    public void cancel() {
        try {
            if (socket == null) {
                return;
            }
            socket.close();
        } catch (IOException e) {
            Log.d("Connect->Cancel", e.toString());
        }
    }

ConnectedThread

ConnectedThread Gestionează trimiterea și primirea mesajelor după ce conexiunea este stabilită. O conexiunea este identificata printr-un socket bluetooth.

Constructorul primește un obiect BluetoothSocket și inițializează fluxurile de intrare și ieșire pentru comunicare. Configurează fluxurile de date (inputStream și outputStream) pentru a trimite și primi mesaje.


// BluetoothSocket este canalul de comuniatie stabilit cu un alt
// device bluetooth.
    public ConnectedThread(MainActivity activity, BluetoothSocket socket) {
        this.mainActivity = activity;
        this.socket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;

        try {
            tmpIn = socket.getInputStream();
        } catch (IOException e) {
            Log.d("Connected->Constructor", e.toString());
        }

        try {
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
            Log.d("Connected->Constructor", e.toString());
        }

        inputStream = tmpIn;
        outputStream = tmpOut;
    }

Run()

Ascultă în mod continuu pentru mesaje primite și le afișează în interfață.

    public void run() {
        byte[] buffer = new byte[1024];
        int bytes;

        if (ActivityCompat.checkSelfPermission(mainActivity, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
            return;
        }

        // obtine numele device-ului cu care comunicam
        String remoteDeviceName = socket.getRemoteDevice().getName();

        mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, "Connected to " + remoteDeviceName, Toast.LENGTH_SHORT).show());

        while (true) {
            try {
                bytes = inputStream.read(buffer);  // Citește mesajul
                String incomingMessage = new String(buffer, 0, bytes);
                
                // Adaugam mesajul in threadul de UI al aplicatiei 
                mainActivity.runOnUiThread(() -> mainActivity.addChatMessage(remoteDeviceName + ": " + incomingMessage));
            } catch (IOException e) {
                mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, "Connection lost.", Toast.LENGTH_SHORT).show());
                break;
            }
        }
    }

Write()

Trimite un mesaj prin fluxul de ieșire.

    public void write(byte[] bytes) {
        try {
            outputStream.write(bytes);
        } catch (IOException e) {
            mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, "Failed to send message.", Toast.LENGTH_SHORT).show());
            Log.d("Connected->Write", e.toString());
        }
    }

Cancel()

Metoda cancel() închide conexiunea Bluetooth prin închiderea obiectului BluetoothSocket.

    public void cancel() {
        try {
            if (socket == null) {
                Log.d("ConnectedThread", "Socket is null, cannot close.");
                return;
            }
            socket.close();
        } catch (IOException e) {
            Log.d("Connected->Cancel", e.toString());
        }
    }

Rezumat

  • AcceptThread: Ascultă conexiunile Bluetooth primite.
  • ConnectThread: Inițiază conexiuni Bluetooth către alte dispozitive.
  • ConnectedThread: Gestionează comunicarea între dispozitive.

Aceste clase funcționează împreună pentru a crea o aplicație de chat complet funcțională. Asigură-te că gestionezi corect permisiunile și închizi conexiunile neutilizate!