Configurarea unui server Bluetooth
Astazi vom implementa patru clase in aplicatia noastra de chat peste Bluetooth:
MainActivity
- Activitatea principala unde se va gasi interfata grafica a aplicatiei de chatConnectThread
- Responsabil pentru conectarea la un socket BluetoothAcceptThread
- Serverul care asteapta conexiuniConnectedThread
- Threadul pe care vor comunica cele 2 dispozitive Bluetooth
Click pentru a vedea interfata grafica a aplicatiei
0. Descarcarea scheletului
Vom porni de la urmatorul schelet de cod:
git clone https://github.com/eim-lab/Laborator07.git
Pentru varianta java:
git checkout skel-java
Pentru solutie Java:
git checkout solution-java
Pentru varianta kotlin:
git checkout skel-kotlin
Pentru solutie Java:
git checkout solution-kotlin
Pasul 1. Configurarea interfetei grafice:
În schelet avem deja definită o interfață grafică in XML care include:
- O listă pentru afișarea mesajelor din chat.
- Un câmp text pentru introducerea mesajelor.
- Buton pentru listarea dispozitivelor Bluetooth împerecheate.
- Buton pentru trimiterea mesajelor.
In continuare, vom defini o fuctie numita initViews
, care inițializează toate componentele grafice definite în XML.
De notat faptul a vom folosi un ArrayAdapter
pentru a lega o sursa de date de un obiect din
interfata grafica.
private void initViews() {
// TODO 1: Implement the method that initializes the views
// Conectăm componentele din XML la codul Java
ListView chatListView = findViewById(R.id.chatListView);
messageEditText = findViewById(R.id.messageEditText);
sendButton = findViewById(R.id.sendButton);
listDevicesButton = findViewById(R.id.listDevicesButton);
// Setăm un eveniment pentru butonul de listare a dispozitivelor
listDevicesButton.setOnClickListener(v -> listPairedDevices());
// Pregătim o listă pentru mesaje
chatMessages = new ArrayList<>();
// Creăm un adaptor care transformă lista noastră de mesaje în elemente vizuale
chatArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, chatMessages);
// Schimbarile din adaptor se vor vedea imediat in interfata grafica. In acest exemplue, chatListView de tip ListView va fi actualizata cand facem schimbari la chatArrayAdapter
chatListView.setAdapter(chatArrayAdapter);
}
Pasul 2. Initializarea conexiunii Bluetooth
Vom implementa o metoda initBluetooth
, care inițializează conexiunea Bluetooth și verifică dacă telefonul suportă Bluetooth.
In aceasta functie vom lua o referinta la dispozitivul Bluetooth sub forma unui BluetoothAdapter
.
private void initBluetooth() {
// Vom lua o referinta la adaptorul de bluetooth
// de pe telefon. Putem vedea acest adaptor ca o interfata
// cu driver-ul de bluetooth.
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Verificăm dacă Bluetooth este disponibil
if (bluetoothAdapter == null) {
Toast.makeText(this, "Bluetooth is not available.", Toast.LENGTH_LONG).show();
finish(); // Închidem aplicația dacă Bluetooth nu este disponibil
}
}
Pasul 3. Gestionarea permisiunilor Bluetooth:
De ce sunt necesare permisiuni? Pe Android, orice funcție care accesează hardware-ul telefonului (precum Bluetooth) necesită permisiuni explicite din partea utilizatorului.
Adaugă metoda checkPermissions:
private void checkPermissions() {
List<String> permissions = new ArrayList<>();
// Pentru Android 12 sau mai nou, folosim permisiuni specifice Bluetooth
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissions.add(Manifest.permission.BLUETOOTH_CONNECT);
permissions.add(Manifest.permission.BLUETOOTH_SCAN);
} else {
// Pentru versiuni mai vechi, accesul la locație este necesar
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
// Cerem permisiunile utilizatorului
ActivityCompat.requestPermissions(this, permissions.toArray(new String[0]), REQUEST_PERMISSIONS);
}
Pasul 4. Activarea Bluetooth programatic
Uneori, utilizatorii pot avea Bluetooth dezactivat. Aplicația trebuie să-l activeze automat.
Adaugă următorul cod:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// TODO 4: handle permission results
if (requestCode == REQUEST_PERMISSIONS) {
boolean permissionGranted = true;
// Verificăm dacă toate permisiunile au fost acordate
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
permissionGranted = false;
break;
}
}
if (!permissionGranted) {
Toast.makeText(this, "Permissions required for Bluetooth operation.", Toast.LENGTH_LONG).show();
finish();
}
// Activăm Bluetooth dacă nu este deja activat
if (!bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}
}
// Handle Bluetooth enable result
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ENABLE_BT && resultCode != RESULT_OK) {
Toast.makeText(this, "Bluetooth must be enabled to continue.", Toast.LENGTH_LONG).show();
finish();
}
super.onActivityResult(requestCode, resultCode, data);
}
Pasul 5. Listarea dispozitivelor împerecheate
Pentru a permite comunicarea, utilizatorul trebuie să selecteze un dispozitiv împerecheat. Pentru a simplifica acest exercitiu, vom face pairing manual si doar vom afisa o lista cu dispozitivele imperecheate deja.
private void listPairedDevices() {
// TODO 5: Implement the method that displays a dialog for selecting a paired device
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
// Obținem dispozitivele împerecheate
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
List<String> deviceList = new ArrayList<>();
final List<BluetoothDevice> devices = new ArrayList<>();
// Le adaugam in lista
if (!pairedDevices.isEmpty()) {
for (BluetoothDevice device : pairedDevices) {
deviceList.add(device.getName() + "\n" + device.getAddress());
devices.add(device);
}
}
// Afișăm dispozitivele într-un dialog
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Select Device");
ArrayAdapter<String> deviceArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, deviceList);
// Tratam situatia in care un dispozitiv este apasat in cadrul dialogului
builder.setAdapter(deviceArrayAdapter, (dialog, which) -> {
selectedDevice = devices.get(which);
// deschidem un thread de comunicare
connectThread = new ConnectThread(this, bluetoothAdapter, selectedDevice, MY_UUID);
connectThread.start();
});
builder.show();
}
Pasul 6. Pornirea serverului pentru conexiuni Bluetooth
Serverul va asculta conexiuni de la alte dispozitive Bluetooth. Aceasta funcționalitate este implementată prin clasa AcceptThread, care gestionează un socket Bluetooth pentru a asculta conexiuni.
Ce face metoda startServer?
- Creează un thread care inițiază un server socket Bluetooth.
- Serverul rămâne activ până când o conexiune este acceptată sau este oprit manual.
private void startServer() {
// TODO 6: Implement server socket to listen for incoming connections
// Inițializăm AcceptThread, care va gestiona conexiunile primite
acceptThread = new AcceptThread(this, bluetoothAdapter, MY_UUID);
acceptThread.start();
}
Ce este AcceptThread?
- AcceptThread este o clasă separată care face toată munca grea pentru ascultarea conexiunilor. Codul acesteia va fi prezentat in urmatoarea sectiune (Comunicarea prin Bluetooth)
Pasul 7. Trimiterea de mesaje prin Bluetooth:
Aceasta metodă permite trimiterea mesajelor către dispozitivul conectat. Folosim ConnectedThread
pentru a scrie datele prin conexiunea Bluetooth. Metoda are responsabilitatea sa:
- Preia textul din câmpul de introducere al utilizatorului.
- Trimita textul folosind connectedThread.
- Adauge mesajul în interfață.
private void sendMessage() {
// TODO 7: Implement the method that sends a message to the connected device
sendButton.setOnClickListener(v -> {
// Preluăm mesajul introdus de utilizator
String message = messageEditText.getText().toString();
// Verificăm dacă mesajul nu este gol și conexiunea este activă
if (!message.isEmpty() && connectedThread != null) {
// Trimitem mesajul ca un array de bytes
connectedThread.write(message.getBytes());
// Golește câmpul de text
messageEditText.setText("");
// Adaugă mesajul trimis în interfață
addChatMessage("Me: " + message);
}
});
}
Clasa ConnectedThread
gestionează comunicarea efectivă între două dispozitive conectate. Se ocupă de: citirea mesajelor primite si trimiterea mesajelor. Detaliile de implementare vor fi prezentate in urmatoarea sectiune (Comunicarea prin Bluetooth).
Metoda addChatMessage
este o metodă auxiliară pentru actualizarea listei de mesaje din interfață:
private void addChatMessage(String message) {
chatMessages.add(message); // Adaugă mesajul în listă
chatArrayAdapter.notifyDataSetChanged(); // Actualizează interfața
}
Pasul 8: Gestionarea opririi aplicației
Este important să eliberăm resursele utilizate de Bluetooth (socket-uri și thread-uri) când aplicația se închide. Vom face acest lucru în metoda onDestroy
@Override
protected void onDestroy() {
super.onDestroy();
// TODO 8: cleanup threads
// Închidem AcceptThread dacă rulează
if (acceptThread != null) acceptThread.cancel();
// Închidem ConnectThread dacă rulează
if (connectThread != null) connectThread.cancel();
// Închidem ConnectedThread dacă rulează
if (connectedThread != null) connectedThread.cancel();
}