// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "device/bluetooth/bluetooth_adapter_android.h" #include #include #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/location.h" #include "base/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "device/bluetooth/android/wrappers.h" #include "device/bluetooth/bluetooth_advertisement.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_device_android.h" #include "device/bluetooth/bluetooth_discovery_session_outcome.h" #include "device/bluetooth/jni_headers/ChromeBluetoothAdapter_jni.h" #include "device/bluetooth/jni_headers/ChromeBluetoothScanFilterBuilder_jni.h" #include "device/bluetooth/jni_headers/ChromeBluetoothScanFilterList_jni.h" using base::android::AppendJavaStringArrayToStringVector; using base::android::AttachCurrentThread; using base::android::ConvertJavaStringToUTF8; using base::android::JavaArrayOfByteArrayToBytesVector; using base::android::JavaByteArrayToByteVector; using base::android::JavaIntArrayToIntVector; using base::android::JavaParamRef; using base::android::JavaRef; namespace { // The poll interval in ms when there is no active discovery. This // matches the max allowed advertisting interval for connectable // devices. enum { kPassivePollInterval = 11000 }; // The poll interval in ms when there is an active discovery. enum { kActivePollInterval = 1000 }; // The delay in ms to wait before purging devices when a scan starts. enum { kPurgeDelay = 500 }; } // namespace namespace device { // static scoped_refptr BluetoothAdapter::CreateAdapter() { return BluetoothAdapterAndroid::Create( BluetoothAdapterWrapper_CreateWithDefaultAdapter()); } // static scoped_refptr BluetoothAdapterAndroid::Create( const JavaRef& bluetooth_adapter_wrapper) { // Java Type: BluetoothAdapterWrapper auto adapter = base::WrapRefCounted(new BluetoothAdapterAndroid()); adapter->j_adapter_.Reset(Java_ChromeBluetoothAdapter_create( AttachCurrentThread(), reinterpret_cast(adapter.get()), bluetooth_adapter_wrapper)); adapter->ui_task_runner_ = base::ThreadTaskRunnerHandle::Get(); return adapter; } void BluetoothAdapterAndroid::Initialize(base::OnceClosure callback) { std::move(callback).Run(); } std::string BluetoothAdapterAndroid::GetAddress() const { return ConvertJavaStringToUTF8(Java_ChromeBluetoothAdapter_getAddress( AttachCurrentThread(), j_adapter_)); } std::string BluetoothAdapterAndroid::GetName() const { return ConvertJavaStringToUTF8( Java_ChromeBluetoothAdapter_getName(AttachCurrentThread(), j_adapter_)); } void BluetoothAdapterAndroid::SetName(const std::string& name, const base::Closure& callback, const ErrorCallback& error_callback) { NOTIMPLEMENTED(); } bool BluetoothAdapterAndroid::IsInitialized() const { return true; } bool BluetoothAdapterAndroid::IsPresent() const { return Java_ChromeBluetoothAdapter_isPresent(AttachCurrentThread(), j_adapter_); } bool BluetoothAdapterAndroid::IsPowered() const { return Java_ChromeBluetoothAdapter_isPowered(AttachCurrentThread(), j_adapter_); } bool BluetoothAdapterAndroid::IsDiscoverable() const { return Java_ChromeBluetoothAdapter_isDiscoverable(AttachCurrentThread(), j_adapter_); } void BluetoothAdapterAndroid::SetDiscoverable( bool discoverable, const base::Closure& callback, const ErrorCallback& error_callback) { NOTIMPLEMENTED(); } bool BluetoothAdapterAndroid::IsDiscovering() const { return Java_ChromeBluetoothAdapter_isDiscovering(AttachCurrentThread(), j_adapter_); } BluetoothAdapter::UUIDList BluetoothAdapterAndroid::GetUUIDs() const { NOTIMPLEMENTED(); return UUIDList(); } void BluetoothAdapterAndroid::CreateRfcommService( const BluetoothUUID& uuid, const ServiceOptions& options, const CreateServiceCallback& callback, const CreateServiceErrorCallback& error_callback) { NOTIMPLEMENTED(); error_callback.Run("Not Implemented"); } void BluetoothAdapterAndroid::CreateL2capService( const BluetoothUUID& uuid, const ServiceOptions& options, const CreateServiceCallback& callback, const CreateServiceErrorCallback& error_callback) { NOTIMPLEMENTED(); error_callback.Run("Not Implemented"); } void BluetoothAdapterAndroid::RegisterAdvertisement( std::unique_ptr advertisement_data, const CreateAdvertisementCallback& callback, const AdvertisementErrorCallback& error_callback) { error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM); } BluetoothLocalGattService* BluetoothAdapterAndroid::GetGattService( const std::string& identifier) const { return nullptr; } void BluetoothAdapterAndroid::OnAdapterStateChanged( JNIEnv* env, const JavaParamRef& caller, const bool powered) { RunPendingPowerCallbacks(); NotifyAdapterPoweredChanged(powered); } void BluetoothAdapterAndroid::OnScanFailed( JNIEnv* env, const JavaParamRef& caller) { MarkDiscoverySessionsAsInactive(); } void BluetoothAdapterAndroid::CreateOrUpdateDeviceOnScan( JNIEnv* env, const JavaParamRef& caller, const JavaParamRef& address, const JavaParamRef& bluetooth_device_wrapper, // Java Type: bluetoothDeviceWrapper const JavaParamRef& local_name, int32_t rssi, const JavaParamRef& advertised_uuids, // Java Type: String[] int32_t tx_power, const JavaParamRef& service_data_keys, // Java Type: String[] const JavaParamRef& service_data_values, // Java Type: byte[] const JavaParamRef& manufacturer_data_keys, // Java Type: int[] const JavaParamRef& manufacturer_data_values, // Java Type: byte[] int32_t advertisement_flags) { std::string device_address = ConvertJavaStringToUTF8(env, address); auto iter = devices_.find(device_address); bool is_new_device = false; std::unique_ptr device_android_owner; BluetoothDeviceAndroid* device_android; if (iter == devices_.end()) { // New device. is_new_device = true; device_android_owner = BluetoothDeviceAndroid::Create(this, bluetooth_device_wrapper); device_android = device_android_owner.get(); } else { // Existing device. device_android = static_cast(iter->second.get()); } DCHECK(device_android); std::vector advertised_uuids_strings; AppendJavaStringArrayToStringVector(env, advertised_uuids, &advertised_uuids_strings); BluetoothDevice::UUIDList advertised_bluetooth_uuids; for (std::string& uuid : advertised_uuids_strings) { advertised_bluetooth_uuids.push_back(BluetoothUUID(std::move(uuid))); } std::vector service_data_keys_vector; std::vector> service_data_values_vector; AppendJavaStringArrayToStringVector(env, service_data_keys, &service_data_keys_vector); JavaArrayOfByteArrayToBytesVector(env, service_data_values, &service_data_values_vector); BluetoothDeviceAndroid::ServiceDataMap service_data_map; for (size_t i = 0; i < service_data_keys_vector.size(); i++) { service_data_map.insert({BluetoothUUID(service_data_keys_vector[i]), service_data_values_vector[i]}); } std::vector manufacturer_data_keys_vector; std::vector> manufacturer_data_values_vector; JavaIntArrayToIntVector(env, manufacturer_data_keys, &manufacturer_data_keys_vector); JavaArrayOfByteArrayToBytesVector(env, manufacturer_data_values, &manufacturer_data_values_vector); BluetoothDeviceAndroid::ManufacturerDataMap manufacturer_data_map; for (size_t i = 0; i < manufacturer_data_keys_vector.size(); i++) { manufacturer_data_map.insert( {static_cast(manufacturer_data_keys_vector[i]), manufacturer_data_values_vector[i]}); } int8_t clamped_tx_power = BluetoothDevice::ClampPower(tx_power); device_android->UpdateAdvertisementData( BluetoothDevice::ClampPower(rssi), // Android uses -1 to indicate no advertising flags. // https://developer.android.com/reference/android/bluetooth/le/ScanRecord.html#getAdvertiseFlags() advertisement_flags == -1 ? base::nullopt : base::make_optional(advertisement_flags), advertised_bluetooth_uuids, // Android uses INT32_MIN to indicate no Advertised Tx Power. // https://developer.android.com/reference/android/bluetooth/le/ScanRecord.html#getTxPowerLevel() tx_power == INT32_MIN ? base::nullopt : base::make_optional(clamped_tx_power), service_data_map, manufacturer_data_map); for (auto& observer : observers_) { base::Optional device_name_opt = device_android->GetName(); base::Optional advertisement_name_opt; if (local_name) advertisement_name_opt = ConvertJavaStringToUTF8(env, local_name); observer.DeviceAdvertisementReceived( device_android->GetAddress(), device_name_opt, advertisement_name_opt, BluetoothDevice::ClampPower(rssi), // Android uses INT32_MIN to indicate no Advertised Tx Power. // https://developer.android.com/reference/android/bluetooth/le/ScanRecord.html#getTxPowerLevel() tx_power == INT32_MIN ? base::nullopt : base::make_optional(clamped_tx_power), base::nullopt, /* TODO(crbug.com/588083) Implement appearance */ advertised_bluetooth_uuids, service_data_map, manufacturer_data_map); } if (is_new_device) { devices_[device_address] = std::move(device_android_owner); for (auto& observer : observers_) observer.DeviceAdded(this, device_android); } else { for (auto& observer : observers_) observer.DeviceChanged(this, device_android); } } BluetoothAdapterAndroid::BluetoothAdapterAndroid() {} BluetoothAdapterAndroid::~BluetoothAdapterAndroid() { Java_ChromeBluetoothAdapter_onBluetoothAdapterAndroidDestruction( AttachCurrentThread(), j_adapter_); } void BluetoothAdapterAndroid::PurgeTimedOutDevices() { RemoveTimedOutDevices(); if (IsDiscovering()) { ui_task_runner_->PostDelayedTask( FROM_HERE, base::BindOnce(&BluetoothAdapterAndroid::PurgeTimedOutDevices, weak_ptr_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(kActivePollInterval)); } else { ui_task_runner_->PostDelayedTask( FROM_HERE, base::BindOnce(&BluetoothAdapterAndroid::RemoveTimedOutDevices, weak_ptr_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(kPassivePollInterval)); } } base::WeakPtr BluetoothAdapterAndroid::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } bool BluetoothAdapterAndroid::SetPoweredImpl(bool powered) { return Java_ChromeBluetoothAdapter_setPowered(AttachCurrentThread(), j_adapter_, powered); } void BluetoothAdapterAndroid::UpdateFilter( std::unique_ptr discovery_filter, DiscoverySessionResultCallback callback) { // If there is only 1 discovery session then StartScan should be called and // not UpdateFilter. DCHECK_GT(NumDiscoverySessions(), 1); if (IsPowered()) { // TODO(jameshollyer): Actually update the filter in Android. std::move(callback).Run(/*is_error=*/false, UMABluetoothDiscoverySessionOutcome::SUCCESS); return; } else { DVLOG(1) << "UpdateFilter: Fails: !isPowered"; std::move(callback).Run(/*is_error=*/true, UMABluetoothDiscoverySessionOutcome::UNKNOWN); } } base::android::ScopedJavaLocalRef BluetoothAdapterAndroid::CreateAndroidFilter( const BluetoothDiscoveryFilter* discovery_filter) { base::android::ScopedJavaLocalRef android_filters = Java_ChromeBluetoothScanFilterList_create(AttachCurrentThread()); const base::flat_set* device_filters = discovery_filter->GetDeviceFilters(); for (const auto& device_filter : *device_filters) { base::android::ScopedJavaLocalRef filter_builder = Java_ChromeBluetoothScanFilterBuilder_create(AttachCurrentThread()); if (!device_filter.uuids.empty()) { // Set the service UUID to the first UUID in the list because Android does // not support filtering for multiple UUIDs. This will return a superset // of the devices that advertise all UUIDs in the list and it will be // filtered internally when returned. Java_ChromeBluetoothScanFilterBuilder_setServiceUuid( AttachCurrentThread(), filter_builder, base::android::ConvertUTF8ToJavaString( AttachCurrentThread(), device_filter.uuids.begin()->value())); } if (!device_filter.name.empty()) { Java_ChromeBluetoothScanFilterBuilder_setDeviceName( AttachCurrentThread(), filter_builder, base::android::ConvertUTF8ToJavaString(AttachCurrentThread(), device_filter.name)); } base::android::ScopedJavaLocalRef scan_filter = Java_ChromeBluetoothScanFilterBuilder_build(AttachCurrentThread(), filter_builder); Java_ChromeBluetoothScanFilterList_addFilter(AttachCurrentThread(), android_filters, scan_filter); } return Java_ChromeBluetoothScanFilterList_getList(AttachCurrentThread(), android_filters); } void BluetoothAdapterAndroid::StartScanWithFilter( std::unique_ptr discovery_filter, DiscoverySessionResultCallback callback) { // This function should only be called if this is the first discovery session. // Otherwise we should have called updateFilter. DCHECK_EQ(NumDiscoverySessions(), 1); bool session_added = false; if (IsPowered()) { auto android_scan_filter = CreateAndroidFilter(discovery_filter.get()); if (Java_ChromeBluetoothAdapter_startScan(AttachCurrentThread(), j_adapter_, android_scan_filter)) { session_added = true; // Using a delayed task in order to give the adapter some time // to settle before purging devices. ui_task_runner_->PostDelayedTask( FROM_HERE, base::BindOnce(&BluetoothAdapterAndroid::PurgeTimedOutDevices, weak_ptr_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(kPurgeDelay)); } } else { DVLOG(1) << "StartScanWithFilter: Fails: !isPowered"; } if (session_added) { DVLOG(1) << "StartScanWithFilter: Now " << unsigned(NumDiscoverySessions()) << " sessions."; std::move(callback).Run(/*is_error=*/false, UMABluetoothDiscoverySessionOutcome::SUCCESS); } else { // TODO(scheib): Eventually wire the SCAN_FAILED result through to here. std::move(callback).Run(/*is_error=*/true, UMABluetoothDiscoverySessionOutcome::UNKNOWN); } } void BluetoothAdapterAndroid::StopScan( DiscoverySessionResultCallback callback) { DCHECK(NumDiscoverySessions() == 0); DVLOG(1) << "Stopping scan."; if (Java_ChromeBluetoothAdapter_stopScan(AttachCurrentThread(), j_adapter_)) { std::move(callback).Run(/*is_error=*/false, UMABluetoothDiscoverySessionOutcome::SUCCESS); } else { // TODO(scheib): Eventually wire the SCAN_FAILED result through to here. std::move(callback).Run(/*is_error=*/true, UMABluetoothDiscoverySessionOutcome::UNKNOWN); } for (const auto& device_id_object_pair : devices_) device_id_object_pair.second->ClearAdvertisementData(); } void BluetoothAdapterAndroid::RemovePairingDelegateInternal( device::BluetoothDevice::PairingDelegate* pairing_delegate) {} } // namespace device