diff --git a/.clang-format b/.clang-format index 0b4b45a62c10bf56e4cc6a8f85b1d5cbea5915e1..d49e3528a1dca86b3af435811afca8bdeaa4a7a2 100644 --- a/.clang-format +++ b/.clang-format @@ -19,6 +19,28 @@ # accommodate for handling of the large legacy code base. # +# AOSP is based on google style with some modification commented below BasedOnStyle: Google -CommentPragmas: NOLINT:.* -DerivePointerAlignment: false + +--- +Language: Cpp +# AOSP ask for 4, but we are not ready to touch every single line +# IndentWidth: 4 # vs 2 +ColumnLimit: 100 # vs 80 +ContinuationIndentWidth: 8 # vs 4 +AccessModifierOffset: -2 # vs -1 Should be -4 when updating IndentWidth to 4 +Standard: c++20 # vs Auto +AllowShortIfStatementsOnASingleLine: Never # vs WithoutElse +AllowShortLoopsOnASingleLine: false # vs true +# AOSP suggest 1, but ask to follow clang-format settings. CppLint ask for 2 +# SpacesBeforeTrailingComments: 1 # vs 2 + +# Allow clang-format to automatically fix more things +RemoveSemicolon: true +InsertBraces: true +RemoveParentheses: ReturnStatement + +--- +Language: Java +# Java format is handled by google-java-format +DisableFormat: true diff --git a/Android.bp b/Android.bp index e403ac4334374d7e62d3fba7c91ce3251ac0ebb5..e88d6ab550663c690b388f625f84d7c4374146f1 100644 --- a/Android.bp +++ b/Android.bp @@ -57,7 +57,7 @@ filegroup { // this default contains. This also means that if you add a new option that isn't // documented by the name of this default, rename it. // -// - Try avoiding adding option that would not fit "future" targets, for exemple dependencies, +// - Try avoiding adding option that would not fit "future" targets, for example dependencies, // even if every modules of Bluetooth depends on a specific dependency it should be left out // from this default to not push it for future targets that might not need it. cc_defaults { @@ -73,3 +73,112 @@ cc_defaults { c_std: "c99", cpp_std: "c++20", } + +java_defaults { + name: "bluetooth_errorprone_rules", + errorprone: { + enabled: true, + javacflags: [ + "-Xep:AlmostJavadoc:ERROR", + "-Xep:AlreadyChecked:ERROR", + "-Xep:BadImport:ERROR", + "-Xep:CatchAndPrintStackTrace:ERROR", + "-Xep:CatchFail:ERROR", + "-Xep:CheckReturnValue:ERROR", + "-Xep:ClassCanBeStatic:ERROR", + "-Xep:DateFormatConstant:ERROR", + "-Xep:DirectInvocationOnMock:ERROR", + "-Xep:EmptyBlockTag:ERROR", + "-Xep:EmptyCatch:ERROR", + "-Xep:EqualsGetClass:ERROR", + "-Xep:EqualsHashCode:ERROR", + "-Xep:EqualsIncompatibleType:ERROR", + "-Xep:FallThrough:ERROR", + "-Xep:Finalize:ERROR", + "-Xep:FutureReturnValueIgnored:ERROR", + "-Xep:GuardedBy:ERROR", + "-Xep:HidingField:ERROR", + "-Xep:InconsistentHashCode:ERROR", + "-Xep:InlineFormatString:ERROR", + "-Xep:InlineMeInliner:ERROR", + "-Xep:InvalidBlockTag:ERROR", + "-Xep:InvalidInlineTag:ERROR", + "-Xep:InvalidParam:ERROR", + "-Xep:JavaUtilDate:ERROR", + "-Xep:JdkObsolete:ERROR", + "-Xep:LockOnNonEnclosingClassLiteral:ERROR", + "-Xep:LongFloatConversion:ERROR", + "-Xep:LoopOverCharArray:ERROR", + "-Xep:MissingCasesInEnumSwitch:ERROR", + "-Xep:MixedMutabilityReturnType:ERROR", + "-Xep:MockNotUsedInProduction:ERROR", + "-Xep:ModifiedButNotUsed:ERROR", + "-Xep:ModifyCollectionInEnhancedForLoop:ERROR", + "-Xep:NarrowCalculation:ERROR", + "-Xep:NarrowingCompoundAssignment:ERROR", + "-Xep:NonApiType:ERROR", + "-Xep:NonAtomicVolatileUpdate:ERROR", + "-Xep:NonCanonicalType:ERROR", + "-Xep:NotJavadoc:ERROR", + "-Xep:ObjectEqualsForPrimitives:ERROR", + "-Xep:OperatorPrecedence:ERROR", + "-Xep:ReferenceEquality:ERROR", + "-Xep:ReturnAtTheEndOfVoidFunction:ERROR", + "-Xep:StaticAssignmentInConstructor:ERROR", + "-Xep:StaticGuardedByInstance:ERROR", + "-Xep:StringCaseLocaleUsage:ERROR", + "-Xep:StringCharset:ERROR", + "-Xep:SynchronizeOnNonFinalField:ERROR", + "-Xep:ToStringReturnsNull:ERROR", + "-Xep:TruthConstantAsserts:ERROR", + "-Xep:TruthIncompatibleType:ERROR", + "-Xep:UndefinedEquals:ERROR", + "-Xep:UnnecessaryAssignment:ERROR", + "-Xep:UnnecessaryAsync:ERROR", + "-Xep:UnnecessaryStringBuilder:ERROR", + "-Xep:UnrecognisedJavadocTag:ERROR", + "-Xep:UnusedMethod:ERROR", + "-Xep:UnusedNestedClass:ERROR", + "-Xep:UnusedVariable:ERROR", + "-Xep:WaitNotInLoop:ERROR", + "-Xep:WakelockReleasedDangerously:ERROR", + + // Exclude generated files + "-XepExcludedPaths:.*/srcjars/.*", + + // The @InlineMe annotation is not available + // "-Xep:InlineMeSuggester:OFF", + ], + }, +} + +java_defaults { + name: "bluetooth_framework_errorprone_rules", + defaults: ["bluetooth_errorprone_rules"], + plugins: [ + "error_prone_android_framework", + ], + errorprone: { + javacflags: [ + "-Xep:AndroidFrameworkBinderIdentity:ERROR", + "-Xep:AndroidFrameworkBluetoothPermission:ERROR", + "-Xep:AndroidFrameworkCompatChange:ERROR", + "-Xep:AndroidFrameworkEfficientParcelable:ERROR", + "-Xep:AndroidFrameworkEfficientStrings:ERROR", + "-Xep:AndroidFrameworkPendingIntentMutability:ERROR", + "-Xep:AndroidFrameworkRequiresPermission:ERROR", + "-Xep:AndroidFrameworkRethrowFromSystem:ERROR", + "-Xep:AndroidFrameworkTargetSdk:ERROR", + "-Xep:AndroidHideInComments:ERROR", + + // After fixing this errorprone, we decided to not merge the change. + // It is not very readable and the benefits are minimal when looking + // at the size of the maps used in the Bluetooth application. + // See https://r.android.com/3200511 + "-Xep:AndroidFrameworkEfficientCollections:OFF", + + // Does not look pertinent in our situation + "-Xep:AndroidFrameworkEfficientXml:OFF", + ], + }, +} diff --git a/CPPLINT.cfg b/CPPLINT.cfg new file mode 100644 index 0000000000000000000000000000000000000000..cc062661c71c86524b7e04325cfa758571dfe361 --- /dev/null +++ b/CPPLINT.cfg @@ -0,0 +1,44 @@ +set noparent +linelength=100 +# Do not check access modifier indentation. +# CPPLint enforces +1, but our rule is no indentation. +filter=-whitespace/indent + +# TODO: b/364967694 re-enable the warning +filter=-whitespace/newline +# TODO: b/364967694 re-enable the warning +filter=-whitespace/blank_line +# TODO: b/364967694 re-enable the warning +filter=-whitespace/ending_newline +# TODO: b/364967694 re-enable the warning +filter=-readability/check +# TODO: b/364967694 re-enable the warning +filter=-runtime/int +# TODO: b/364967694 re-enable the warning +filter=-runtime/string +# TODO: b/364967694 re-enable the warning +filter=-runtime/explicit +# TODO: b/364967694 re-enable the warning +filter=-readability/braces +# TODO: b/364967694 re-enable the warning +filter=-whitespace/braces +# TODO: b/364967694 re-enable the warning +filter=-build/c++11 +# TODO: b/364967694 re-enable the warning +filter=-readability/todo +# TODO: b/364967694 re-enable the warning +filter=-readability/multiline_comment +# TODO: b/364967694 re-enable the warning +filter=-build/namespaces +# TODO: b/364967694 re-enable the warning +filter=-readability/inheritance +# TODO: b/364967694 re-enable the warning +filter=-build/header_guard +# TODO: b/364967694 re-enable the warning +filter=-runtime/references +# TODO: b/364967694 re-enable the warning +filter=-build/include_what_you_use +# TODO: b/364967694 re-enable the warning +filter=-build/include_subdir +# TODO: b/364967694 re-enable the warning +filter=-readability/casting diff --git a/OWNERS_build b/OWNERS_build index 263e0532b6ba983665554ebbeb49c3c0540039df..5b3f4bad26bb6d12a5b76f2f028bde581286585c 100644 --- a/OWNERS_build +++ b/OWNERS_build @@ -1,2 +1 @@ -licorne@google.com wescande@google.com diff --git a/OWNERS_chromeos b/OWNERS_chromeos index 0280ec47df9af042be2674c2d55beaeb5ceb43cc..075a04e5e3c80b043790f920a8dbeebb1d79ef51 100644 --- a/OWNERS_chromeos +++ b/OWNERS_chromeos @@ -1,6 +1,5 @@ # Project owners abhishekpandit@google.com -sonnysasaka@google.com # Audio enshuo@google.com @@ -12,3 +11,12 @@ whalechang@google.com michaelfsun@google.com laikatherine@google.com yinghsu@google.com +apusaka@google.com +deanliao@google.com +chharry@google.com +melhuishj@google.com +johnlai@google.com +mmandlik@google.com +sarveshkalwit@google.com +howardchung@google.com +jiangzp@google.com diff --git a/OWNERS_hearingaid b/OWNERS_hearingaid index 09f245c2fcca2b51b7bb92e2f0d95d318f0ace8f..e70772a9527eb199cb115399b3cf6bab7bae263a 100644 --- a/OWNERS_hearingaid +++ b/OWNERS_hearingaid @@ -1,2 +1 @@ charliebout@google.com -licorne@google.com diff --git a/TEST_MAPPING b/TEST_MAPPING index d63ee4014153169c47c779131d2978f6ecddb053..5f21a9b8008fcf89ae41382b075e80d0a67004f6 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -25,9 +25,6 @@ //{ // "name": "bluetooth-test-audio-hal-interface" //}, - { - "name": "net_test_audio_a2dp_hw" - }, { "name": "net_test_audio_hearing_aid_hw" }, @@ -110,9 +107,6 @@ { "name": "bluetooth_test_common" }, - { - "name": "bluetooth_test_gd_unit" - }, { "name": "bluetooth_test_sdp" }, @@ -226,9 +220,6 @@ //{ // "name": "bluetooth-test-audio-hal-interface" //}, - { - "name": "net_test_audio_a2dp_hw" - }, { "name": "net_test_audio_hearing_aid_hw" }, diff --git a/android/ChannelSoundingTestApp/app/build.gradle.kts b/android/ChannelSoundingTestApp/app/build.gradle.kts index 76749335f57db90e07352b04ac82771c3347c234..cfc03a1247c0da78faec718937f3e1955f805743 100644 --- a/android/ChannelSoundingTestApp/app/build.gradle.kts +++ b/android/ChannelSoundingTestApp/app/build.gradle.kts @@ -10,8 +10,8 @@ android { applicationId = "com.android.bluetooth.channelsoundingtestapp" minSdk = 34 targetSdk = 34 - versionCode = 1 - versionName = "1.0" + versionCode = 2 + versionName = "2.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -38,7 +38,10 @@ dependencies { implementation(libs.constraintlayout) implementation(libs.navigation.fragment) implementation(libs.navigation.ui) + implementation(libs.legacy.support.v4) + implementation(libs.lifecycle.livedata.ktx) + implementation(libs.lifecycle.viewmodel.ktx) testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) -} \ No newline at end of file +} diff --git a/android/ChannelSoundingTestApp/app/src/main/AndroidManifest.xml b/android/ChannelSoundingTestApp/app/src/main/AndroidManifest.xml index 9be650fdc5dffd6cf661ab59c1fade81b692a5c6..a949fa7cc24fb27aa7b7ed43bd1d6354063bc9a6 100644 --- a/android/ChannelSoundingTestApp/app/src/main/AndroidManifest.xml +++ b/android/ChannelSoundingTestApp/app/src/main/AndroidManifest.xml @@ -4,9 +4,12 @@ package="com.android.bluetooth.channelsoundingtestapp"> + + - + + + tools:targetApi="34"> mBondedBtDevicesArrayAdapter; + private Button mButtonUpdate; + private Button mButtonGatt; + private Button mButtonScanConnect; + private Spinner mSpinnerBtAddress; + + public static BleConnectionFragment newInstance() { + return new BleConnectionFragment(); + } + + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + View root = inflater.inflate(R.layout.fragment_ble_connection, container, false); + mBtnAdvertising = root.findViewById(R.id.btn_advertising); + mButtonUpdate = (Button) root.findViewById(R.id.btn_update_devices); + mButtonGatt = (Button) root.findViewById(R.id.btn_connect_gatt); + mButtonScanConnect = (Button) root.findViewById(R.id.btn_scan_connect); + mSpinnerBtAddress = (Spinner) root.findViewById(R.id.spinner_bt_address); + return root; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + mBondedBtDevicesArrayAdapter = + new ArrayAdapter( + getContext(), android.R.layout.simple_spinner_item, new ArrayList<>()); + mBondedBtDevicesArrayAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mSpinnerBtAddress.setAdapter(mBondedBtDevicesArrayAdapter); + + mViewModel = + new ViewModelProvider(requireParentFragment()).get(BleConnectionViewModel.class); + mViewModel + .getGattState() + .observe( + getActivity(), + gattSate -> { + switch (gattSate) { + case CONNECTED_DIRECT: + mButtonGatt.setText("Disconnect Gatt"); + break; + case SCANNING: + mButtonScanConnect.setText("Stop Scan"); + break; + case CONNECTED_SCAN: + mButtonScanConnect.setText("Disconnect Gatt"); + break; + case DISCONNECTED: + default: + mButtonGatt.setText("Connect Gatt"); + mButtonScanConnect.setText("Scan and Connect"); + } + }); + mButtonUpdate.setOnClickListener( + v -> { + mViewModel.updateBondedDevices(); + }); + mViewModel + .getBondedBtDeviceAddresses() + .observe( + getActivity(), + deviceList -> { + mBondedBtDevicesArrayAdapter.clear(); + mBondedBtDevicesArrayAdapter.addAll(deviceList); + if (mSpinnerBtAddress.getSelectedItem() != null) { + String selectedBtAddress = + mSpinnerBtAddress.getSelectedItem().toString(); + mViewModel.setCsTargetAddress(selectedBtAddress); + } + }); + mSpinnerBtAddress.setOnItemSelectedListener( + new OnItemSelectedListener() { + @Override + public void onItemSelected( + AdapterView adapterView, View view, int i, long l) { + String btAddress = mSpinnerBtAddress.getSelectedItem().toString(); + mViewModel.setCsTargetAddress(btAddress); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + mViewModel.setCsTargetAddress(""); + } + }); + mButtonGatt.setOnClickListener( + v -> { + mViewModel.toggleGattConnection(); + }); + mButtonScanConnect.setOnClickListener( + v -> { + mViewModel.toggleScanConnect(); + }); + mViewModel + .getIsAdvertising() + .observe( + getActivity(), + isAdvertising -> { + if (isAdvertising) { + mBtnAdvertising.setText("Stop Advertising"); + } else { + mBtnAdvertising.setText("Start Advertising"); + } + }); + + mBtnAdvertising.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View view) { + mViewModel.toggleAdvertising(); + } + }); + } +} diff --git a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/BleConnectionViewModel.java b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/BleConnectionViewModel.java new file mode 100644 index 0000000000000000000000000000000000000000..c5f25cff1f2d36fe37e04668edf53df91a92ef11 --- /dev/null +++ b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/BleConnectionViewModel.java @@ -0,0 +1,342 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * 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.android.bluetooth.channelsoundingtestapp; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattServer; +import android.bluetooth.BluetoothGattServerCallback; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertisingSet; +import android.bluetooth.le.AdvertisingSetCallback; +import android.bluetooth.le.AdvertisingSetParameters; +import android.bluetooth.le.BluetoothLeAdvertiser; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.os.ParcelUuid; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.android.bluetooth.channelsoundingtestapp.Constants.GattState; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** The ViewModel for the BLE GATT connection. */ +@SuppressLint("MissingPermission") // permissions are checked upfront +public class BleConnectionViewModel extends AndroidViewModel { + private static final int GATT_MTU_SIZE = 512; + + private final BluetoothAdapter mBluetoothAdapter; + private final BluetoothManager mBluetoothManager; + @Nullable private BluetoothGatt mBluetoothGatt = null; + private MutableLiveData mIsAdvertising = new MutableLiveData<>(false); + private MutableLiveData mLogText = new MutableLiveData<>(); + private MutableLiveData mTargetDevice = new MutableLiveData<>(); + // scanner + private final MutableLiveData> mBondedBtDeviceAddresses = new MutableLiveData<>(); + private final MutableLiveData mGattState = + new MutableLiveData<>(GattState.DISCONNECTED); + private String mTargetBtAddress = ""; + + private GattState mExpectedGattState = GattState.DISCONNECTED; + + /** Constructor */ + public BleConnectionViewModel(@NonNull Application application) { + super(application); + mBluetoothManager = application.getSystemService(BluetoothManager.class); + mBluetoothAdapter = mBluetoothManager.getAdapter(); + } + + LiveData getIsAdvertising() { + return mIsAdvertising; + } + + LiveData getLogText() { + return mLogText; + } + + LiveData getGattState() { + return mGattState; + } + + LiveData> getBondedBtDeviceAddresses() { + return mBondedBtDeviceAddresses; + } + + LiveData getTargetDevice() { + return mTargetDevice; + } + + void toggleAdvertising() { + if (mIsAdvertising.getValue()) { + stopAdvertising(); + } else { + startConnectableAdvertising(); + } + } + + AdvertisingSetCallback mAdvertisingSetCallback = + new AdvertisingSetCallback() { + @Override + public void onAdvertisingSetStarted( + AdvertisingSet advertisingSet, int txPower, int status) { + printLog( + "onAdvertisingSetStarted(): txPower:" + + txPower + + " , status: " + + status); + if (status == 0) { + mIsAdvertising.postValue(true); + } + } + + @Override + public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) { + printLog("onAdvertisingDataSet() :status:" + status); + } + + @Override + public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) { + printLog("onScanResponseDataSet(): status:" + status); + } + + @Override + public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { + printLog("onAdvertisingSetStopped():"); + mIsAdvertising.postValue(false); + } + }; + + private void startConnectableAdvertising() { + if (mIsAdvertising.getValue()) { + return; + } + BluetoothLeAdvertiser advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); + AdvertisingSetParameters parameters = + new AdvertisingSetParameters.Builder() + .setLegacyMode(false) // True by default, but set here as a reminder. + .setConnectable(true) + .setInterval(AdvertisingSetParameters.INTERVAL_LOW) + .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM) + .build(); + + BluetoothGattServerCallback gattServerCallback = + new BluetoothGattServerCallback() { + @Override + public void onConnectionStateChange( + BluetoothDevice device, int status, int newState) { + super.onConnectionStateChange(device, status, newState); + if (newState == BluetoothProfile.STATE_CONNECTED) { + printLog("Device connected: " + device.getName()); + mTargetDevice.postValue(device); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + printLog("Device disconnected: " + device.getName()); + mTargetDevice.postValue(null); + } + } + }; + + BluetoothGattServer bluetoothGattServer = + mBluetoothManager.openGattServer( + getApplication().getApplicationContext(), gattServerCallback); + AdvertiseData advertiseData = + new AdvertiseData.Builder() + .setIncludeDeviceName(true) + .addServiceUuid(new ParcelUuid(Constants.CS_TEST_SERVICE_UUID)) + .build(); + + printLog("Start connectable advertising"); + + advertiser.startAdvertisingSet( + parameters, advertiseData, null, null, null, 0, 0, mAdvertisingSetCallback); + } + + private void stopAdvertising() { + BluetoothLeAdvertiser advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); + advertiser.stopAdvertisingSet(mAdvertisingSetCallback); + printLog("stop advertising"); + } + + void updateBondedDevices() { + List addresses = new ArrayList<>(); + Set bonded_devices = mBluetoothAdapter.getBondedDevices(); + for (BluetoothDevice device : bonded_devices) { + addresses.add(device.getAddress()); + } + mBondedBtDeviceAddresses.setValue(addresses); + } + + void setCsTargetAddress(String btAddress) { + printLog("set target address: " + btAddress); + mTargetBtAddress = btAddress; + } + + void toggleGattConnection() { + if (mGattState.getValue() == GattState.DISCONNECTED) { + if (TextUtils.isEmpty(mTargetBtAddress)) { + printLog("Pair and select a target device first!"); + return; + } + connectGatt(); + } else if (mGattState.getValue() == GattState.CONNECTED_DIRECT) { + disconnectGatt(); + } + } + + private BluetoothGattCallback mGattCallback = + new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + printLog("onConnectionStateChange status:" + status + ", newState:" + newState); + if (newState == BluetoothProfile.STATE_CONNECTED) { + printLog(gatt.getDevice().getName() + " is connected"); + gatt.requestMtu(GATT_MTU_SIZE); + mBluetoothGatt = gatt; + mGattState.postValue(mExpectedGattState); + mTargetDevice.postValue(gatt.getDevice()); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + printLog("disconnected from " + gatt.getDevice().getName()); + mExpectedGattState = GattState.DISCONNECTED; + mGattState.postValue(mExpectedGattState); + mBluetoothGatt.close(); + mBluetoothGatt = null; + mTargetDevice.postValue(null); + } + } + + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + printLog("MTU changed to: " + mtu); + } else { + printLog("MTU change failed: " + status); + } + } + }; + + private void connectGatt() { + BluetoothDevice btDevice = mBluetoothAdapter.getRemoteDevice(mTargetBtAddress); + printLog("Connect gatt to " + btDevice.getName()); + mExpectedGattState = GattState.CONNECTED_DIRECT; + btDevice.connectGatt( + getApplication().getApplicationContext(), + false, + mGattCallback, + BluetoothDevice.TRANSPORT_LE); + } + + private void disconnectGatt() { + if (mBluetoothGatt != null) { + printLog("disconnect from " + mBluetoothGatt.getDevice().getName()); + mBluetoothGatt.disconnect(); + } + } + + void toggleScanConnect() { + if (mGattState.getValue() == GattState.DISCONNECTED) { + connectGattByScanning(); + } else if (mGattState.getValue() == GattState.SCANNING) { + stopScanning(); + } else if (mGattState.getValue() == GattState.CONNECTED_SCAN) { + disconnectGatt(); + } + } + + private ScanCallback mScanCallback = + new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + List serviceUuids = result.getScanRecord().getServiceUuids(); + if (serviceUuids != null) { + for (ParcelUuid parcelUuid : serviceUuids) { + BluetoothDevice btDevice = result.getDevice(); + printLog("found device - " + btDevice.getName()); + if (parcelUuid.getUuid().equals(Constants.CS_TEST_SERVICE_UUID)) { + mExpectedGattState = GattState.CONNECTED_SCAN; + stopScanning(); + printLog("connect GATT to: " + btDevice.getName()); + // Connect to the GATT server + mBluetoothGatt = + btDevice.connectGatt( + getApplication().getApplicationContext(), + false, + mGattCallback, + BluetoothDevice.TRANSPORT_LE); + } + } + } + } + }; + + private void connectGattByScanning() { + BluetoothLeScanner bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); + + List filters = new ArrayList<>(); + ScanFilter filter = + new ScanFilter.Builder() + .setServiceUuid( + new ParcelUuid( + Constants.CS_TEST_SERVICE_UUID)) // Filter by service UUID + .build(); + filters.add(filter); + + ScanSettings settings = + new ScanSettings.Builder() + .setLegacy(false) + .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setReportDelay(0) + .build(); + + printLog("start scanning..."); + + // Start scanning + bluetoothLeScanner.startScan(filters, settings, mScanCallback); + mExpectedGattState = GattState.SCANNING; + mGattState.setValue(mExpectedGattState); + } + + private void stopScanning() { + BluetoothLeScanner bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); + if (bluetoothLeScanner != null) { + bluetoothLeScanner.stopScan(mScanCallback); + if (mExpectedGattState == GattState.SCANNING) { + mExpectedGattState = GattState.DISCONNECTED; + mGattState.setValue(mExpectedGattState); + } + } + } + + private void printLog(@NonNull String logMsg) { + mLogText.postValue("BT Log: " + logMsg); + } +} diff --git a/system/gd/l2cap/le/dynamic_channel.cc b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/Constants.java similarity index 59% rename from system/gd/l2cap/le/dynamic_channel.cc rename to android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/Constants.java index 108b65f97e0790a21a6a7d89ec140061772c754f..a672811c6f170fb4bd365168da3fc2aa723f5af0 100644 --- a/system/gd/l2cap/le/dynamic_channel.cc +++ b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 The Android Open Source Project + * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,18 @@ * limitations under the License. */ -#include "l2cap/le/dynamic_channel.h" -#include "l2cap/le/internal/link.h" +package com.android.bluetooth.channelsoundingtestapp; -namespace bluetooth { -namespace l2cap { -namespace le { -LinkOptions* DynamicChannel::GetLinkOptions() { - return link_->GetLinkOptions(); -} +import java.util.UUID; -Mtu DynamicChannel::GetMtu() const { - return mtu_; -} +abstract class Constants { + static final UUID CS_TEST_SERVICE_UUID = + UUID.fromString("f81d4fae-7ccc-eeee-a765-00aaaaaaaaaa"); -} // namespace le -} // namespace l2cap -} // namespace bluetooth + enum GattState { + DISCONNECTED, + SCANNING, + CONNECTED_DIRECT, + CONNECTED_SCAN, + } +} diff --git a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/DistanceMeasurementInitiator.java b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/DistanceMeasurementInitiator.java index 44e7cff79408a7383c6e2675cd31305dd2761eaa..9d39dcb10f633e92e60c9e76c6666dcb424db8a3 100644 --- a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/DistanceMeasurementInitiator.java +++ b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/DistanceMeasurementInitiator.java @@ -19,10 +19,7 @@ package com.android.bluetooth.channelsoundingtestapp; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothManager; -import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.le.DistanceMeasurementManager; import android.bluetooth.le.DistanceMeasurementMethod; @@ -36,14 +33,12 @@ import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; class DistanceMeasurementInitiator { private static final int DISTANCE_MEASUREMENT_DURATION_SEC = 3600; - private static final int GATT_MTU_SIZE = 512; private static final List> mDistanceMeasurementMethodMapping = List.of( new Pair<>(DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_AUTO, "AUTO"), @@ -58,9 +53,8 @@ class DistanceMeasurementInitiator { private final Context mApplicationContext; private final Executor mBtExecutor; private final BtDistanceMeasurementCallback mBtDistanceMeasurementCallback; - private String mTargetBtAddress = ""; - @Nullable private BluetoothGatt mBluetoothGatt = null; @Nullable private DistanceMeasurementSession mSession = null; + @Nullable private BluetoothDevice mTargetDevice = null; DistanceMeasurementInitiator( Context applicationContext, @@ -77,70 +71,8 @@ class DistanceMeasurementInitiator { mBtExecutor = Executors.newSingleThreadExecutor(); } - void setTargetBtAddress(String btAddress) { - mTargetBtAddress = btAddress; - } - - @SuppressLint("MissingPermission") // permissions are checked upfront - List updatePairedDevice() { - List arrayList = new ArrayList<>(); - Set bonded_devices = mBluetoothAdapter.getBondedDevices(); - for (BluetoothDevice device : bonded_devices) { - arrayList.add(device.getAddress()); - } - printLog("Num of paired Devices: " + arrayList.size()); - return arrayList; - } - - @SuppressLint("MissingPermission") // permissions are checked upfront - void connectGatt() { - if (mTargetBtAddress == null) { - printLog("A paired device must be selected first."); - return; - } - BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mTargetBtAddress); - BluetoothGattCallback gattCallback = - new BluetoothGattCallback() { - @Override - public void onConnectionStateChange( - BluetoothGatt gatt, int status, int newState) { - printLog( - "onConnectionStateChange status:" - + status - + ", newState:" - + newState); - if (newState == BluetoothProfile.STATE_CONNECTED) { - printLog(gatt.getDevice().getName() + " is connected"); - gatt.requestMtu(GATT_MTU_SIZE); - mBluetoothGatt = gatt; - mBtDistanceMeasurementCallback.onGattConnected(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - printLog("disconnected from " + gatt.getDevice().getName()); - mBtDistanceMeasurementCallback.onGattDisconnected(); - mBluetoothGatt.close(); - mBluetoothGatt = null; - } - } - - public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - printLog("MTU changed to: " + mtu); - } else { - printLog("MTU change failed: " + status); - } - } - }; - printLog("Connect gatt to " + device.getAddress()); - - device.connectGatt(mApplicationContext, false, gattCallback, BluetoothDevice.TRANSPORT_LE); - } - - @SuppressLint("MissingPermission") // permissions are checked upfront - void disconnectGatt() { - if (mBluetoothGatt != null) { - printLog("disconnect from " + mBluetoothGatt.getDevice().getName()); - mBluetoothGatt.disconnect(); - } + void setTargetDevice(BluetoothDevice targetDevice) { + mTargetDevice = targetDevice; } private void printLog(String log) { @@ -190,16 +122,15 @@ class DistanceMeasurementInitiator { @SuppressLint("MissingPermission") // permissions are checked upfront void startDistanceMeasurement(String distanceMeasurementMethodName) { - if (mTargetBtAddress == null) { - printLog("pair and select a valid address."); + if (mTargetDevice == null) { + printLog("do Gatt connect first"); return; } - printLog("start CS with address: " + mTargetBtAddress); + printLog("start CS with device: " + mTargetDevice.getName()); - BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mTargetBtAddress); DistanceMeasurementParams params = - new DistanceMeasurementParams.Builder(device) + new DistanceMeasurementParams.Builder(mTargetDevice) .setDurationSeconds(DISTANCE_MEASUREMENT_DURATION_SEC) .setFrequency(DistanceMeasurementParams.REPORT_FREQUENCY_LOW) .setMethodId(getDistanceMeasurementMethodId(distanceMeasurementMethodName)) @@ -255,9 +186,5 @@ class DistanceMeasurementInitiator { void onStop(); void onDistanceResult(double distanceMeters); - - void onGattConnected(); - - void onGattDisconnected(); } } diff --git a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/InitiatorFragment.java b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/InitiatorFragment.java index 010ef3b9881959a95bca2d551e703f47abccf37f..fe3a211f9f7965d6df3fb5f2b3c0c10a1c174d60 100644 --- a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/InitiatorFragment.java +++ b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/InitiatorFragment.java @@ -22,8 +22,6 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.LinearLayout; @@ -32,37 +30,38 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProviders; +import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; import java.text.DecimalFormat; import java.util.ArrayList; -/** d The fragment holds the initiator of channel sounding. */ +/** The fragment holds the initiator of channel sounding. */ @SuppressWarnings("SetTextI18n") public class InitiatorFragment extends Fragment { private static final DecimalFormat DISTANCE_DECIMAL_FMT = new DecimalFormat("0.00"); private ArrayAdapter mDmMethodArrayAdapter; - private ArrayAdapter mBondedBtDevicesArrayAdapter; private TextView mDistanceText; private CanvasView mDistanceCanvasView; private Spinner mSpinnerDmMethod; - private Button mButtonUpdate; private Button mButtonCs; - private Button mButtonGatt; - private Spinner mSpinnerBtAddress; private LinearLayout mDistanceViewLayout; private TextView mLogText; + private BleConnectionViewModel mBleConnectionViewModel; + private InitiatorViewModel mInitiatorViewModel; + @Override public View onCreateView( @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_initiator, container, false); - mButtonUpdate = (Button) root.findViewById(R.id.btn_update_devices); + Fragment bleConnectionFragment = new BleConnectionFragment(); + FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); + transaction.replace(R.id.init_ble_connection_container, bleConnectionFragment).commit(); + mButtonCs = (Button) root.findViewById(R.id.btn_cs); - mButtonGatt = (Button) root.findViewById(R.id.btn_connect_gatt); - mSpinnerBtAddress = (Spinner) root.findViewById(R.id.spinner_bt_address); mSpinnerDmMethod = (Spinner) root.findViewById(R.id.spinner_dm_method); mDistanceViewLayout = (LinearLayout) root.findViewById(R.id.layout_distance_view); mDistanceText = new TextView(getContext()); @@ -87,28 +86,24 @@ public class InitiatorFragment extends Fragment { android.R.layout.simple_spinner_dropdown_item); mSpinnerDmMethod.setAdapter(mDmMethodArrayAdapter); - mBondedBtDevicesArrayAdapter = - new ArrayAdapter( - getContext(), android.R.layout.simple_spinner_item, new ArrayList<>()); - mBondedBtDevicesArrayAdapter.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item); - mSpinnerBtAddress.setAdapter(mBondedBtDevicesArrayAdapter); - - InitiatorViewModel initiatorViewModel = - ViewModelProviders.of(getActivity()).get(InitiatorViewModel.class); - - initiatorViewModel - .getGattConnected() + mInitiatorViewModel = new ViewModelProvider(this).get(InitiatorViewModel.class); + mBleConnectionViewModel = new ViewModelProvider(this).get(BleConnectionViewModel.class); + mBleConnectionViewModel + .getLogText() .observe( getActivity(), - connected -> { - if (connected) { - mButtonGatt.setText("Disconnect Gatt"); - } else { - mButtonGatt.setText("Connect Gatt"); - } + log -> { + mLogText.setText(log); + }); + mBleConnectionViewModel + .getTargetDevice() + .observe( + getActivity(), + targetDevice -> { + mInitiatorViewModel.setTargetDevice(targetDevice); }); - initiatorViewModel + + mInitiatorViewModel .getCsStarted() .observe( getActivity(), @@ -120,36 +115,7 @@ public class InitiatorFragment extends Fragment { mButtonCs.setText("Start Distance Measurement"); } }); - initiatorViewModel - .getBondedBtDeviceAddresses() - .observe( - getActivity(), - deviceList -> { - mBondedBtDevicesArrayAdapter.clear(); - mBondedBtDevicesArrayAdapter.addAll(deviceList); - if (mSpinnerBtAddress.getSelectedItem() != null) { - String selectedBtAddress = - mSpinnerBtAddress.getSelectedItem().toString(); - printLog("set target address: "); - initiatorViewModel.setCsTargetAddress(selectedBtAddress); - } - }); - mSpinnerBtAddress.setOnItemSelectedListener( - new OnItemSelectedListener() { - @Override - public void onItemSelected( - AdapterView adapterView, View view, int i, long l) { - String btAddress = mSpinnerBtAddress.getSelectedItem().toString(); - printLog("Target Address: " + btAddress); - initiatorViewModel.setCsTargetAddress(btAddress); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - initiatorViewModel.setCsTargetAddress(""); - } - }); - initiatorViewModel + mInitiatorViewModel .getLogText() .observe( getActivity(), @@ -157,7 +123,7 @@ public class InitiatorFragment extends Fragment { mLogText.setText(log); }); - initiatorViewModel + mInitiatorViewModel .getDistanceResult() .observe( getActivity(), @@ -167,41 +133,18 @@ public class InitiatorFragment extends Fragment { DISTANCE_DECIMAL_FMT.format(distanceMeters) + " m"); }); - mDmMethodArrayAdapter.addAll(initiatorViewModel.getSupportedDmMethods()); + mDmMethodArrayAdapter.addAll(mInitiatorViewModel.getSupportedDmMethods()); - mButtonUpdate.setOnClickListener( - v -> { - printLog("click update Bonded Devices."); - initiatorViewModel.updateBondedDevices(); - }); - mButtonGatt.setOnClickListener( - v -> { - if (!hasValidTarget()) return; - initiatorViewModel.toggleGattConnection(); - }); mButtonCs.setOnClickListener( v -> { - if (!hasValidTarget()) return; String methodName = mSpinnerDmMethod.getSelectedItem().toString(); if (TextUtils.isEmpty(methodName)) { printLog("the device doesn't support any distance measurement methods."); } - initiatorViewModel.toggleCsStartStop(methodName); + mInitiatorViewModel.toggleCsStartStop(methodName); }); } - private boolean hasValidTarget() { - String btAddress = ""; - if (mSpinnerBtAddress.getSelectedItem() != null) { - btAddress = mSpinnerBtAddress.getSelectedItem().toString(); - } - if (TextUtils.isEmpty(btAddress)) { - printLog("Pair and select a target device first!"); - return false; - } - return true; - } - private void printLog(String logMessage) { mLogText.setText("LOG: " + logMessage); } diff --git a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/InitiatorViewModel.java b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/InitiatorViewModel.java index ae6459976306d0ea9c8ba2bdf689e0b0da7547a7..f54ec7b94a6b859b91fe46e8b4aa84787a5008e4 100644 --- a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/InitiatorViewModel.java +++ b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/InitiatorViewModel.java @@ -17,6 +17,7 @@ package com.android.bluetooth.channelsoundingtestapp; import android.app.Application; +import android.bluetooth.BluetoothDevice; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; @@ -32,9 +33,8 @@ public class InitiatorViewModel extends AndroidViewModel { private final MutableLiveData mLogText = new MutableLiveData<>(); private final MutableLiveData mCsStarted = new MutableLiveData<>(false); - private final MutableLiveData> mBondedBtDeviceAddresses = new MutableLiveData<>(); + private final MutableLiveData mDistanceResult = new MutableLiveData<>(); - private final MutableLiveData mGattConnected = new MutableLiveData<>(false); private final DistanceMeasurementInitiator mDistanceMeasurementInitiator; // mDistanceMeasurementInitiator; @@ -51,42 +51,22 @@ public class InitiatorViewModel extends AndroidViewModel { }); } - LiveData getLogText() { - return mLogText; + void setTargetDevice(BluetoothDevice targetDevice) { + mDistanceMeasurementInitiator.setTargetDevice(targetDevice); } - LiveData getGattConnected() { - return mGattConnected; + LiveData getLogText() { + return mLogText; } LiveData getCsStarted() { return mCsStarted; } - LiveData> getBondedBtDeviceAddresses() { - return mBondedBtDeviceAddresses; - } - LiveData getDistanceResult() { return mDistanceResult; } - void setCsTargetAddress(String btAddress) { - mDistanceMeasurementInitiator.setTargetBtAddress(btAddress); - } - - void updateBondedDevices() { - mBondedBtDeviceAddresses.setValue(mDistanceMeasurementInitiator.updatePairedDevice()); - } - - void toggleGattConnection() { - if (!mGattConnected.getValue()) { - mDistanceMeasurementInitiator.connectGatt(); - } else { - mDistanceMeasurementInitiator.disconnectGatt(); - } - } - List getSupportedDmMethods() { return mDistanceMeasurementInitiator.getDistanceMeasurementMethods(); } @@ -118,15 +98,5 @@ public class InitiatorViewModel extends AndroidViewModel { public void onDistanceResult(double distanceMeters) { mDistanceResult.postValue(distanceMeters); } - - @Override - public void onGattConnected() { - mGattConnected.postValue(true); - } - - @Override - public void onGattDisconnected() { - mGattConnected.postValue(false); - } }; } diff --git a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/MainActivity.java b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/MainActivity.java index 7162f12a3c489113e202054c856a4062d9ea79e3..a78067c012ffb40d321fb2ef163a69c68c0b9212 100644 --- a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/MainActivity.java +++ b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/MainActivity.java @@ -16,7 +16,7 @@ package com.android.bluetooth.channelsoundingtestapp; -import android.Manifest; +import android.Manifest.permission; import android.content.pm.PackageManager; import android.os.Bundle; @@ -55,12 +55,16 @@ public class MainActivity extends AppCompatActivity { private void requestBtPermissions() { String[] requiredPermissions = new String[] { - android.Manifest.permission.BLUETOOTH_ADVERTISE, - android.Manifest.permission.BLUETOOTH_CONNECT, + permission.ACCESS_COARSE_LOCATION, + permission.ACCESS_FINE_LOCATION, + permission.BLUETOOTH_ADVERTISE, + permission.BLUETOOTH_CONNECT, + permission.BLUETOOTH_SCAN, }; List permissionsToRequest = new ArrayList<>(); + for (String permission : requiredPermissions) { - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) + if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { permissionsToRequest.add(permission); } diff --git a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/ReflectorFragment.java b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/ReflectorFragment.java index 240529bdc0a6397954c4ef03c34ded91e67baea2..13a8981682533548322d9c231fb8b6fc5cc03ff9 100644 --- a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/ReflectorFragment.java +++ b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/ReflectorFragment.java @@ -19,21 +19,18 @@ package com.android.bluetooth.channelsoundingtestapp; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.Button; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProviders; +import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; /** The fragment holds the reflector of channel sounding. */ @SuppressWarnings("SetTextI18n") public class ReflectorFragment extends Fragment { - - private ReflectorViewModel mReflectorViewModel; - private Button mBtnAdvertising; + private BleConnectionViewModel mBleConnectionViewModel; private TextView mLogText; @Override @@ -41,40 +38,24 @@ public class ReflectorFragment extends Fragment { @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_reflector, container, false); - mBtnAdvertising = (Button) root.findViewById(R.id.btn_advertising); + Fragment bleConnectionFragment = new BleConnectionFragment(); + FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); + transaction.replace(R.id.ref_ble_connection_container, bleConnectionFragment).commit(); mLogText = (TextView) root.findViewById(R.id.text_log); return root; } public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - mReflectorViewModel = ViewModelProviders.of(getActivity()).get(ReflectorViewModel.class); - mReflectorViewModel - .getIsAdvertising() - .observe( - getActivity(), - isAdvertising -> { - if (isAdvertising) { - mBtnAdvertising.setText("Stop Advertising"); - } else { - mBtnAdvertising.setText("Start Advertising"); - } - }); - mReflectorViewModel + + mBleConnectionViewModel = new ViewModelProvider(this).get(BleConnectionViewModel.class); + mBleConnectionViewModel .getLogText() .observe( getActivity(), log -> { mLogText.setText(log); }); - - mBtnAdvertising.setOnClickListener( - new OnClickListener() { - @Override - public void onClick(View view) { - mReflectorViewModel.toggleAdvertising(); - } - }); } @Override diff --git a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/ReflectorViewModel.java b/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/ReflectorViewModel.java deleted file mode 100644 index 98cd4816aee428f4104bb3af432961bf1e964dc5..0000000000000000000000000000000000000000 --- a/android/ChannelSoundingTestApp/app/src/main/java/com/android/bluetooth/channelsoundingtestapp/ReflectorViewModel.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * 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.android.bluetooth.channelsoundingtestapp; - -import android.annotation.SuppressLint; -import android.app.Application; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothManager; -import android.bluetooth.le.AdvertiseData; -import android.bluetooth.le.AdvertisingSet; -import android.bluetooth.le.AdvertisingSetCallback; -import android.bluetooth.le.AdvertisingSetParameters; -import android.bluetooth.le.BluetoothLeAdvertiser; - -import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; - -/** ViewModel for the Reflector. */ -@SuppressLint("MissingPermission") -public class ReflectorViewModel extends AndroidViewModel { - - private final BluetoothAdapter mBluetoothAdapter; - private MutableLiveData mIsAdvertising = new MutableLiveData<>(false); - private MutableLiveData mLogText = new MutableLiveData<>(); - private AdvertisingSet mAdvertisingSet = null; - - /** Constructor */ - public ReflectorViewModel(@NonNull Application application) { - super(application); - BluetoothManager bluetoothManager = application.getSystemService(BluetoothManager.class); - mBluetoothAdapter = bluetoothManager.getAdapter(); - } - - LiveData getIsAdvertising() { - return mIsAdvertising; - } - - LiveData getLogText() { - return mLogText; - } - - void toggleAdvertising() { - if (mIsAdvertising.getValue()) { - stopAdvertising(); - } else { - startConnectableAdvertising(); - } - } - - private void startConnectableAdvertising() { - if (mAdvertisingSet != null) { - mAdvertisingSet.enableAdvertising(!mIsAdvertising.getValue(), 0, 0); - return; - } - - BluetoothLeAdvertiser advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); - AdvertisingSetParameters parameters = - (new AdvertisingSetParameters.Builder()) - .setLegacyMode(false) // True by default, but set here as a reminder. - .setConnectable(true) - .setScannable(false) - .setInterval(AdvertisingSetParameters.INTERVAL_LOW) - .setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM) - .build(); - - AdvertiseData response = (new AdvertiseData.Builder()).setIncludeDeviceName(true).build(); - - AdvertisingSetCallback callback = - new AdvertisingSetCallback() { - @Override - public void onAdvertisingSetStarted( - AdvertisingSet advertisingSet, int txPower, int status) { - printLog( - "onAdvertisingSetStarted(): txPower:" - + txPower - + " , status: " - + status); - mAdvertisingSet = advertisingSet; - if (status == 0) { - mIsAdvertising.setValue(true); - } - } - - @Override - public void onAdvertisingEnabled( - AdvertisingSet advertisingSet, boolean enable, int status) { - printLog("enable: " + enable + ", status:" + status); - if (enable == false) { - mIsAdvertising.setValue(false); - } else { - mIsAdvertising.setValue(true); - } - } - - @Override - public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) { - printLog("onAdvertisingDataSet() :status:" + status); - } - - @Override - public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) { - printLog("onScanResponseDataSet(): status:" + status); - } - - @Override - public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { - printLog("onAdvertisingSetStopped():"); - } - }; - - printLog("Start connectable advertising"); - advertiser.startAdvertisingSet(parameters, response, null, null, null, 0, 0, callback); - } - - private void stopAdvertising() { - if (mAdvertisingSet != null) { - printLog("advertising is stopped."); - mAdvertisingSet.enableAdvertising(false, 0, 0); - } - } - - private void printLog(@NonNull String logMsg) { - mLogText.setValue("BT Log: " + logMsg); - } -} diff --git a/android/ChannelSoundingTestApp/app/src/main/res/layout/fragment_ble_connection.xml b/android/ChannelSoundingTestApp/app/src/main/res/layout/fragment_ble_connection.xml new file mode 100644 index 0000000000000000000000000000000000000000..5c70cec470d7bec5b9f77e712352d0a03fb4bea4 --- /dev/null +++ b/android/ChannelSoundingTestApp/app/src/main/res/layout/fragment_ble_connection.xml @@ -0,0 +1,114 @@ + + + + +