From 0af3bed4a67ddb9007d3df23ddfb4e1ca57f9eff Mon Sep 17 00:00:00 2001 From: shenyangsi Date: Tue, 14 Jan 2025 21:01:55 +0800 Subject: [PATCH] test_suite: add BluetoothTest APP based on AndroidStudio bug: v/52280 Signed-off-by: shenyangsi --- tools/test_suite/android/.gitignore | 16 ++ tools/test_suite/android/app/.gitignore | 1 + tools/test_suite/android/app/build.gradle | 52 ++++ .../test_suite/android/app/proguard-rules.pro | 21 ++ .../ExampleInstrumentedTest.java | 25 ++ .../android/app/src/main/AndroidManifest.xml | 52 ++++ .../src/main/ic_launcher-velabluetooth.png | Bin 0 -> 9162 bytes .../openvela/bluetoothtest/MainActivity.java | 127 +++++++++ .../bluetoothtest/ble/BleCentralActivity.java | 132 +++++++++ .../ble/BlePeripheralActivity.java | 112 ++++++++ .../bluetoothtest/ble/BleScanActivity.java | 106 +++++++ .../bluetoothtest/ble/BleScanAdapter.java | 264 ++++++++++++++++++ .../bluetoothtest/ble/GattClientAdapter.java | 226 +++++++++++++++ .../ble/GattClientCharAdapter.java | 85 ++++++ .../bredr/BredrInquiryActivity.java | 105 +++++++ .../bredr/BredrInquiryAdapter.java | 187 +++++++++++++ .../res/drawable/ic_launcher_background.xml | 74 +++++ .../res/drawable/ic_launcher_foreground.xml | 30 ++ .../main/res/layout/activity_ble_central.xml | 37 +++ .../res/layout/activity_ble_peripheral.xml | 46 +++ .../src/main/res/layout/activity_ble_scan.xml | 43 +++ .../res/layout/activity_bredr_inquiry.xml | 43 +++ .../app/src/main/res/layout/activity_main.xml | 54 ++++ .../src/main/res/layout/item_gatt_element.xml | 47 ++++ .../src/main/res/layout/item_gatt_service.xml | 35 +++ .../main/res/layout/item_inquiry_result.xml | 69 +++++ .../src/main/res/layout/item_scan_result.xml | 69 +++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1744 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 1886 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 3548 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1024 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 938 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2186 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 2380 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 2308 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 4866 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 3654 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 4102 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 7732 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 5164 bytes .../ic_launcher_foreground.webp | Bin 0 -> 5398 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 10860 bytes .../app/src/main/res/values-night/themes.xml | 7 + .../app/src/main/res/values/colors.xml | 12 + .../res/values/ic_launcher_background.xml | 4 + .../app/src/main/res/values/strings.xml | 11 + .../app/src/main/res/values/themes.xml | 11 + .../app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 ++ .../xiaomi/velabluetooth/ExampleUnitTest.java | 17 ++ tools/test_suite/android/build.gradle | 9 + tools/test_suite/android/core/.gitignore | 1 + tools/test_suite/android/core/build.gradle | 33 +++ .../android/core/proguard-rules.pro | 21 ++ .../android/core/src/main/AndroidManifest.xml | 8 + .../bluetooth/BluetoothDiscoveryObserver.java | 94 +++++++ .../bluetooth/BluetoothStateObserver.java | 70 +++++ .../java/com/openvela/bluetooth/BtDevice.java | 129 +++++++++ .../bluetooth/adapter/RecyclerAdapter.java | 54 ++++ .../bluetooth/adapter/RecyclerViewHolder.java | 74 +++++ .../callback/BleConnectCallback.java | 13 + .../callback/BluetoothDiscoveryCallback.java | 18 ++ .../callback/BluetoothStateCallback.java | 7 + .../core/src/main/res/values/strings.xml | 3 + tools/test_suite/android/gradle.properties | 23 ++ .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43462 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + tools/test_suite/android/gradlew | 249 +++++++++++++++++ tools/test_suite/android/gradlew.bat | 92 ++++++ tools/test_suite/android/settings.gradle | 19 ++ 72 files changed, 3086 insertions(+) create mode 100755 tools/test_suite/android/.gitignore create mode 100755 tools/test_suite/android/app/.gitignore create mode 100755 tools/test_suite/android/app/build.gradle create mode 100755 tools/test_suite/android/app/proguard-rules.pro create mode 100755 tools/test_suite/android/app/src/androidTest/java/com/xiaomi/velabluetooth/ExampleInstrumentedTest.java create mode 100755 tools/test_suite/android/app/src/main/AndroidManifest.xml create mode 100755 tools/test_suite/android/app/src/main/ic_launcher-velabluetooth.png create mode 100755 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java create mode 100755 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleCentralActivity.java create mode 100755 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BlePeripheralActivity.java create mode 100755 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanActivity.java create mode 100755 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanAdapter.java create mode 100755 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientAdapter.java create mode 100644 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientCharAdapter.java create mode 100755 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryActivity.java create mode 100755 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryAdapter.java create mode 100755 tools/test_suite/android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100755 tools/test_suite/android/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100755 tools/test_suite/android/app/src/main/res/layout/activity_ble_central.xml create mode 100755 tools/test_suite/android/app/src/main/res/layout/activity_ble_peripheral.xml create mode 100755 tools/test_suite/android/app/src/main/res/layout/activity_ble_scan.xml create mode 100755 tools/test_suite/android/app/src/main/res/layout/activity_bredr_inquiry.xml create mode 100755 tools/test_suite/android/app/src/main/res/layout/activity_main.xml create mode 100644 tools/test_suite/android/app/src/main/res/layout/item_gatt_element.xml create mode 100755 tools/test_suite/android/app/src/main/res/layout/item_gatt_service.xml create mode 100755 tools/test_suite/android/app/src/main/res/layout/item_inquiry_result.xml create mode 100755 tools/test_suite/android/app/src/main/res/layout/item_scan_result.xml create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100755 tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100755 tools/test_suite/android/app/src/main/res/values-night/themes.xml create mode 100755 tools/test_suite/android/app/src/main/res/values/colors.xml create mode 100755 tools/test_suite/android/app/src/main/res/values/ic_launcher_background.xml create mode 100755 tools/test_suite/android/app/src/main/res/values/strings.xml create mode 100755 tools/test_suite/android/app/src/main/res/values/themes.xml create mode 100755 tools/test_suite/android/app/src/main/res/xml/backup_rules.xml create mode 100755 tools/test_suite/android/app/src/main/res/xml/data_extraction_rules.xml create mode 100755 tools/test_suite/android/app/src/test/java/com/xiaomi/velabluetooth/ExampleUnitTest.java create mode 100755 tools/test_suite/android/build.gradle create mode 100755 tools/test_suite/android/core/.gitignore create mode 100755 tools/test_suite/android/core/build.gradle create mode 100755 tools/test_suite/android/core/proguard-rules.pro create mode 100755 tools/test_suite/android/core/src/main/AndroidManifest.xml create mode 100755 tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothDiscoveryObserver.java create mode 100755 tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothStateObserver.java create mode 100755 tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BtDevice.java create mode 100755 tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/adapter/RecyclerAdapter.java create mode 100755 tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/adapter/RecyclerViewHolder.java create mode 100755 tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BleConnectCallback.java create mode 100755 tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothDiscoveryCallback.java create mode 100755 tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothStateCallback.java create mode 100755 tools/test_suite/android/core/src/main/res/values/strings.xml create mode 100755 tools/test_suite/android/gradle.properties create mode 100755 tools/test_suite/android/gradle/wrapper/gradle-wrapper.jar create mode 100755 tools/test_suite/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 tools/test_suite/android/gradlew create mode 100755 tools/test_suite/android/gradlew.bat create mode 100755 tools/test_suite/android/settings.gradle diff --git a/tools/test_suite/android/.gitignore b/tools/test_suite/android/.gitignore new file mode 100755 index 0000000..c7fb9e3 --- /dev/null +++ b/tools/test_suite/android/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/ +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/tools/test_suite/android/app/.gitignore b/tools/test_suite/android/app/.gitignore new file mode 100755 index 0000000..796b96d --- /dev/null +++ b/tools/test_suite/android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tools/test_suite/android/app/build.gradle b/tools/test_suite/android/app/build.gradle new file mode 100755 index 0000000..b84f8dd --- /dev/null +++ b/tools/test_suite/android/app/build.gradle @@ -0,0 +1,52 @@ +apply plugin: 'com.android.application' + +android { + namespace 'com.openvela.bluetoothtest' + compileSdk 34 + + defaultConfig { + applicationId "com.openvela.bluetoothtest" + minSdk 29 + targetSdk 34 + versionName "1.0" + + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + vectorDrawables.useSupportLibrary = true + multiDexEnabled true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + debug { + jniDebuggable true + } + } + + android.applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "BluetoothTest_${buildType.name}_v${defaultConfig.versionName}.apk" + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + + implementation project(':core') + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.12.0' + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" + +} + diff --git a/tools/test_suite/android/app/proguard-rules.pro b/tools/test_suite/android/app/proguard-rules.pro new file mode 100755 index 0000000..481bb43 --- /dev/null +++ b/tools/test_suite/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/tools/test_suite/android/app/src/androidTest/java/com/xiaomi/velabluetooth/ExampleInstrumentedTest.java b/tools/test_suite/android/app/src/androidTest/java/com/xiaomi/velabluetooth/ExampleInstrumentedTest.java new file mode 100755 index 0000000..d78645e --- /dev/null +++ b/tools/test_suite/android/app/src/androidTest/java/com/xiaomi/velabluetooth/ExampleInstrumentedTest.java @@ -0,0 +1,25 @@ +package com.openvela.bluetoothtest; + +import android.content.Context; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.openvela.bluetoothtest", appContext.getPackageName()); + } +} diff --git a/tools/test_suite/android/app/src/main/AndroidManifest.xml b/tools/test_suite/android/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..cdef959 --- /dev/null +++ b/tools/test_suite/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/ic_launcher-velabluetooth.png b/tools/test_suite/android/app/src/main/ic_launcher-velabluetooth.png new file mode 100755 index 0000000000000000000000000000000000000000..64530471abe1015b52e037e27246be32b375d08b GIT binary patch literal 9162 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 Htt0;*%KLM; literal 0 HcmV?d00001 diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java new file mode 100755 index 0000000..16dba9a --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java @@ -0,0 +1,127 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest; + +import java.util.ArrayList; +import java.util.List; + +import android.Manifest; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import android.bluetooth.BluetoothAdapter; + +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AppCompatActivity; + +import com.openvela.bluetooth.BluetoothStateObserver; +import com.openvela.bluetooth.callback.BluetoothStateCallback; +import com.openvela.bluetoothtest.ble.BleScanActivity; +import com.openvela.bluetoothtest.ble.BlePeripheralActivity; +import com.openvela.bluetoothtest.bredr.BredrInquiryActivity; + +public class MainActivity extends AppCompatActivity { + private final String TAG = MainActivity.class.getSimpleName(); + private final int REQUEST_ENABLE_BT = 1; + + private LinearLayout llBluetoothAdapterTip; + private BluetoothStateObserver btStateObserver; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + initView(); + requestBluetoothPermission(); + listenBluetoothState(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + btStateObserver.unregisterReceiver(); + } + + private void initView() { + llBluetoothAdapterTip = findViewById(R.id.ll_adapter_tip); + TextView tvAdapterStates = findViewById(R.id.tv_adapter_states); + + tvAdapterStates.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE_BT); + } + }); + } + + @RequiresApi(api = Build.VERSION_CODES.S) + private void requestBluetoothPermission() { + List permissions = new ArrayList<>(); + permissions.add(Manifest.permission.BLUETOOTH_SCAN); + permissions.add(Manifest.permission.BLUETOOTH_ADVERTISE); + permissions.add(Manifest.permission.BLUETOOTH_CONNECT); + permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); + permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); + + registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), map -> { + if (!isBluetoothEnabled()) { + llBluetoothAdapterTip.setVisibility(View.VISIBLE); + } + }).launch(permissions.toArray(new String[0])); + } + + private void listenBluetoothState() { + btStateObserver = new BluetoothStateObserver(this); + btStateObserver.registerReceiver(new BluetoothStateCallback() { + @Override + public void onEnabled() { + Log.i(TAG, "BluetoothAdapter is enabled!"); + llBluetoothAdapterTip.setVisibility(View.GONE); + } + + @Override + public void onDisabled() { + Log.i(TAG, "BluetoothAdapter is disabled!"); + llBluetoothAdapterTip.setVisibility(View.VISIBLE); + } + }); + } + + private boolean isBluetoothEnabled() { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); + } + + public void entryBredrInquiryActivity(View view) { + startActivity(new Intent(this, BredrInquiryActivity.class)); + } + + public void entryBleCentralActivity(View view) { + startActivity(new Intent(this, BleScanActivity.class)); + } + + public void entryBlePeripheralActivity(View view) { + startActivity(new Intent(this, BlePeripheralActivity.class)); + } +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleCentralActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleCentralActivity.java new file mode 100755 index 0000000..fb073f7 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleCentralActivity.java @@ -0,0 +1,132 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest.ble; + +import android.os.Bundle; +import android.widget.Button; +import android.widget.TextView; + +import android.bluetooth.BluetoothProfile; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BleConnectCallback; +import com.openvela.bluetoothtest.R; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class BleCentralActivity extends AppCompatActivity { + private final String TAG = BleCentralActivity.class.getSimpleName(); + public static final String EXTRA_TAG = "device"; + private TextView tvConnectState; + private Button btnConnect; + private @NotNull BtDevice currentDevice; + private GattClientAdapter gattClientAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ble_central); + gattClientAdapter = new GattClientAdapter(this); + initView(); + + currentDevice = getIntent().getParcelableExtra(EXTRA_TAG); + getSupportActionBar().setSubtitle(currentDevice.getAddress()); + gattClientAdapter.connect(currentDevice, connectCallback); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (currentDevice.getConnectionState() != BluetoothProfile.STATE_CONNECTING) { + gattClientAdapter.cancelConnect(currentDevice); + } else if (currentDevice.getConnectionState() != BluetoothProfile.STATE_CONNECTED){ + gattClientAdapter.disconnect(currentDevice); + } + } + + private void initView() { + tvConnectState = findViewById(R.id.tv_connect_state); + btnConnect = findViewById(R.id.btn_connect); + RecyclerView recyclerView = findViewById(R.id.recyclerView); + + btnConnect.setOnClickListener(v -> { + if (currentDevice.getConnectionState() == BluetoothProfile.STATE_DISCONNECTED) { + gattClientAdapter.connect(currentDevice, connectCallback); + } else if (currentDevice.getConnectionState() == BluetoothProfile.STATE_CONNECTING) { + gattClientAdapter.cancelConnect(currentDevice); + } else { + gattClientAdapter.disconnect(currentDevice); + } + }); + + recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL)); + recyclerView.getItemAnimator().setChangeDuration(300); + recyclerView.getItemAnimator().setMoveDuration(300); + recyclerView.setAdapter(gattClientAdapter); + } + + private final BleConnectCallback connectCallback = new BleConnectCallback() { + @Override + public void onConnectionChanged(String address, int newState) { + if (!address.equals(currentDevice.getAddress())) { + return; + } + currentDevice.setConnectionState(newState); + if (newState == BluetoothProfile.STATE_CONNECTED) { + tvConnectState.setText("Connected"); + btnConnect.setText("DISCONNECT"); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED){ + tvConnectState.setText("Disconnected"); + btnConnect.setText("CONNECT"); + } else if (newState == BluetoothProfile.STATE_CONNECTING) { + tvConnectState.setText("Connecting..."); + btnConnect.setText("DISCONNECT"); + } else if (newState == BluetoothProfile.STATE_DISCONNECTING){ + tvConnectState.setText("Disconnecting..."); + btnConnect.setText("DISCONNECT"); + } + } + + @Override + public void onConnectFailed(String address, int errorCode) { + super.onConnectFailed(address, errorCode); + if (errorCode == BleConnectCallback.FAILED_DEVICE_NOT_FOUND) { + tvConnectState.setText("Connect Failed:" + "device not found"); + } else if (errorCode == BleConnectCallback.FAILED_TIMEOUT) { + tvConnectState.setText("Connect Failed:" + "timeout"); + } else { + tvConnectState.setText("Connect Failed:" + errorCode); + } + currentDevice.setConnectionState(BluetoothProfile.STATE_DISCONNECTED); + btnConnect.setText("CONNECT"); + } + + @Override + public void onServicesDiscovered(String address) { + super.onServicesDiscovered(address); + tvConnectState.setText("Discovered"); + } + }; + +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BlePeripheralActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BlePeripheralActivity.java new file mode 100755 index 0000000..18ca4ca --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BlePeripheralActivity.java @@ -0,0 +1,112 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest.ble; + +import android.os.Bundle; +import android.util.Log; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Button; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.le.AdvertiseCallback; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.BluetoothLeAdvertiser; + +import androidx.appcompat.app.AppCompatActivity; + +import com.openvela.bluetoothtest.R; + +public class BlePeripheralActivity extends AppCompatActivity { + private final String TAG = BlePeripheralActivity.class.getSimpleName(); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private volatile BluetoothLeAdvertiser bluetoothAdvertiser; + private TextView tvAdvState; + private Button btnAdv; + private EditText etAdvName; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ble_peripheral); + initView(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (isAdvertising()) { + stopAdvertising(); + } + } + + private void initView() { + tvAdvState = findViewById(R.id.tv_adv_state); + btnAdv = findViewById(R.id.btn_adv); + etAdvName = findViewById(R.id.et_adv_name); + + btnAdv.setOnClickListener(v -> { + if (isAdvertising()) { + stopAdvertising(); + tvAdvState.setText("Advertise Stopped"); + btnAdv.setText("START ADVERTISE"); + } else { + startAdvertising(etAdvName.getText().toString().getBytes()); + } + }); + } + + private boolean isAdvertising() { + return (bluetoothAdvertiser != null); + } + + private void startAdvertising(final byte[] payload) { + AdvertiseSettings advertiseSettings = new AdvertiseSettings.Builder() + .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) + .setConnectable(true) + .setTimeout(0) + .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) + .build(); + AdvertiseData advertiseData = new AdvertiseData.Builder() + .addManufacturerData(0xFF00, payload) + .setIncludeDeviceName(true) + .build(); + + bluetoothAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); + bluetoothAdvertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback); + } + + public void stopAdvertising() { + bluetoothAdvertiser.stopAdvertising(advertiseCallback); + bluetoothAdvertiser = null; + } + + private final AdvertiseCallback advertiseCallback = new AdvertiseCallback() { + @Override + public void onStartSuccess(AdvertiseSettings settingsInEffect) { + tvAdvState.setText("Advertising..."); + btnAdv.setText("STOP ADVERTISE"); + } + + @Override + public void onStartFailure(int errorCode) { + tvAdvState.setText("Advertise Failed: " + errorCode); + Log.e(TAG, "onAdvStartFailure: " + errorCode); + } + }; +} \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanActivity.java new file mode 100755 index 0000000..30d82c6 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanActivity.java @@ -0,0 +1,106 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest.ble; + +import android.os.Bundle; +import android.util.Log; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Button; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; +import com.openvela.bluetoothtest.R; + +public class BleScanActivity extends AppCompatActivity { + private final String TAG = BleScanActivity.class.getSimpleName(); + private static final long BLE_SCAN_PERIOD_MS = 12 * 1000; + private TextView tvScanState; + private Button btnScan; + private EditText etFilter; + private BleScanAdapter bleScanAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ble_scan); + bleScanAdapter = new BleScanAdapter(this); + initView(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (bleScanAdapter.isScanning()) { + bleScanAdapter.stopScan(); + } + } + + private void initView() { + tvScanState = findViewById(R.id.tv_scan_state); + btnScan = findViewById(R.id.btn_scan); + etFilter = findViewById(R.id.et_filter); + RecyclerView recyclerView = findViewById(R.id.recyclerView); + + btnScan.setOnClickListener(v -> { + if (bleScanAdapter.isScanning()) { + bleScanAdapter.stopScan(); + } else { + bleScanAdapter.startScan(new String[]{etFilter.getText().toString()}, BLE_SCAN_PERIOD_MS, discoveryCallback); + } + }); + + recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); + recyclerView.getItemAnimator().setChangeDuration(300); + recyclerView.getItemAnimator().setMoveDuration(300); + recyclerView.setAdapter(bleScanAdapter); + } + + private final BluetoothDiscoveryCallback discoveryCallback = new BluetoothDiscoveryCallback() { + @Override + public void onDiscoveryResult(final BtDevice device) {} + + @Override + public void onStart() { + super.onStart(); + tvScanState.setText("Scanning..."); + btnScan.setText("STOP SCAN"); + } + + @Override + public void onStop() { + super.onStop(); + tvScanState.setText("Scan Stopped"); + btnScan.setText("START SCAN"); + } + + @Override + public void onDiscoveryFailed(int errorCode) { + super.onDiscoveryFailed(errorCode); + tvScanState.setText("Scan Failed: " + errorCode); + Log.e(TAG, "onDiscoveryFailed: " + errorCode); + } + }; + +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanAdapter.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanAdapter.java new file mode 100755 index 0000000..0c7420d --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanAdapter.java @@ -0,0 +1,264 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest.ble; + +import java.util.ArrayList; +import java.util.List; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.ParcelUuid; +import android.text.TextUtils; +import android.view.View; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; + +import androidx.core.os.HandlerCompat; + +import com.openvela.bluetooth.adapter.RecyclerAdapter; +import com.openvela.bluetooth.adapter.RecyclerViewHolder; +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; +import com.openvela.bluetoothtest.R; + +public class BleScanAdapter extends RecyclerAdapter { + private final String TAG = BleScanAdapter.class.getSimpleName(); + private static final String SCAN_TIMEOUT_TOKEN = "scan_timeout_token"; + private static final int RSSI_UPDATE_INTERVAL_MS = 2 * 1000; + private final Handler handler = new Handler(Looper.myLooper()); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private volatile BluetoothLeScanner bluetoothScanner; + private ScanSettings scanSettings; + private String[] scanFilters; + private BluetoothDiscoveryCallback bleDiscoveryCallback; + private int view_position = -1; + + public BleScanAdapter(Context context) { + super(context, R.layout.item_scan_result, new ArrayList<>()); + } + + @SuppressLint("DefaultLocale") + @Override + public void onBindViewHolderItem(RecyclerViewHolder viewHolder, BtDevice device) { + viewHolder.setText(R.id.tv_address, device.getAddress()); + viewHolder.setText(R.id.tv_rssi, String.format("%ddBm", device.getRssi())); + if (device.getName() == null){ + viewHolder.setText(R.id.tv_name, "Unknown"); + } else { + viewHolder.setText(R.id.tv_name, device.getName()); + } + + if (viewHolder.getBindingAdapterPosition() == view_position){ + viewHolder.setVisibility(R.id.ll_detail, View.VISIBLE); + + ScanRecord scanRecord = device.getScanRecord(); + if (scanRecord != null) { + // Flags + if (scanRecord.getAdvertiseFlags() >= 0) { + viewHolder.setVisibility(R.id.tv_flags, View.VISIBLE); + viewHolder.setText(R.id.tv_flags, "Flags: 0x" + String.format("%02x", scanRecord.getAdvertiseFlags())); + } + // Local Name + if (scanRecord.getDeviceName() != null) { + viewHolder.setVisibility(R.id.tv_local_name, View.VISIBLE); + viewHolder.setText(R.id.tv_local_name, "Local Name: "+ scanRecord.getDeviceName()); + } + // Service UUIDs + List serviceUuids = scanRecord.getServiceUuids(); + if (serviceUuids != null && !serviceUuids.isEmpty()){ + viewHolder.setVisibility(R.id.tv_uuid, View.VISIBLE); + viewHolder.setText(R.id.tv_uuid, String.format("Service Uuids: %s", TextUtils.join(", ", serviceUuids))); + } + // Raw Data + byte[] rawData = scanRecord.getBytes(); + int totalLength = 0; + do { + totalLength += rawData[totalLength] + 1; + } while (rawData[totalLength] != 0); + + StringBuilder builder = new StringBuilder(); + builder.append("RAW: 0x"); + for (int i = 0; i < totalLength; i++) { + builder.append(String.format("%02x", rawData[i])); + } + viewHolder.setText(R.id.tv_raw_data, builder.toString()); + } + + } else { + viewHolder.setVisibility(R.id.ll_detail, View.GONE); + } + + if (device.isConnectable()) { + viewHolder.setVisibility(R.id.tv_connect, View.VISIBLE); + viewHolder.setOnClickListener(R.id.tv_connect, v -> { + if (isScanning()) { + stopScan(); + } + mContext.startActivity(new Intent(mContext, BleCentralActivity.class) + .putExtra(BleCentralActivity.EXTRA_TAG, device)); + }); + } else { + viewHolder.setVisibility(R.id.tv_connect, View.GONE); + } + + viewHolder.setOnClickListener(v -> { + if(viewHolder.getBindingAdapterPosition() == view_position) { + notifyItemChanged(view_position); + view_position = -1; + } else { + notifyItemChanged(view_position); + view_position = viewHolder.getBindingAdapterPosition(); + notifyItemChanged(view_position); + } + }); + } + + public boolean isScanning() { + return (bluetoothScanner != null); + } + + @SuppressLint("NotifyDataSetChanged") + public void startScan(final String[] scanFilters, long scanPeriod, BluetoothDiscoveryCallback callback) { + bleDiscoveryCallback = callback; + + if (!bluetoothAdapter.isEnabled()) { + if (bleDiscoveryCallback != null) { + bleDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_INTERNAL_ERROR); + return; + } + } + + if (isScanning()) { + if (bleDiscoveryCallback != null){ + bleDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_ALREADY_STARTED); + } + return; + } + + bluetoothScanner = bluetoothAdapter.getBluetoothLeScanner(); + if (bluetoothScanner != null) { + super.mItems.clear(); + super.notifyDataSetChanged(); + startScanTimer(scanPeriod); + + this.scanFilters = scanFilters; + scanSettings = new ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setReportDelay(0L) + .build(); + bluetoothScanner.startScan(null, scanSettings, scanCallback); + bleDiscoveryCallback.onStart(); + } else { + bleDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_INTERNAL_ERROR); + } + } + + public void stopScan() { + if (!isScanning()) { + return; + } + + cancelScanTimer(); + bluetoothScanner.stopScan(scanCallback); + bleDiscoveryCallback.onStop(); + bluetoothScanner = null; + } + + private void cancelScanTimer() { + handler.removeCallbacksAndMessages(SCAN_TIMEOUT_TOKEN); + } + + private void startScanTimer(long scanPeriod) { + cancelScanTimer(); + + if (scanPeriod >= 0){ + HandlerCompat.postDelayed(handler, () -> { + if (isScanning()) { + stopScan(); + } + }, SCAN_TIMEOUT_TOKEN, scanPeriod); + } + } + + private final ScanCallback scanCallback = new ScanCallback() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onScanResult(final int callbackType, final ScanResult result) { + synchronized (this) { + final String address = result.getDevice().getAddress(); + final String name = result.getDevice().getName(); + boolean found = true; + + if (scanFilters != null) { + found = false; + for (String filter : scanFilters) { + if ((address != null && address.toLowerCase().contains(filter.toLowerCase())) || + (name != null && name.toLowerCase().contains(filter.toLowerCase()))) { + found = true; + break; + } + } + } + if (!found) { + return; + } + + for (int i = 0; i < getItemCount(); i++) { + BtDevice device = mItems.get(i); + if (TextUtils.equals(device.getAddress(), address)) { + if (device.getRssi() != result.getRssi() && System.currentTimeMillis() - device.getRssiUpdateTime() > RSSI_UPDATE_INTERVAL_MS) { + device.setRssi(result.getRssi()); + device.setRssiUpdateTime(System.currentTimeMillis()); + mItems.set(i, device); + notifyItemChanged(i); + } + return; + } + } + + BtDevice newDevice = new BtDevice(address, name); + newDevice.setConnectable(result.isConnectable()); + newDevice.setScanRecord(result.getScanRecord()); + newDevice.setRssi(result.getRssi()); + newDevice.setRssiUpdateTime(System.currentTimeMillis()); + mItems.add(newDevice); + notifyDataSetChanged(); + + if (bleDiscoveryCallback != null) { + bleDiscoveryCallback.onDiscoveryResult(newDevice); + } + } + } + + @Override + public void onScanFailed(final int errorCode) { + stopScan(); + if (bleDiscoveryCallback != null) { + bleDiscoveryCallback.onDiscoveryFailed(errorCode); + } + } + }; + +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientAdapter.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientAdapter.java new file mode 100755 index 0000000..26caa75 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientAdapter.java @@ -0,0 +1,226 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest.ble; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import android.view.View; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothProfile; + +import androidx.core.os.HandlerCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.openvela.bluetooth.adapter.RecyclerAdapter; +import com.openvela.bluetooth.adapter.RecyclerViewHolder; +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BleConnectCallback; +import com.openvela.bluetoothtest.R; + +public class GattClientAdapter extends RecyclerAdapter { + private final static String TAG = GattClientAdapter.class.getSimpleName(); + private static final String BASE_UUID_REGEX = "0000([0-9a-f][0-9a-f][0-9a-f][0-9a-f])-0000-1000-8000-00805f9b34fb"; + private static final int GATT_CONNECT_TIMEOUT_MS = 10 * 1000; + private final Handler handler = new Handler(Looper.myLooper()); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private final Map gattHashMap = new HashMap<>(); + private BleConnectCallback bleConnectCallback; + private int view_position = -1; + + public GattClientAdapter(Context context) { + super(context, R.layout.item_gatt_service, new ArrayList<>()); + } + + @Override + public void onBindViewHolderItem(RecyclerViewHolder viewHolder, BluetoothGattService gattService) { + // Service UUID + String serviceUuid = gattService.getUuid().toString(); + StringBuilder builder = new StringBuilder(); + builder.append("UUID: 0x"); + if (serviceUuid.toLowerCase().matches(BASE_UUID_REGEX)) { + builder.append(serviceUuid.substring(4, 8).toUpperCase()); + } else { + builder.append(serviceUuid); + } + viewHolder.setText(R.id.tv_service_uuid, builder.toString()); + // Service Type + if (gattService.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) { + viewHolder.setText(R.id.tv_service_type, "PRIMARY SERVICE"); + } else if (gattService.getType() == BluetoothGattService.SERVICE_TYPE_SECONDARY) { + viewHolder.setText(R.id.tv_service_type, "SECONDARY SERVICE"); + } else { + viewHolder.setText(R.id.tv_service_type, "UNKNOWN SERVICE"); + } + + if (viewHolder.getBindingAdapterPosition() == view_position){ + GattClientCharAdapter charAdapter = new GattClientCharAdapter(mContext, gattService.getCharacteristics()); + RecyclerView recyclerView = viewHolder.getView(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); + recyclerView.setAdapter(charAdapter); + + viewHolder.setVisibility(R.id.recyclerView, View.VISIBLE); + } else { + viewHolder.setVisibility(R.id.recyclerView, View.GONE); + } + + viewHolder.setOnClickListener(v -> { + if(viewHolder.getBindingAdapterPosition() == view_position) { + notifyItemChanged(view_position); + view_position = -1; + } else { + notifyItemChanged(view_position); + view_position = viewHolder.getBindingAdapterPosition(); + notifyItemChanged(view_position); + } + }); + } + + public void connect(BtDevice device, BleConnectCallback callback) { + bleConnectCallback = callback; + + String address = device.getAddress(); + final BluetoothDevice bluetoothdevice = bluetoothAdapter.getRemoteDevice(address); + if (bluetoothdevice == null) { + bleConnectCallback.onConnectFailed(address, BleConnectCallback.FAILED_DEVICE_NOT_FOUND); + return; + } + + BluetoothGatt bluetoothGatt; + bluetoothGatt = bluetoothdevice.connectGatt(mContext, false, gattCallback, BluetoothDevice.TRANSPORT_LE); + if (bluetoothGatt != null) { + startConnectTimer(address); + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_CONNECTING); + gattHashMap.put(address, bluetoothGatt); + } + } + + public void disconnect(BtDevice device) { + String address = device.getAddress(); + BluetoothGatt bluetoothGatt = gattHashMap.get(address); + + if (bluetoothGatt != null) { + cancelConnectTimer(address); + bluetoothGatt.disconnect(); + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_DISCONNECTING); + } + } + + public void cancelConnect(BtDevice device) { + String address = device.getAddress(); + BluetoothGatt bluetoothGatt = gattHashMap.get(address); + + if (bluetoothGatt != null) { + cancelConnectTimer(address); + bluetoothGatt.disconnect(); + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_DISCONNECTED); + } + } + + private void cancelConnectTimer(String address){ + handler.removeCallbacksAndMessages(address); + } + + private void startConnectTimer(String address) { + cancelConnectTimer(address); + + HandlerCompat.postDelayed(handler, () -> { + bleConnectCallback.onConnectFailed(address, BleConnectCallback.FAILED_TIMEOUT); + close(address); + }, address, GATT_CONNECT_TIMEOUT_MS); + } + + private void close(String address) { + BluetoothGatt bluetoothGatt = gattHashMap.get(address); + if (bluetoothGatt != null) { + bluetoothGatt.close(); + gattHashMap.remove(address); + } + } + + private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + BluetoothDevice device = gatt.getDevice(); + if (device == null){ + return; + } + + String address = device.getAddress(); + cancelConnectTimer(address); + + if (status != BluetoothGatt.GATT_SUCCESS) { + if (bleConnectCallback != null){ + bleConnectCallback.onConnectFailed(address, status); + } + close(address); + return; + } + if (newState == BluetoothProfile.STATE_CONNECTED) { + if (bleConnectCallback != null) { + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_CONNECTED); + } + gatt.discoverServices(); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + if (bleConnectCallback != null) { + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_DISCONNECTED); + } + close(address); + } + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + BluetoothDevice device = gatt.getDevice(); + if (device == null){ + return; + } + + String address = device.getAddress(); + if (status == BluetoothGatt.GATT_SUCCESS) { + if (bleConnectCallback != null) { + bleConnectCallback.onServicesDiscovered(address); + } + handler.post(() -> { + mItems.clear(); + mItems.addAll(gatt.getServices()); + notifyDataSetChanged(); + }); + } else { + Log.e(TAG, "onServicesDiscovered failed: " + status); + } + } + + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status){ + + } + }; + +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientCharAdapter.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientCharAdapter.java new file mode 100644 index 0000000..b1a71d8 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientCharAdapter.java @@ -0,0 +1,85 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest.ble; + +import java.util.List; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.View; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; + +import com.openvela.bluetooth.adapter.RecyclerAdapter; +import com.openvela.bluetooth.adapter.RecyclerViewHolder; +import com.openvela.bluetoothtest.R; + +public class GattClientCharAdapter extends RecyclerAdapter { + private final static String TAG = GattClientCharAdapter.class.getSimpleName(); + private static final String BASE_UUID_REGEX = "0000([0-9a-f][0-9a-f][0-9a-f][0-9a-f])-0000-1000-8000-00805f9b34fb"; + private final Handler handler = new Handler(Looper.myLooper()); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + public GattClientCharAdapter(Context context, List data) { + super(context, R.layout.item_gatt_element, data); + } + + @Override + public void onBindViewHolderItem(RecyclerViewHolder viewHolder, BluetoothGattCharacteristic gattChar) { + // Char UUID + String charUuid = gattChar.getUuid().toString(); + StringBuilder builder = new StringBuilder(); + builder.append("UUID: 0x"); + if (charUuid.toLowerCase().matches(BASE_UUID_REGEX)) { + builder.append(charUuid.substring(4, 8).toUpperCase()); + } else { + builder.append(charUuid); + } + viewHolder.setText(R.id.tv_char_uuid, builder.toString()); + + // Char Properties + int charProp = gattChar.getProperties(); + builder.setLength(0); + if ((charProp & BluetoothGattCharacteristic.PROPERTY_READ) != 0) { + builder.append("READ,"); + } + if ((charProp & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0) { + builder.append("WRITE,"); + } + if ((charProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) { + builder.append("WRITE_NO_RESPONSE,"); + } + if ((charProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) { + builder.append("NOTIFY,"); + } + if ((charProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) { + builder.append("INDICATE,"); + } + viewHolder.setText(R.id.tv_char_prop, String.format("Properties: %s", builder.toString())); + + if ((charProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) { + viewHolder.setVisibility(R.id.tv_write_tput, View.VISIBLE); + viewHolder.setOnClickListener(R.id.tv_write_tput, v -> { + Log.i(TAG, "Write Tput start!"); + }); + } + } +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryActivity.java new file mode 100755 index 0000000..ab27b30 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryActivity.java @@ -0,0 +1,105 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest.bredr; + +import android.os.Bundle; +import android.util.Log; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Button; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; +import com.openvela.bluetoothtest.R; + +public class BredrInquiryActivity extends AppCompatActivity { + private final String TAG = BredrInquiryActivity.class.getSimpleName(); + private static final long BREDR_INQUIRY_PERIOD_MS = 12 * 1000; + private TextView tvInquiryState; + private Button btnInquiry; + private EditText etFilter; + private BredrInquiryAdapter bredrInquiryAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bredr_inquiry); + bredrInquiryAdapter = new BredrInquiryAdapter(this); + initView(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (bredrInquiryAdapter.isDiscovering()) { + bredrInquiryAdapter.stopDiscovery(); + } + } + + private void initView() { + tvInquiryState = findViewById(R.id.tv_inquiry_state); + btnInquiry = findViewById(R.id.btn_inquiry); + etFilter = findViewById(R.id.et_filter); + RecyclerView recyclerView = findViewById(R.id.recyclerView); + + btnInquiry.setOnClickListener(v -> { + if (bredrInquiryAdapter.isDiscovering()) { + bredrInquiryAdapter.stopDiscovery(); + } else { + bredrInquiryAdapter.startDiscovery(new String[]{etFilter.getText().toString()}, BREDR_INQUIRY_PERIOD_MS, discoveryCallback); + } + }); + + recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); + recyclerView.getItemAnimator().setChangeDuration(300); + recyclerView.getItemAnimator().setMoveDuration(300); + recyclerView.setAdapter(bredrInquiryAdapter); + } + + private final BluetoothDiscoveryCallback discoveryCallback = new BluetoothDiscoveryCallback() { + @Override + public void onDiscoveryResult(final BtDevice device) {} + + @Override + public void onStart() { + super.onStart(); + tvInquiryState.setText("Inquiring..."); + btnInquiry.setText("STOP INQUIRY"); + } + + @Override + public void onStop() { + super.onStop(); + tvInquiryState.setText("Inquiry Stopped"); + btnInquiry.setText("START SCAN"); + } + + @Override + public void onDiscoveryFailed(int errorCode) { + super.onDiscoveryFailed(errorCode); + tvInquiryState.setText("Inquiry Failed: " + errorCode); + Log.e(TAG, "onDiscoveryFailed: " + errorCode); + } + }; +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryAdapter.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryAdapter.java new file mode 100755 index 0000000..b89fdad --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryAdapter.java @@ -0,0 +1,187 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * 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 com.openvela.bluetoothtest.bredr; + +import java.util.ArrayList; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.view.View; + +import android.bluetooth.BluetoothAdapter; + +import androidx.core.os.HandlerCompat; + +import com.openvela.bluetooth.adapter.RecyclerAdapter; +import com.openvela.bluetooth.adapter.RecyclerViewHolder; +import com.openvela.bluetooth.BluetoothDiscoveryObserver; +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; + +import com.openvela.bluetoothtest.R; + +public class BredrInquiryAdapter extends RecyclerAdapter { + private final String TAG = BredrInquiryAdapter.class.getSimpleName(); + private static final String DISCOVERY_TIMEOUT_TOKEN = "discovery_timeout_token"; + private static final int RSSI_UPDATE_INTERVAL_MS = 2 * 1000; + private final Handler handler = new Handler(Looper.myLooper()); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private volatile BluetoothDiscoveryObserver bluetoothDiscoveryObserver; + private String[] discoveryFilters; + private BluetoothDiscoveryCallback bluetoothDiscoveryCallback; + private int view_position = -1; + + public BredrInquiryAdapter(Context context) { + super(context, R.layout.item_inquiry_result, new ArrayList<>()); + bluetoothDiscoveryObserver = new BluetoothDiscoveryObserver(context); + bluetoothDiscoveryObserver.registerReceiver(discoveryCallback); + } + + @SuppressLint("DefaultLocale") + @Override + public void onBindViewHolderItem(RecyclerViewHolder viewHolder, BtDevice device) { + viewHolder.setText(R.id.tv_address, device.getAddress()); + viewHolder.setText(R.id.tv_rssi, String.format("%ddBm", device.getRssi())); + if (device.getName() == null){ + viewHolder.setText(R.id.tv_name, "Unknown"); + } else { + viewHolder.setText(R.id.tv_name, device.getName()); + } + + if (viewHolder.getBindingAdapterPosition() == view_position){ + + } else { + viewHolder.setVisibility(R.id.ll_detail, View.GONE); + } + } + + public boolean isDiscovering() { + return bluetoothDiscoveryObserver.isDiscovering(); + } + + public void startDiscovery(final String[] discoveryFilters, long discoveryPeriod, BluetoothDiscoveryCallback callback) { + bluetoothDiscoveryCallback = callback; + + if (!bluetoothAdapter.isEnabled()) { + if (bluetoothDiscoveryCallback != null) { + bluetoothDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_INTERNAL_ERROR); + return; + } + } + + if (isDiscovering()) { + if (bluetoothDiscoveryCallback != null){ + bluetoothDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_ALREADY_STARTED); + } + return; + } + + super.mItems.clear(); + startDiscoveryTimer(discoveryPeriod); + + this.discoveryFilters = discoveryFilters; + bluetoothDiscoveryObserver.startDiscovery(); + } + + public void stopDiscovery() { + if (!isDiscovering()) { + return; + } + + cancelDiscoveryTimer(); + bluetoothDiscoveryObserver.cancelDiscovery(); + } + + private void cancelDiscoveryTimer() { + handler.removeCallbacksAndMessages(DISCOVERY_TIMEOUT_TOKEN); + } + + private void startDiscoveryTimer(long discoveryPeriod) { + cancelDiscoveryTimer(); + + if (discoveryPeriod >= 0){ + HandlerCompat.postDelayed(handler, () -> { + if (isDiscovering()) { + stopDiscovery(); + } + }, DISCOVERY_TIMEOUT_TOKEN, discoveryPeriod); + } + } + + private final BluetoothDiscoveryCallback discoveryCallback = new BluetoothDiscoveryCallback() { + @Override + public void onDiscoveryResult(final BtDevice foundDevice) { + final String address = foundDevice.getAddress(); + final String name = foundDevice.getName(); + boolean found = true; + + if (discoveryFilters != null) { + found = false; + for (String filter : discoveryFilters) { + if ((address != null && address.toLowerCase().contains(filter.toLowerCase())) || + (name != null && name.toLowerCase().contains(filter.toLowerCase()))) { + found = true; + break; + } + } + } + if (!found) { + return; + } + + for (int i = 0; i < getItemCount(); i++) { + BtDevice device = mItems.get(i); + if (TextUtils.equals(device.getAddress(), address)) { + if (device.getRssi() != foundDevice.getRssi() && System.currentTimeMillis() - device.getRssiUpdateTime() > RSSI_UPDATE_INTERVAL_MS) { + device.setRssi(foundDevice.getRssi()); + device.setRssiUpdateTime(System.currentTimeMillis()); + mItems.set(i, device); + notifyItemChanged(i); + } + return; + } + } + + BtDevice newDevice = new BtDevice(address, name); + newDevice.setRssi(foundDevice.getRssi()); + newDevice.setRssiUpdateTime(System.currentTimeMillis()); + mItems.add(newDevice); + notifyDataSetChanged(); + + if (bluetoothDiscoveryCallback != null) { + bluetoothDiscoveryCallback.onDiscoveryResult(newDevice); + } + } + + @Override + public void onStart() { + if (bluetoothDiscoveryCallback != null) { + bluetoothDiscoveryCallback.onStart(); + } + } + + @Override + public void onStop() { + if (bluetoothDiscoveryCallback != null) { + bluetoothDiscoveryCallback.onStop(); + } + } + }; +} diff --git a/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_background.xml b/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100755 index 0000000..9ae8512 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100755 index 0000000..2b068d1 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_ble_central.xml b/tools/test_suite/android/app/src/main/res/layout/activity_ble_central.xml new file mode 100755 index 0000000..d3b9098 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_ble_central.xml @@ -0,0 +1,37 @@ + + + + + + + +