chromium/device/bluetooth/bluetooth_adapter_android.cc

// Copyright 2015 The Chromium Authors
// 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 <memory>
#include <string>

#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.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"

// Must come after all headers that specialize FromJniType() / ToJniType().
#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> BluetoothAdapter::CreateAdapter() {
  return BluetoothAdapterAndroid::Create(
      BluetoothAdapterWrapper_CreateWithDefaultAdapter());
}

// static
scoped_refptr<BluetoothAdapterAndroid> BluetoothAdapterAndroid::Create(
    const JavaRef<jobject>&
        bluetooth_adapter_wrapper) {  // Java Type: BluetoothAdapterWrapper
  auto adapter = base::WrapRefCounted(new BluetoothAdapterAndroid());

  adapter->j_adapter_.Reset(Java_ChromeBluetoothAdapter_create(
      AttachCurrentThread(), reinterpret_cast<intptr_t>(adapter.get()),
      bluetooth_adapter_wrapper));

  adapter->ui_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();

  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,
                                      base::OnceClosure callback,
                                      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,
                                              base::OnceClosure callback,
                                              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,
    CreateServiceCallback callback,
    CreateServiceErrorCallback error_callback) {
  NOTIMPLEMENTED();
  std::move(error_callback).Run("Not Implemented");
}

void BluetoothAdapterAndroid::CreateL2capService(
    const BluetoothUUID& uuid,
    const ServiceOptions& options,
    CreateServiceCallback callback,
    CreateServiceErrorCallback error_callback) {
  NOTIMPLEMENTED();
  std::move(error_callback).Run("Not Implemented");
}

void BluetoothAdapterAndroid::RegisterAdvertisement(
    std::unique_ptr<BluetoothAdvertisement::Data> advertisement_data,
    CreateAdvertisementCallback callback,
    AdvertisementErrorCallback error_callback) {
  std::move(error_callback)
      .Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM);
}

BluetoothLocalGattService* BluetoothAdapterAndroid::GetGattService(
    const std::string& identifier) const {
  return nullptr;
}

void BluetoothAdapterAndroid::OnAdapterStateChanged(
    JNIEnv* env,
    const JavaParamRef<jobject>& caller,
    const bool powered) {
  RunPendingPowerCallbacks();
  NotifyAdapterPoweredChanged(powered);
}

void BluetoothAdapterAndroid::OnScanFailed(
    JNIEnv* env,
    const JavaParamRef<jobject>& caller) {
  MarkDiscoverySessionsAsInactive();
}

void BluetoothAdapterAndroid::CreateOrUpdateDeviceOnScan(
    JNIEnv* env,
    const JavaParamRef<jobject>& caller,
    const JavaParamRef<jstring>& address,
    const JavaParamRef<jobject>&
        bluetooth_device_wrapper,  // Java Type: bluetoothDeviceWrapper
    const JavaParamRef<jstring>& local_name,
    int32_t rssi,
    const JavaParamRef<jobjectArray>& advertised_uuids,  // Java Type: String[]
    int32_t tx_power,
    const JavaParamRef<jobjectArray>& service_data_keys,  // Java Type: String[]
    const JavaParamRef<jobjectArray>& service_data_values,  // Java Type: byte[]
    const JavaParamRef<jintArray>& manufacturer_data_keys,  // Java Type: int[]
    const JavaParamRef<jobjectArray>&
        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<BluetoothDeviceAndroid> 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<BluetoothDeviceAndroid*>(iter->second.get());
  }
  DCHECK(device_android);

  std::vector<std::string> 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<std::string> service_data_keys_vector;
  std::vector<std::vector<uint8_t>> 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<jint> manufacturer_data_keys_vector;
  std::vector<std::vector<uint8_t>> 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<uint16_t>(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 ? std::nullopt
                                : std::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 ? std::nullopt
                            : std::make_optional(clamped_tx_power),
      service_data_map, manufacturer_data_map);

  for (auto& observer : observers_) {
    std::optional<std::string> device_name_opt = device_android->GetName();
    std::optional<std::string> 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 ? std::nullopt
                              : std::make_optional(clamped_tx_power),
        std::nullopt, /* TODO(crbug.com/41240161) 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::Milliseconds(kActivePollInterval));
  } else {
    ui_task_runner_->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&BluetoothAdapterAndroid::RemoveTimedOutDevices,
                       weak_ptr_factory_.GetWeakPtr()),
        base::Milliseconds(kPassivePollInterval));
  }
}

base::WeakPtr<BluetoothAdapter> 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<BluetoothDiscoveryFilter> 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<jobject>
BluetoothAdapterAndroid::CreateAndroidFilter(
    const BluetoothDiscoveryFilter* discovery_filter) {
  base::android::ScopedJavaLocalRef<jobject> android_filters =
      Java_ChromeBluetoothScanFilterList_create(AttachCurrentThread());
  const base::flat_set<device::BluetoothDiscoveryFilter::DeviceInfoFilter>*
      device_filters = discovery_filter->GetDeviceFilters();
  for (const auto& device_filter : *device_filters) {
    base::android::ScopedJavaLocalRef<jobject> 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<jobject> 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<BluetoothDiscoveryFilter> 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::Milliseconds(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