* Copyright (c) 2024-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ohos.ace.plugin.bluetoothplugin;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import ohos.ace.adapter.ALog;
public class BluetoothGattClient {
private static final String LOG_TAG = "BluetoothGattClient";
private static final int OH_BT_STATUS_SUCCESS = 0;
private static final int OH_BT_STATUS_FAIL = 1;
private static final int WRITE_SUCCESS = 0;
private static final int VERSION_TIRAMISU = 33;
private BluetoothGatt bluetoothGatt_ = null;
private int appId_ = 0;
private BluetoothGattCallback gattCallback_ = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
ALog.i(LOG_TAG, "onConnectionStateChange status is " + status + ", newState is " + newState);
if (status != 0 && gatt != null) {
gatt.close();
}
Intent intent = new Intent(BluetoothPlugin.BLE_CLIENT_CONNECTION_STATE_CHANGE);
intent.putExtra("appId", appId_);
intent.putExtra("status", status);
intent.putExtra("newState", newState);
BluetoothPlugin.sendBleBroadcast(intent);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
ALog.i(LOG_TAG, "onServicesDiscovered enter.");
Intent intent = new Intent(BluetoothPlugin.BLE_CLIENT_SERVICES_DISCOVERED);
intent.putExtra("appId", appId_);
if (status == BluetoothGatt.GATT_SUCCESS) {
for (BluetoothGattService service : gatt.getServices()) {
String serviceJSONString = BluetoothHelper.convertGattServiceToJSONString(service);
intent.putExtra("serviceJSONString", serviceJSONString);
HashMap<String, String[]> clientCharacteristicMap = new HashMap<>();
String serviceUuid = service.getUuid().toString();
List<String> characterStrings = new ArrayList<>();
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
String characterJSONString = BluetoothHelper.convertCharacteristicToJSONString(characteristic);
characterStrings.add(characterJSONString);
intent.putStringArrayListExtra ("characterStrings", (ArrayList<String>)characterStrings);
List<String> descriptorStrings = new ArrayList<>();
for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
String descriptorResult = BluetoothHelper.convertDescriptorToJSONString(descriptor);
descriptorStrings.add(descriptorResult);
}
clientCharacteristicMap.put(
characteristic.getUuid().toString(), descriptorStrings.toArray(new String[] {}));
intent.putExtra("clientCharacteristicMap", (Serializable)clientCharacteristicMap);
}
BluetoothPlugin.sendBleBroadcast(intent);
}
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
Intent intent = new Intent(BluetoothPlugin.BLE_CLIENT_READ_CHARACTERISTIC);
intent.putExtra("appId", appId_);
String result = BluetoothHelper.convertCharacteristicToJSONString(characteristic);
if (status == BluetoothGatt.GATT_SUCCESS) {
intent.putExtra("status", OH_BT_STATUS_SUCCESS);
} else {
intent.putExtra("status", OH_BT_STATUS_FAIL);
}
intent.putExtra("result", result);
BluetoothPlugin.sendBleBroadcast(intent);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Intent intent = new Intent(BluetoothPlugin.BLE_CLIENT_WRITE_CHARACTERISTIC);
intent.putExtra("appId", appId_);
String result = BluetoothHelper.convertCharacteristicToJSONString(characteristic);
if (status == BluetoothGatt.GATT_SUCCESS) {
intent.putExtra("status", OH_BT_STATUS_SUCCESS);
} else {
intent.putExtra("status", OH_BT_STATUS_FAIL);
}
intent.putExtra("result", result);
BluetoothPlugin.sendBleBroadcast(intent);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Intent intent = new Intent(BluetoothPlugin.BLE_CLIENT_CHARACTERISTIC_CHANGED);
intent.putExtra("appId", appId_);
String result = BluetoothHelper.convertCharacteristicToJSONString(characteristic);
intent.putExtra("result", result);
BluetoothPlugin.sendBleBroadcast(intent);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
ALog.i(LOG_TAG, "onDescriptorRead enter.");
Intent intent = new Intent(BluetoothPlugin.BLE_CLIENT_READ_DESCRIPTOR);
intent.putExtra("appId", appId_);
String result = BluetoothHelper.convertDescriptorToJSONString(descriptor);
if (status == BluetoothGatt.GATT_SUCCESS) {
intent.putExtra("status", OH_BT_STATUS_SUCCESS);
} else {
intent.putExtra("status", OH_BT_STATUS_FAIL);
}
intent.putExtra("result", result);
BluetoothPlugin.sendBleBroadcast(intent);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
ALog.i(LOG_TAG, "onDescriptorWrite enter.");
Intent intent = new Intent(BluetoothPlugin.BLE_CLIENT_WRITE_DESCRIPTOR);
intent.putExtra("appId", appId_);
String result = BluetoothHelper.convertDescriptorToJSONString(descriptor);
if (status == BluetoothGatt.GATT_SUCCESS) {
intent.putExtra("status", OH_BT_STATUS_SUCCESS);
} else {
intent.putExtra("status", OH_BT_STATUS_FAIL);
}
intent.putExtra("result", result);
BluetoothPlugin.sendBleBroadcast(intent);
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
Intent intent = new Intent(BluetoothPlugin.BLE_CLIENT_SET_MTU);
intent.putExtra("appId", appId_);
if (status == BluetoothGatt.GATT_SUCCESS) {
ALog.i(LOG_TAG, "MTU updated to: " + mtu);
intent.putExtra("mtu", mtu);
intent.putExtra("status", OH_BT_STATUS_SUCCESS);
} else {
ALog.e(LOG_TAG, "MTU update failed: " + mtu);
intent.putExtra("mtu", mtu);
intent.putExtra("status", OH_BT_STATUS_FAIL);
}
BluetoothPlugin.sendBleBroadcast(intent);
}
};
public BluetoothGattClient(int appId) {
this.appId_ = appId;
}
public int connect(Context context, BluetoothDevice device) {
int errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
if (context == null) {
ALog.e(LOG_TAG, "connect failed, context is null");
return errorCode;
}
if (device == null) {
ALog.e(LOG_TAG, "connect failed, device is null");
return errorCode;
}
try{
bluetoothGatt_ = device.connectGatt(context, false, gattCallback_, BluetoothDevice.TRANSPORT_LE);
if (bluetoothGatt_ == null) {
ALog.e(LOG_TAG, "connect failed, bluetoothGatt is null");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
} else {
errorCode = BluetoothErrorCode.BT_NO_ERROR.getId();
}
} catch (IllegalArgumentException e) {
ALog.e(LOG_TAG, "connectGatt failed, error is " + e);
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
return errorCode;
}
public int discoverServices() {
if (bluetoothGatt_ != null) {
if (bluetoothGatt_.discoverServices()) {
return BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "native discoverServices result is false");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
} else {
ALog.e(LOG_TAG, "discoverServices failed, bluetoothGatt is null");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
}
public int disconnect() {
if (bluetoothGatt_ != null) {
bluetoothGatt_.disconnect();
return BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "disconnect failed, bluetoothGatt is null");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
}
public int close() {
if (bluetoothGatt_ != null) {
bluetoothGatt_.close();
ALog.e(LOG_TAG, "close " + BluetoothErrorCode.BT_NO_ERROR.getId());
return BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "close failed, bluetoothGatt is null");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
}
public int requestExchangeMtu(int mtu) {
if (bluetoothGatt_ == null) {
ALog.e(LOG_TAG, "requestExchangeMtu failed, bluetoothGatt_ is null");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
} else {
if (bluetoothGatt_.requestMtu(mtu)) {
return BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "native requestMtu failed.");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
}
}
private BluetoothGattCharacteristic getCharacterForServices(String sUuidString, String cUuidString) {
try {
if (sUuidString != null && cUuidString != null) {
UUID sUuid = UUID.fromString(sUuidString);
BluetoothGattService service = bluetoothGatt_.getService(sUuid);
if (service != null) {
UUID cUuid = UUID.fromString(cUuidString);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(cUuid);
return characteristic;
} else {
ALog.e(LOG_TAG, "getCharacterForServices failed, the service is null.");
}
} else {
ALog.e(LOG_TAG, "getCharacterForServices failed, sUuidString or cUuidString is null.");
}
} catch (IllegalArgumentException e) {
ALog.e(LOG_TAG, "UUID fromString failed for getCharacterForServices, error is " + e);
}
return null;
}
public int readCharacter(String sUuidString, String cUuidString) {
BluetoothGattCharacteristic characteristic = getCharacterForServices(sUuidString, cUuidString);
if (characteristic == null) {
ALog.e(LOG_TAG, "readCharacter failed, characteristic is null.");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
if (bluetoothGatt_.readCharacteristic(characteristic)) {
return BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "native readCharacteristic failed.");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
}
* Requests to enable or disable notifications/indications for a specific
* characteristic.
*
* @param sUuidString The UUID string of the service containing the
* characteristic.
* @param cUuidString The UUID string of the characteristic to configure.
* @param enable True to enable notifications/indications, false to
* disable.
* @return An error code indicating the result of the operation.
*/
public int requestNotification(String sUuidString, String cUuidString, boolean enable) {
if (bluetoothGatt_ == null) {
ALog.e(LOG_TAG, "requestNotification failed, bluetoothGatt_ is null.");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
BluetoothGattCharacteristic characteristic = getCharacterForServices(sUuidString, cUuidString);
if (characteristic == null) {
ALog.e(LOG_TAG, "requestNotification failed, characteristic is null.");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
if (bluetoothGatt_.setCharacteristicNotification(characteristic, enable)) {
return BluetoothErrorCode.BT_NO_ERROR.getId();
}
ALog.e(LOG_TAG, "native setCharacteristicNotification failed.");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
public int writeCharacter(String sUuidString, String cUuidString, byte[] value, int writeType) {
BluetoothGattCharacteristic characteristic = getCharacterForServices(sUuidString, cUuidString);
int errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
if (characteristic == null) {
return errorCode;
}
if (Build.VERSION.SDK_INT >= VERSION_TIRAMISU) {
try {
Method writeCharacteristicMethod = BluetoothGatt.class.getMethod("writeCharacteristic",
BluetoothGattCharacteristic.class, byte[].class, int.class);
errorCode = (int) writeCharacteristicMethod.invoke(bluetoothGatt_, characteristic, value, writeType);
if (errorCode == WRITE_SUCCESS) {
errorCode = BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "native writeCharacter failed.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
} catch (NoSuchMethodException e) {
ALog.e(LOG_TAG, "writeCharacter failed, NoSuchMethodException.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
} catch (IllegalAccessException e) {
ALog.e(LOG_TAG, "writeCharacter failed, IllegalAccessException.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
} catch (InvocationTargetException e) {
ALog.e(LOG_TAG, "writeCharacter failed, InvocationTargetException.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
} else {
characteristic.setValue(value);
characteristic.setWriteType(writeType);
if (bluetoothGatt_.writeCharacteristic(characteristic)) {
errorCode = BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "native readCharacteristic failed.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
}
return errorCode;
}
public int readDescriptor(String sUuidString, String cUuidString, String dUuidString) {
BluetoothGattDescriptor descriptor = getDescriptorForServices(sUuidString, cUuidString, dUuidString);
if (descriptor == null) {
ALog.e(LOG_TAG, "readDescriptor failed, descriptor is null.");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
if (bluetoothGatt_.readDescriptor(descriptor)) {
return BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "native readDescriptor failed.");
return BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
}
private BluetoothGattDescriptor getDescriptorForServices(
String sUuidString, String cUuidString, String dUuidString) {
if (sUuidString == null || cUuidString == null || dUuidString == null) {
ALog.e(LOG_TAG, "getDescriptorForServices failed, sUuidString or cUuidString or dUuidString is null.");
return null;
}
try {
UUID sUuid = UUID.fromString(sUuidString);
BluetoothGattService service = bluetoothGatt_.getService(sUuid);
if (service == null) {
ALog.e(LOG_TAG, "getDescriptorForServices failed, the service is null.");
return null;
}
UUID cUuid = UUID.fromString(cUuidString);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(cUuid);
if (characteristic == null) {
ALog.e(LOG_TAG, "getDescriptorForServices failed, the characteristic is null.");
return null;
}
UUID dUuid = UUID.fromString(dUuidString);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(dUuid);
return descriptor;
} catch (IllegalArgumentException e) {
ALog.e(LOG_TAG, "UUID fromString failed for getDescriptorForServices, error is " + e);
return null;
}
}
public int writeDescriptor(String sUuidString, String cUuidString, String dUuidString, byte[] value) {
BluetoothGattDescriptor descriptor = getDescriptorForServices(sUuidString, cUuidString, dUuidString);
int errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
if (descriptor == null) {
return errorCode;
}
if (Build.VERSION.SDK_INT >= VERSION_TIRAMISU) {
try {
Method writeDescriptorMethod =
BluetoothGatt.class.getMethod("writeDescriptor", BluetoothGattDescriptor.class, byte[].class);
errorCode = (int) writeDescriptorMethod.invoke(bluetoothGatt_, descriptor, value);
if (errorCode == WRITE_SUCCESS) {
errorCode = BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "native writeDescriptor failed.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
} catch (NoSuchMethodException e) {
ALog.e(LOG_TAG, "writeDescriptor failed, NoSuchMethodException.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
} catch (IllegalAccessException e) {
ALog.e(LOG_TAG, "writeDescriptor failed, IllegalAccessException.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
} catch (InvocationTargetException e) {
ALog.e(LOG_TAG, "writeDescriptor failed, InvocationTargetException.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
} else {
descriptor.setValue(value);
if (bluetoothGatt_.writeDescriptor(descriptor)) {
errorCode = BluetoothErrorCode.BT_NO_ERROR.getId();
} else {
ALog.e(LOG_TAG, "native writeDescriptor failed.");
errorCode = BluetoothErrorCode.BT_ERR_INTERNAL_ERROR.getId();
}
}
return errorCode;
}
}