245 lines
8.8 KiB
C++
245 lines
8.8 KiB
C++
|
/*
|
||
|
* Copyright (C) 2021 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.
|
||
|
*/
|
||
|
|
||
|
#include "gd/rust/topshim/hfp/hfp_shim.h"
|
||
|
|
||
|
#include "btif/include/btif_hf.h"
|
||
|
#include "gd/os/log.h"
|
||
|
#include "gd/rust/topshim/common/utils.h"
|
||
|
#include "include/hardware/bt_hf.h"
|
||
|
#include "src/profiles/hfp.rs.h"
|
||
|
#include "types/raw_address.h"
|
||
|
|
||
|
namespace rusty = ::bluetooth::topshim::rust;
|
||
|
|
||
|
namespace bluetooth {
|
||
|
namespace topshim {
|
||
|
namespace rust {
|
||
|
namespace internal {
|
||
|
static HfpIntf* g_hfpif;
|
||
|
|
||
|
static void connection_state_cb(bluetooth::headset::bthf_connection_state_t state, RawAddress* addr) {
|
||
|
RustRawAddress raddr = rusty::CopyToRustAddress(*addr);
|
||
|
rusty::hfp_connection_state_callback(state, raddr);
|
||
|
}
|
||
|
|
||
|
static void audio_state_cb(bluetooth::headset::bthf_audio_state_t state, RawAddress* addr) {
|
||
|
RustRawAddress raddr = rusty::CopyToRustAddress(*addr);
|
||
|
rusty::hfp_audio_state_callback(state, raddr);
|
||
|
}
|
||
|
|
||
|
} // namespace internal
|
||
|
|
||
|
class DBusHeadsetCallbacks : public headset::Callbacks {
|
||
|
public:
|
||
|
static Callbacks* GetInstance(headset::Interface* headset) {
|
||
|
static Callbacks* instance = new DBusHeadsetCallbacks(headset);
|
||
|
return instance;
|
||
|
}
|
||
|
|
||
|
DBusHeadsetCallbacks(headset::Interface* headset) : headset_(headset) {
|
||
|
call_status = 0;
|
||
|
};
|
||
|
|
||
|
// headset::Callbacks
|
||
|
void ConnectionStateCallback(headset::bthf_connection_state_t state, RawAddress* bd_addr) override {
|
||
|
LOG_INFO("ConnectionStateCallback from %s", bd_addr->ToString().c_str());
|
||
|
topshim::rust::internal::connection_state_cb(state, bd_addr);
|
||
|
}
|
||
|
|
||
|
void AudioStateCallback(headset::bthf_audio_state_t state, RawAddress* bd_addr) override {
|
||
|
LOG_INFO("AudioStateCallback %u from %s", state, bd_addr->ToString().c_str());
|
||
|
topshim::rust::internal::audio_state_cb(state, bd_addr);
|
||
|
|
||
|
switch (state) {
|
||
|
case headset::bthf_audio_state_t::BTHF_AUDIO_STATE_CONNECTED:
|
||
|
SetCallStatus(1, bd_addr);
|
||
|
// This triggers a +VGS command to set the speaker volume for HFP
|
||
|
// devices.
|
||
|
// TODO(b/215089433): Add a set volume API and have client to handle the
|
||
|
// set volume when start.
|
||
|
headset_->VolumeControl(headset::bthf_volume_type_t::BTHF_VOLUME_TYPE_SPK, 5, bd_addr);
|
||
|
return;
|
||
|
case headset::bthf_audio_state_t::BTHF_AUDIO_STATE_DISCONNECTED:
|
||
|
SetCallStatus(0, bd_addr);
|
||
|
return;
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VoiceRecognitionCallback(
|
||
|
[[maybe_unused]] headset::bthf_vr_state_t state, [[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void AnswerCallCallback([[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void HangupCallCallback([[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void VolumeControlCallback(
|
||
|
[[maybe_unused]] headset::bthf_volume_type_t type,
|
||
|
[[maybe_unused]] int volume,
|
||
|
[[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void DialCallCallback([[maybe_unused]] char* number, [[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void DtmfCmdCallback([[maybe_unused]] char tone, [[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void NoiseReductionCallback(
|
||
|
[[maybe_unused]] headset::bthf_nrec_t nrec, [[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void WbsCallback([[maybe_unused]] headset::bthf_wbs_config_t wbs, [[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void AtChldCallback([[maybe_unused]] headset::bthf_chld_type_t chld, [[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void AtCnumCallback([[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void AtCindCallback(RawAddress* bd_addr) override {
|
||
|
// This is required to setup the SLC, the format of the response should be
|
||
|
// +CIND: <call>,<callsetup>,<service>,<signal>,<roam>,<battery>,<callheld>
|
||
|
LOG_WARN("Respond +CIND: 0,0,1,5,0,5,0 to AT+CIND? from %s", bd_addr->ToString().c_str());
|
||
|
|
||
|
// headset::Interface::CindResponse's parameters are similar but different
|
||
|
// from the actual CIND response. It will construct the final response for
|
||
|
// you based on the arguments you provide.
|
||
|
// CindResponse(network_service_availability, active_call_num,
|
||
|
// held_call_num, callsetup_state, signal_strength,
|
||
|
// roam_state, battery_level, bd_addr);
|
||
|
headset_->CindResponse(1, 0, 0, headset::BTHF_CALL_STATE_IDLE, 5, 0, 5, bd_addr);
|
||
|
}
|
||
|
|
||
|
void AtCopsCallback(RawAddress* bd_addr) override {
|
||
|
LOG_WARN("Respond +COPS: 0 to AT+COPS? from %s", bd_addr->ToString().c_str());
|
||
|
headset_->CopsResponse("", bd_addr);
|
||
|
}
|
||
|
|
||
|
void AtClccCallback(RawAddress* bd_addr) override {
|
||
|
// Reply +CLCC:<idx>,<dir>,<status>,<mode>,<mprty>[,<number>,<type>] if
|
||
|
// there is an active audio connection. Simply rely OK otherwise.
|
||
|
// This is required for some headsets to start to send actual data to AG.
|
||
|
if (call_status)
|
||
|
headset_->ClccResponse(
|
||
|
/*index=*/1,
|
||
|
/*dir=*/headset::BTHF_CALL_DIRECTION_OUTGOING,
|
||
|
/*state=*/headset::BTHF_CALL_STATE_ACTIVE,
|
||
|
/*mode=*/headset::BTHF_CALL_TYPE_VOICE,
|
||
|
/*multi_party=*/headset::BTHF_CALL_MPTY_TYPE_SINGLE,
|
||
|
/*number=*/"",
|
||
|
/*type=*/headset::BTHF_CALL_ADDRTYPE_UNKNOWN,
|
||
|
bd_addr);
|
||
|
|
||
|
headset_->AtResponse(headset::BTHF_AT_RESPONSE_OK, 0, bd_addr);
|
||
|
}
|
||
|
|
||
|
void UnknownAtCallback(char* at_string, RawAddress* bd_addr) override {
|
||
|
LOG_WARN("Reply Error to UnknownAtCallback:%s", at_string);
|
||
|
headset_->AtResponse(headset::BTHF_AT_RESPONSE_ERROR, 0, bd_addr);
|
||
|
}
|
||
|
|
||
|
void KeyPressedCallback([[maybe_unused]] RawAddress* bd_addr) override {}
|
||
|
|
||
|
void AtBindCallback(char* at_string, RawAddress* bd_addr) override {
|
||
|
LOG_WARN(
|
||
|
"AT+BIND %s from addr %s: Bluetooth HF Indicators is not supported.", at_string, bd_addr->ToString().c_str());
|
||
|
}
|
||
|
|
||
|
void AtBievCallback(headset::bthf_hf_ind_type_t ind_id, int ind_value, RawAddress* bd_addr) override {
|
||
|
LOG_WARN(
|
||
|
"AT+BIEV=%d,%d from addr %s: Bluetooth HF Indicators is not supported.",
|
||
|
ind_id,
|
||
|
ind_value,
|
||
|
bd_addr->ToString().c_str());
|
||
|
}
|
||
|
|
||
|
void AtBiaCallback(bool service, bool roam, bool signal, bool battery, RawAddress* bd_addr) override {
|
||
|
LOG_WARN("AT+BIA=,,%d,%d,%d,%d,from addr %s", service, signal, roam, battery, bd_addr->ToString().c_str());
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
headset::Interface* headset_;
|
||
|
int call_status;
|
||
|
|
||
|
void SetCallStatus(int call, RawAddress* bd_addr) {
|
||
|
if (call == call_status) return;
|
||
|
|
||
|
if (call) {
|
||
|
// This triggers a +CIEV command to set the call status for HFP
|
||
|
// devices. It is required along with the SCO establishment for some
|
||
|
// devices to provide sound.
|
||
|
headset_->PhoneStateChange(
|
||
|
/*num_active=*/1,
|
||
|
/*num_held=*/0,
|
||
|
/*call_setup_state=*/headset::bthf_call_state_t::BTHF_CALL_STATE_IDLE,
|
||
|
/*number=*/"",
|
||
|
/*type=*/(headset::bthf_call_addrtype_t)0,
|
||
|
/*name=*/"",
|
||
|
/*bd_addr=*/bd_addr);
|
||
|
} else {
|
||
|
headset_->PhoneStateChange(
|
||
|
/*num_active=*/0,
|
||
|
/*num_held=*/0,
|
||
|
/*call_setup_state=*/headset::bthf_call_state_t::BTHF_CALL_STATE_IDLE,
|
||
|
/*number=*/"",
|
||
|
/*type=*/(headset::bthf_call_addrtype_t)0,
|
||
|
/*name=*/"",
|
||
|
/*bd_addr=*/bd_addr);
|
||
|
}
|
||
|
|
||
|
call_status = call;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
int HfpIntf::init() {
|
||
|
return intf_->Init(DBusHeadsetCallbacks::GetInstance(intf_), 1, false);
|
||
|
}
|
||
|
|
||
|
int HfpIntf::connect(RustRawAddress bt_addr) {
|
||
|
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
|
||
|
return intf_->Connect(&addr);
|
||
|
}
|
||
|
|
||
|
int HfpIntf::connect_audio(RustRawAddress bt_addr) {
|
||
|
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
|
||
|
return intf_->ConnectAudio(&addr);
|
||
|
}
|
||
|
|
||
|
int HfpIntf::disconnect(RustRawAddress bt_addr) {
|
||
|
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
|
||
|
return intf_->Disconnect(&addr);
|
||
|
}
|
||
|
|
||
|
int HfpIntf::disconnect_audio(RustRawAddress bt_addr) {
|
||
|
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
|
||
|
return intf_->DisconnectAudio(&addr);
|
||
|
}
|
||
|
|
||
|
void HfpIntf::cleanup() {}
|
||
|
|
||
|
std::unique_ptr<HfpIntf> GetHfpProfile(const unsigned char* btif) {
|
||
|
if (internal::g_hfpif) std::abort();
|
||
|
|
||
|
const bt_interface_t* btif_ = reinterpret_cast<const bt_interface_t*>(btif);
|
||
|
|
||
|
auto hfpif = std::make_unique<HfpIntf>(const_cast<headset::Interface*>(
|
||
|
reinterpret_cast<const headset::Interface*>(btif_->get_profile_interface("handsfree"))));
|
||
|
internal::g_hfpif = hfpif.get();
|
||
|
|
||
|
return hfpif;
|
||
|
}
|
||
|
|
||
|
} // namespace rust
|
||
|
} // namespace topshim
|
||
|
} // namespace bluetooth
|