tenfourfox/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
Cameron Kaiser c9b2922b70 hello FPR
2017-04-19 00:56:45 -07:00

1774 lines
47 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "base/basictypes.h"
#include "BluetoothHfpManager.h"
#include "BluetoothProfileController.h"
#include "BluetoothUtils.h"
#include "jsapi.h"
#include "mozilla/dom/bluetooth/BluetoothTypes.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "nsContentUtils.h"
#include "nsIAudioManager.h"
#include "nsIIccInfo.h"
#include "nsIIccService.h"
#include "nsIMobileConnectionInfo.h"
#include "nsIMobileConnectionService.h"
#include "nsIMobileNetworkInfo.h"
#include "nsIObserverService.h"
#include "nsISettingsService.h"
#include "nsITelephonyService.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/SettingChangeNotificationBinding.h"
#define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
#define AUDIO_VOLUME_BT_SCO_ID "audio.volume.bt_sco"
/**
* Dispatch task with arguments to main thread.
*/
using namespace mozilla;
using namespace mozilla::ipc;
USING_BLUETOOTH_NAMESPACE
namespace {
StaticRefPtr<BluetoothHfpManager> sBluetoothHfpManager;
static BluetoothHandsfreeInterface* sBluetoothHfpInterface = nullptr;
bool sInShutdown = false;
// Wait for 2 seconds for Dialer processing event 'BLDN'. '2' seconds is a
// magic number. The mechanism should be revised once we can get call history.
static int sWaitingForDialingInterval = 2000; //unit: ms
// Wait 3.7 seconds until Dialer stops playing busy tone. '3' seconds is the
// time window set in Dialer and the extra '0.7' second is a magic number.
// The mechanism should be revised once we know the exact time at which
// Dialer stops playing.
static int sBusyToneInterval = 3700; //unit: ms
} // namespace
const int BluetoothHfpManager::MAX_NUM_CLIENTS = 1;
static bool
IsValidDtmf(const char aChar) {
// Valid DTMF: [*#0-9ABCD]
return (aChar == '*' || aChar == '#') ||
(aChar >= '0' && aChar <= '9') ||
(aChar >= 'A' && aChar <= 'D');
}
static bool
IsSupportedChld(const int aChld) {
// We currently only support CHLD=0~3.
return (aChld >= 0 && aChld <= 3);
}
class BluetoothHfpManager::GetVolumeTask final
: public nsISettingsServiceCallback
{
public:
NS_DECL_ISUPPORTS
NS_IMETHOD
Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
{
MOZ_ASSERT(NS_IsMainThread());
JSContext *cx = nsContentUtils::GetCurrentJSContext();
NS_ENSURE_TRUE(cx, NS_OK);
if (!aResult.isNumber()) {
BT_WARNING("'" AUDIO_VOLUME_BT_SCO_ID "' is not a number!");
return NS_OK;
}
BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
hfp->mCurrentVgs = aResult.toNumber();
return NS_OK;
}
NS_IMETHOD
HandleError(const nsAString& aName)
{
BT_WARNING("Unable to get value for '" AUDIO_VOLUME_BT_SCO_ID "'");
return NS_OK;
}
protected:
~GetVolumeTask() { }
};
class BluetoothHfpManager::CloseScoTask : public Task
{
private:
void Run() override
{
MOZ_ASSERT(sBluetoothHfpManager);
sBluetoothHfpManager->DisconnectSco();
}
};
class BluetoothHfpManager::CloseScoRunnable : public nsRunnable
{
public:
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
MessageLoop::current()->PostDelayedTask(
FROM_HERE, new CloseScoTask(), sBusyToneInterval);
return NS_OK;
}
};
class BluetoothHfpManager::RespondToBLDNTask : public Task
{
private:
void Run() override
{
MOZ_ASSERT(sBluetoothHfpManager);
if (!sBluetoothHfpManager->mDialingRequestProcessed) {
sBluetoothHfpManager->mDialingRequestProcessed = true;
sBluetoothHfpManager->SendResponse(HFP_AT_RESPONSE_ERROR);
}
}
};
NS_IMPL_ISUPPORTS(BluetoothHfpManager::GetVolumeTask,
nsISettingsServiceCallback);
/**
* Call
*/
Call::Call()
{
Reset();
}
void
Call::Set(const nsAString& aNumber, const bool aIsOutgoing)
{
mNumber = aNumber;
mDirection = (aIsOutgoing) ? HFP_CALL_DIRECTION_OUTGOING :
HFP_CALL_DIRECTION_INCOMING;
// Same logic as implementation in ril_worker.js
if (aNumber.Length() && aNumber[0] == '+') {
mType = HFP_CALL_ADDRESS_TYPE_INTERNATIONAL;
} else {
mType = HFP_CALL_ADDRESS_TYPE_UNKNOWN;
}
}
void
Call::Reset()
{
mState = nsITelephonyService::CALL_STATE_DISCONNECTED;
mDirection = HFP_CALL_DIRECTION_OUTGOING;
mNumber.Truncate();
mType = HFP_CALL_ADDRESS_TYPE_UNKNOWN;
}
bool
Call::IsActive()
{
return (mState == nsITelephonyService::CALL_STATE_CONNECTED);
}
/**
* BluetoothHfpManager
*/
BluetoothHfpManager::BluetoothHfpManager() : mPhoneType(PhoneType::NONE)
{
Reset();
}
void
BluetoothHfpManager::ResetCallArray()
{
mCurrentCallArray.Clear();
// Append a call object at the beginning of mCurrentCallArray since call
// index from RIL starts at 1.
Call call;
mCurrentCallArray.AppendElement(call);
if (mPhoneType == PhoneType::CDMA) {
mCdmaSecondCall.Reset();
}
}
void
BluetoothHfpManager::Cleanup()
{
mReceiveVgsFlag = false;
mDialingRequestProcessed = true;
mConnectionState = HFP_CONNECTION_STATE_DISCONNECTED;
mPrevConnectionState = HFP_CONNECTION_STATE_DISCONNECTED;
mBattChg = 5;
mService = HFP_NETWORK_STATE_NOT_AVAILABLE;
mRoam = HFP_SERVICE_TYPE_HOME;
mSignal = 0;
mNrecEnabled = HFP_NREC_STARTED;
mController = nullptr;
}
void
BluetoothHfpManager::Reset()
{
// Phone & Device CIND
ResetCallArray();
// Clear Sco state
mAudioState = HFP_AUDIO_STATE_DISCONNECTED;
Cleanup();
}
bool
BluetoothHfpManager::Init()
{
// The function must run at b2g process since it would access SettingsService.
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
NS_ENSURE_TRUE(obs, false);
if (NS_FAILED(obs->AddObserver(this, MOZSETTINGS_CHANGED_ID, false)) ||
NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
BT_WARNING("Failed to add observers!");
return false;
}
hal::RegisterBatteryObserver(this);
// Update to the latest battery level
hal::BatteryInformation batteryInfo;
hal::GetCurrentBatteryInformation(&batteryInfo);
Notify(batteryInfo);
mListener = new BluetoothRilListener();
NS_ENSURE_TRUE(mListener->Listen(true), false);
nsCOMPtr<nsISettingsService> settings =
do_GetService("@mozilla.org/settingsService;1");
NS_ENSURE_TRUE(settings, false);
nsCOMPtr<nsISettingsServiceLock> settingsLock;
nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock));
NS_ENSURE_SUCCESS(rv, false);
RefPtr<GetVolumeTask> callback = new GetVolumeTask();
rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO_ID, callback);
NS_ENSURE_SUCCESS(rv, false);
return true;
}
class BluetoothHfpManager::RegisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
RegisterModuleResultHandler(BluetoothHandsfreeInterface* aInterface,
BluetoothProfileResultHandler* aRes)
: mInterface(aInterface)
, mRes(aRes)
{
MOZ_ASSERT(mInterface);
}
void OnError(BluetoothStatus aStatus) override
{
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::RegisterModule failed for HFP: %d",
(int)aStatus);
mInterface->SetNotificationHandler(nullptr);
if (mRes) {
mRes->OnError(NS_ERROR_FAILURE);
}
}
void RegisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBluetoothHfpInterface = mInterface;
if (mRes) {
mRes->Init();
}
}
private:
BluetoothHandsfreeInterface* mInterface;
RefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothHfpManager::InitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
InitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
MOZ_ASSERT(mRes);
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Init();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
private:
RefPtr<BluetoothProfileResultHandler> mRes;
nsresult mRv;
};
// static
void
BluetoothHfpManager::InitHfpInterface(BluetoothProfileResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
if (sBluetoothHfpInterface) {
BT_LOGR("Bluetooth Handsfree interface is already initalized.");
RefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP Init runnable");
}
return;
}
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no backend interface, we dispatch a runnable
// that calls the profile result handler.
RefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
RefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
auto interface = btInf->GetBluetoothHandsfreeInterface();
if (NS_WARN_IF(!interface)) {
// If there's no HFP interface, we dispatch a runnable
// that calls the profile result handler.
RefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
// Set notification handler _before_ registering the module. It could
// happen that we receive notifications, before the result handler runs.
interface->SetNotificationHandler(BluetoothHfpManager::Get());
setupInterface->RegisterModule(
SETUP_SERVICE_ID_HANDSFREE, MODE_NARROWBAND_SPEECH, MAX_NUM_CLIENTS,
new RegisterModuleResultHandler(interface, aRes));
}
BluetoothHfpManager::~BluetoothHfpManager()
{
if (!mListener->Listen(false)) {
BT_WARNING("Failed to stop listening RIL");
}
mListener = nullptr;
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
NS_ENSURE_TRUE_VOID(obs);
if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) ||
NS_FAILED(obs->RemoveObserver(this, MOZSETTINGS_CHANGED_ID))) {
BT_WARNING("Failed to remove observers!");
}
hal::UnregisterBatteryObserver(this);
}
class BluetoothHfpManager::UnregisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
UnregisterModuleResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
{ }
void OnError(BluetoothStatus aStatus) override
{
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::UnregisterModule failed for HFP: %d",
(int)aStatus);
sBluetoothHfpInterface->SetNotificationHandler(nullptr);
sBluetoothHfpInterface = nullptr;
if (mRes) {
mRes->OnError(NS_ERROR_FAILURE);
}
}
void UnregisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBluetoothHfpInterface->SetNotificationHandler(nullptr);
sBluetoothHfpInterface = nullptr;
if (mRes) {
mRes->Deinit();
}
}
private:
RefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothHfpManager::DeinitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
DeinitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
MOZ_ASSERT(mRes);
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Deinit();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
private:
RefPtr<BluetoothProfileResultHandler> mRes;
nsresult mRv;
};
// static
void
BluetoothHfpManager::DeinitHfpInterface(BluetoothProfileResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
if (!sBluetoothHfpInterface) {
BT_LOGR("Bluetooth Handsfree interface has not been initialized.");
RefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP Deinit runnable");
}
return;
}
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no backend interface, we dispatch a runnable
// that calls the profile result handler.
RefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
RefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
setupInterface->UnregisterModule(
SETUP_SERVICE_ID_HANDSFREE,
new UnregisterModuleResultHandler(aRes));
}
//static
BluetoothHfpManager*
BluetoothHfpManager::Get()
{
MOZ_ASSERT(NS_IsMainThread());
// If sBluetoothHfpManager already exists, exit early
if (sBluetoothHfpManager) {
return sBluetoothHfpManager;
}
// If we're in shutdown, don't create a new instance
NS_ENSURE_FALSE(sInShutdown, nullptr);
// Create a new instance, register, and return
BluetoothHfpManager* manager = new BluetoothHfpManager();
NS_ENSURE_TRUE(manager->Init(), nullptr);
sBluetoothHfpManager = manager;
return sBluetoothHfpManager;
}
NS_IMETHODIMP
BluetoothHfpManager::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
HandleVolumeChanged(aSubject);
} else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
HandleShutdown();
} else {
MOZ_ASSERT(false, "BluetoothHfpManager got unexpected topic!");
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
void
BluetoothHfpManager::Notify(const hal::BatteryInformation& aBatteryInfo)
{
// Range of battery level: [0, 1], double
// Range of CIND::BATTCHG: [0, 5], int
mBattChg = (int) round(aBatteryInfo.level() * 5.0);
UpdateDeviceCIND();
}
void
BluetoothHfpManager::NotifyConnectionStateChanged(const nsAString& aType)
{
MOZ_ASSERT(NS_IsMainThread());
// Notify Gecko observers
nsCOMPtr<nsIObserverService> obs =
do_GetService("@mozilla.org/observer-service;1");
NS_ENSURE_TRUE_VOID(obs);
nsAutoString deviceAddressStr;
AddressToString(mDeviceAddress, deviceAddressStr);
if (NS_FAILED(obs->NotifyObservers(this, NS_ConvertUTF16toUTF8(aType).get(),
deviceAddressStr.get()))) {
BT_WARNING("Failed to notify observsers!");
}
// Dispatch an event of status change
bool status;
nsAutoString eventName;
if (aType.EqualsLiteral(BLUETOOTH_HFP_STATUS_CHANGED_ID)) {
status = IsConnected();
eventName.AssignLiteral(HFP_STATUS_CHANGED_ID);
} else if (aType.EqualsLiteral(BLUETOOTH_SCO_STATUS_CHANGED_ID)) {
status = IsScoConnected();
eventName.AssignLiteral(SCO_STATUS_CHANGED_ID);
} else {
MOZ_ASSERT(false);
return;
}
DispatchStatusChangedEvent(eventName, deviceAddressStr, status);
// Notify profile controller
if (aType.EqualsLiteral(BLUETOOTH_HFP_STATUS_CHANGED_ID)) {
if (IsConnected()) {
MOZ_ASSERT(mListener);
// Enumerate current calls
mListener->EnumerateCalls();
OnConnect(EmptyString());
} else if (mConnectionState == HFP_CONNECTION_STATE_DISCONNECTED) {
mDeviceAddress.Clear();
if (mPrevConnectionState == HFP_CONNECTION_STATE_DISCONNECTED) {
// Bug 979160: This implies the outgoing connection failure.
// When the outgoing hfp connection fails, state changes to disconnected
// state. Since bluedroid would not report connecting state, but only
// report connected/disconnected.
OnConnect(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
} else {
OnDisconnect(EmptyString());
}
Cleanup();
}
}
}
void
BluetoothHfpManager::NotifyDialer(const nsAString& aCommand)
{
NS_NAMED_LITERAL_STRING(type, "bluetooth-dialer-command");
InfallibleTArray<BluetoothNamedValue> parameters;
AppendNamedValue(parameters, "command", nsString(aCommand));
BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters);
}
class BluetoothHfpManager::VolumeControlResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::VolumeControl failed: %d",
(int)aStatus);
}
};
void
BluetoothHfpManager::HandleVolumeChanged(nsISupports* aSubject)
{
MOZ_ASSERT(NS_IsMainThread());
// The string that we're interested in will be a JSON string that looks like:
// {"key":"volumeup", "value":10}
// {"key":"volumedown", "value":2}
RootedDictionary<dom::SettingChangeNotification> setting(nsContentUtils::RootingCx());
if (!WrappedJSToDictionary(aSubject, setting)) {
return;
}
if (!setting.mKey.EqualsASCII(AUDIO_VOLUME_BT_SCO_ID)) {
return;
}
if (!setting.mValue.isNumber()) {
return;
}
mCurrentVgs = setting.mValue.toNumber();
// Adjust volume by headset and we don't have to send volume back to headset
if (mReceiveVgsFlag) {
mReceiveVgsFlag = false;
return;
}
// Only send volume back when there's a connected headset
if (IsConnected()) {
NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
sBluetoothHfpInterface->VolumeControl(
HFP_VOLUME_TYPE_SPEAKER, mCurrentVgs, mDeviceAddress,
new VolumeControlResultHandler());
}
}
void
BluetoothHfpManager::HandleVoiceConnectionChanged(uint32_t aClientId)
{
nsCOMPtr<nsIMobileConnectionService> mcService =
do_GetService(NS_MOBILE_CONNECTION_SERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(mcService);
nsCOMPtr<nsIMobileConnection> connection;
mcService->GetItemByServiceId(aClientId, getter_AddRefs(connection));
NS_ENSURE_TRUE_VOID(connection);
nsCOMPtr<nsIMobileConnectionInfo> voiceInfo;
connection->GetVoice(getter_AddRefs(voiceInfo));
NS_ENSURE_TRUE_VOID(voiceInfo);
nsString type;
voiceInfo->GetType(type);
mPhoneType = GetPhoneType(type);
// Roam
bool roaming;
voiceInfo->GetRoaming(&roaming);
mRoam = (roaming) ? HFP_SERVICE_TYPE_ROAMING : HFP_SERVICE_TYPE_HOME;
// Service
nsString regState;
voiceInfo->GetState(regState);
BluetoothHandsfreeNetworkState service =
(regState.EqualsLiteral("registered")) ? HFP_NETWORK_STATE_AVAILABLE :
HFP_NETWORK_STATE_NOT_AVAILABLE;
if (service != mService) {
// Notify BluetoothRilListener of service change
mListener->ServiceChanged(aClientId, service);
}
mService = service;
// Signal
JS::Rooted<JS::Value> value(nsContentUtils::RootingCxForThread());
voiceInfo->GetRelSignalStrength(&value);
if (value.isNumber()) {
mSignal = (int)ceil(value.toNumber() / 20.0);
}
UpdateDeviceCIND();
// Operator name
nsCOMPtr<nsIMobileNetworkInfo> network;
voiceInfo->GetNetwork(getter_AddRefs(network));
if (!network) {
BT_LOGD("Unable to get network information");
return;
}
network->GetLongName(mOperatorName);
// According to GSM 07.07, "<format> indicates if the format is alphanumeric
// or numeric; long alphanumeric format can be upto 16 characters long and
// short format up to 8 characters (refer GSM MoU SE.13 [9])..."
// However, we found that the operator name may sometimes be longer than 16
// characters. After discussion, we decided to fix this here but not in RIL
// or modem.
//
// Please see Bug 871366 for more information.
if (mOperatorName.Length() > 16) {
BT_WARNING("The operator name was longer than 16 characters. We cut it.");
mOperatorName.Left(mOperatorName, 16);
}
}
void
BluetoothHfpManager::HandleIccInfoChanged(uint32_t aClientId)
{
nsCOMPtr<nsIIccService> service =
do_GetService(ICC_SERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(service);
nsCOMPtr<nsIIcc> icc;
service->GetIccByServiceId(aClientId, getter_AddRefs(icc));
NS_ENSURE_TRUE_VOID(icc);
nsCOMPtr<nsIIccInfo> iccInfo;
icc->GetIccInfo(getter_AddRefs(iccInfo));
NS_ENSURE_TRUE_VOID(iccInfo);
nsCOMPtr<nsIGsmIccInfo> gsmIccInfo = do_QueryInterface(iccInfo);
NS_ENSURE_TRUE_VOID(gsmIccInfo);
gsmIccInfo->GetMsisdn(mMsisdn);
}
void
BluetoothHfpManager::HandleShutdown()
{
MOZ_ASSERT(NS_IsMainThread());
sInShutdown = true;
Disconnect(nullptr);
DisconnectSco();
sBluetoothHfpManager = nullptr;
}
class BluetoothHfpManager::ClccResponseResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::ClccResponse failed: %d",
(int)aStatus);
}
};
void
BluetoothHfpManager::SendCLCC(Call& aCall, int aIndex)
{
NS_ENSURE_TRUE_VOID(aCall.mState !=
nsITelephonyService::CALL_STATE_DISCONNECTED);
NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
BluetoothHandsfreeCallState callState =
ConvertToBluetoothHandsfreeCallState(aCall.mState);
if (mPhoneType == PhoneType::CDMA && aIndex == 1 && aCall.IsActive()) {
callState = (mCdmaSecondCall.IsActive()) ? HFP_CALL_STATE_HELD :
HFP_CALL_STATE_ACTIVE;
}
if (callState == HFP_CALL_STATE_INCOMING &&
FindFirstCall(nsITelephonyService::CALL_STATE_CONNECTED)) {
callState = HFP_CALL_STATE_WAITING;
}
sBluetoothHfpInterface->ClccResponse(
aIndex, aCall.mDirection, callState, HFP_CALL_MODE_VOICE,
HFP_CALL_MPTY_TYPE_SINGLE, aCall.mNumber,
aCall.mType, mDeviceAddress, new ClccResponseResultHandler());
}
class BluetoothHfpManager::FormattedAtResponseResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::FormattedAtResponse failed: %d",
(int)aStatus);
}
};
void
BluetoothHfpManager::SendLine(const char* aMessage)
{
NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
sBluetoothHfpInterface->FormattedAtResponse(
aMessage, mDeviceAddress, new FormattedAtResponseResultHandler());
}
class BluetoothHfpManager::AtResponseResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::AtResponse failed: %d",
(int)aStatus);
}
};
void
BluetoothHfpManager::SendResponse(BluetoothHandsfreeAtResponse aResponseCode)
{
NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
sBluetoothHfpInterface->AtResponse(
aResponseCode, 0, mDeviceAddress, new AtResponseResultHandler());
}
class BluetoothHfpManager::PhoneStateChangeResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::PhoneStateChange failed: %d",
(int)aStatus);
}
};
void
BluetoothHfpManager::UpdatePhoneCIND(uint32_t aCallIndex)
{
NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
int numActive = GetNumberOfCalls(nsITelephonyService::CALL_STATE_CONNECTED);
int numHeld = GetNumberOfCalls(nsITelephonyService::CALL_STATE_HELD);
BluetoothHandsfreeCallState callSetupState =
ConvertToBluetoothHandsfreeCallState(GetCallSetupState());
BluetoothHandsfreeCallAddressType type = mCurrentCallArray[aCallIndex].mType;
BT_LOGR("[%d] state %d => BTHF: active[%d] held[%d] setupstate[%d]",
aCallIndex, mCurrentCallArray[aCallIndex].mState,
numActive, numHeld, callSetupState);
sBluetoothHfpInterface->PhoneStateChange(
numActive, numHeld, callSetupState,
mCurrentCallArray[aCallIndex].mNumber, type,
new PhoneStateChangeResultHandler());
}
class BluetoothHfpManager::DeviceStatusNotificationResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING(
"BluetoothHandsfreeInterface::DeviceStatusNotification failed: %d",
(int)aStatus);
}
};
void
BluetoothHfpManager::UpdateDeviceCIND()
{
if (sBluetoothHfpInterface) {
sBluetoothHfpInterface->DeviceStatusNotification(
mService,
mRoam,
mSignal,
mBattChg, new DeviceStatusNotificationResultHandler());
}
}
uint32_t
BluetoothHfpManager::FindFirstCall(uint16_t aState)
{
uint32_t callLength = mCurrentCallArray.Length();
for (uint32_t i = 1; i < callLength; ++i) {
if (mCurrentCallArray[i].mState == aState) {
return i;
}
}
return 0;
}
uint32_t
BluetoothHfpManager::GetNumberOfCalls(uint16_t aState)
{
uint32_t num = 0;
uint32_t callLength = mCurrentCallArray.Length();
for (uint32_t i = 1; i < callLength; ++i) {
if (mCurrentCallArray[i].mState == aState) {
++num;
}
}
return num;
}
uint16_t
BluetoothHfpManager::GetCallSetupState()
{
uint32_t callLength = mCurrentCallArray.Length();
for (uint32_t i = 1; i < callLength; ++i) {
switch (mCurrentCallArray[i].mState) {
case nsITelephonyService::CALL_STATE_INCOMING:
case nsITelephonyService::CALL_STATE_DIALING:
case nsITelephonyService::CALL_STATE_ALERTING:
return mCurrentCallArray[i].mState;
default:
break;
}
}
return nsITelephonyService::CALL_STATE_DISCONNECTED;
}
BluetoothHandsfreeCallState
BluetoothHfpManager::ConvertToBluetoothHandsfreeCallState(int aCallState) const
{
BluetoothHandsfreeCallState state;
// Refer to AOSP BluetoothPhoneService.convertCallState
if (aCallState == nsITelephonyService::CALL_STATE_INCOMING) {
state = HFP_CALL_STATE_INCOMING;
} else if (aCallState == nsITelephonyService::CALL_STATE_DIALING) {
state = HFP_CALL_STATE_DIALING;
} else if (aCallState == nsITelephonyService::CALL_STATE_ALERTING) {
state = HFP_CALL_STATE_ALERTING;
} else if (aCallState == nsITelephonyService::CALL_STATE_CONNECTED) {
state = HFP_CALL_STATE_ACTIVE;
} else if (aCallState == nsITelephonyService::CALL_STATE_HELD) {
state = HFP_CALL_STATE_HELD;
} else { // disconnected
state = HFP_CALL_STATE_IDLE;
}
return state;
}
bool
BluetoothHfpManager::IsTransitionState(uint16_t aCallState, bool aIsConference)
{
/**
* Regard this callstate change as during CHLD=2 transition state if
* - the call becomes active, and numActive > 1
* - the call becomes held, and numHeld > 1 or an incoming call exists
*
* TODO:
* 1) handle CHLD=1 transition state
* 2) handle conference call cases
*/
if (!aIsConference) {
switch (aCallState) {
case nsITelephonyService::CALL_STATE_CONNECTED:
return (GetNumberOfCalls(aCallState) > 1);
case nsITelephonyService::CALL_STATE_HELD:
return (GetNumberOfCalls(aCallState) > 1 ||
FindFirstCall(nsITelephonyService::CALL_STATE_INCOMING));
default:
break;
}
}
return false;
}
void
BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex,
uint16_t aCallState,
const nsAString& aError,
const nsAString& aNumber,
const bool aIsOutgoing,
const bool aIsConference,
bool aSend)
{
// aCallIndex can be UINT32_MAX for the pending outgoing call state update.
// aCallIndex will be updated again after real call state changes. See Bug
// 990467.
if (aCallIndex == UINT32_MAX) {
return;
}
// Update call state only
while (aCallIndex >= mCurrentCallArray.Length()) {
Call call;
mCurrentCallArray.AppendElement(call);
}
mCurrentCallArray[aCallIndex].mState = aCallState;
// Return if SLC is disconnected
if (!IsConnected()) {
return;
}
// Update call information besides call state
mCurrentCallArray[aCallIndex].Set(aNumber, aIsOutgoing);
// Notify bluedroid of phone state change if this
// call state change is not during transition state
if (!IsTransitionState(aCallState, aIsConference)) {
UpdatePhoneCIND(aCallIndex);
}
switch (aCallState) {
case nsITelephonyService::CALL_STATE_DIALING:
// We've send Dialer a dialing request and this is the response.
if (!mDialingRequestProcessed) {
SendResponse(HFP_AT_RESPONSE_OK);
mDialingRequestProcessed = true;
}
break;
case nsITelephonyService::CALL_STATE_DISCONNECTED:
// -1 is necessary because call 0 is an invalid (padding) call object.
if (mCurrentCallArray.Length() - 1 ==
GetNumberOfCalls(nsITelephonyService::CALL_STATE_DISCONNECTED)) {
// In order to let user hear busy tone via connected Bluetooth headset,
// we postpone the timing of dropping SCO.
if (aError.EqualsLiteral("BusyError")) {
// FIXME: UpdatePhoneCIND later since it causes SCO close but
// Dialer is still playing busy tone via HF.
NS_DispatchToMainThread(new CloseScoRunnable());
}
ResetCallArray();
}
break;
default:
break;
}
}
PhoneType
BluetoothHfpManager::GetPhoneType(const nsAString& aType)
{
// FIXME: Query phone type from RIL after RIL implements new API (bug 912019)
if (aType.EqualsLiteral("gsm") || aType.EqualsLiteral("gprs") ||
aType.EqualsLiteral("edge") || aType.EqualsLiteral("umts") ||
aType.EqualsLiteral("hspa") || aType.EqualsLiteral("hsdpa") ||
aType.EqualsLiteral("hsupa") || aType.EqualsLiteral("hspa+")) {
return PhoneType::GSM;
} else if (aType.EqualsLiteral("is95a") || aType.EqualsLiteral("is95b") ||
aType.EqualsLiteral("1xrtt") || aType.EqualsLiteral("evdo0") ||
aType.EqualsLiteral("evdoa") || aType.EqualsLiteral("evdob")) {
return PhoneType::CDMA;
}
return PhoneType::NONE;
}
void
BluetoothHfpManager::UpdateSecondNumber(const nsAString& aNumber)
{
MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
// Always regard second call as incoming call since v1.2 RIL
// doesn't support outgoing second call in CDMA.
mCdmaSecondCall.Set(aNumber, false);
// FIXME: check CDMA + bluedroid
//UpdateCIND(CINDType::CALLSETUP, CallSetupState::INCOMING, true);
}
void
BluetoothHfpManager::AnswerWaitingCall()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
// Pick up second call. First call is held now.
mCdmaSecondCall.mState = nsITelephonyService::CALL_STATE_CONNECTED;
// FIXME: check CDMA + bluedroid
//UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, true);
//sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE;
//SendCommand("+CIEV: ", CINDType::CALLHELD);
}
void
BluetoothHfpManager::IgnoreWaitingCall()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
mCdmaSecondCall.Reset();
// FIXME: check CDMA + bluedroid
//UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, true);
}
void
BluetoothHfpManager::ToggleCalls()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
// Toggle acitve and held calls
mCdmaSecondCall.mState = (mCdmaSecondCall.IsActive()) ?
nsITelephonyService::CALL_STATE_HELD :
nsITelephonyService::CALL_STATE_CONNECTED;
}
/*
* Reset connection state and audio state to DISCONNECTED to handle backend
* error. The state change triggers UI status bar update as ordinary bluetooth
* turn-off sequence.
*/
void
BluetoothHfpManager::HandleBackendError()
{
MOZ_ASSERT(NS_IsMainThread());
if (mConnectionState != HFP_CONNECTION_STATE_DISCONNECTED) {
ConnectionStateNotification(HFP_CONNECTION_STATE_DISCONNECTED,
mDeviceAddress);
}
if (mAudioState != HFP_AUDIO_STATE_DISCONNECTED) {
AudioStateNotification(HFP_AUDIO_STATE_DISCONNECTED, mDeviceAddress);
}
}
class BluetoothHfpManager::ConnectAudioResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::ConnectAudio failed: %d",
(int)aStatus);
}
};
bool
BluetoothHfpManager::ConnectSco()
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE(!sInShutdown, false);
NS_ENSURE_TRUE(IsConnected() && !IsScoConnected(), false);
NS_ENSURE_TRUE(sBluetoothHfpInterface, false);
sBluetoothHfpInterface->ConnectAudio(mDeviceAddress,
new ConnectAudioResultHandler());
return true;
}
class BluetoothHfpManager::DisconnectAudioResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::DisconnectAudio failed: %d",
(int)aStatus);
}
};
bool
BluetoothHfpManager::DisconnectSco()
{
NS_ENSURE_TRUE(IsScoConnected(), false);
NS_ENSURE_TRUE(sBluetoothHfpInterface, false);
sBluetoothHfpInterface->DisconnectAudio(mDeviceAddress,
new DisconnectAudioResultHandler());
return true;
}
bool
BluetoothHfpManager::IsScoConnected()
{
return (mAudioState == HFP_AUDIO_STATE_CONNECTED);
}
bool
BluetoothHfpManager::IsConnected()
{
return (mConnectionState == HFP_CONNECTION_STATE_SLC_CONNECTED);
}
bool
BluetoothHfpManager::IsNrecEnabled()
{
return mNrecEnabled;
}
void
BluetoothHfpManager::OnConnectError()
{
MOZ_ASSERT(NS_IsMainThread());
mController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
mController = nullptr;
mDeviceAddress.Clear();
}
class BluetoothHfpManager::ConnectResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
ConnectResultHandler(BluetoothHfpManager* aHfpManager)
: mHfpManager(aHfpManager)
{
MOZ_ASSERT(mHfpManager);
}
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::Connect failed: %d",
(int)aStatus);
mHfpManager->OnConnectError();
}
private:
BluetoothHfpManager* mHfpManager;
};
void
BluetoothHfpManager::Connect(const BluetoothAddress& aDeviceAddress,
BluetoothProfileController* aController)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aController && !mController);
if (sInShutdown) {
aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
return;
}
if (!sBluetoothHfpInterface) {
BT_LOGR("sBluetoothHfpInterface is null");
aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
return;
}
mDeviceAddress = aDeviceAddress;
mController = aController;
sBluetoothHfpInterface->Connect(mDeviceAddress,
new ConnectResultHandler(this));
}
void
BluetoothHfpManager::OnDisconnectError()
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(mController);
mController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
}
class BluetoothHfpManager::DisconnectResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
DisconnectResultHandler(BluetoothHfpManager* aHfpManager)
: mHfpManager(aHfpManager)
{
MOZ_ASSERT(mHfpManager);
}
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::Disconnect failed: %d",
(int)aStatus);
mHfpManager->OnDisconnectError();
}
private:
BluetoothHfpManager* mHfpManager;
};
void
BluetoothHfpManager::Disconnect(BluetoothProfileController* aController)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mController);
if (!sBluetoothHfpInterface) {
BT_LOGR("sBluetoothHfpInterface is null");
if (aController) {
aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
}
return;
}
mController = aController;
sBluetoothHfpInterface->Disconnect(mDeviceAddress,
new DisconnectResultHandler(this));
}
void
BluetoothHfpManager::OnConnect(const nsAString& aErrorStr)
{
MOZ_ASSERT(NS_IsMainThread());
/**
* On the one hand, notify the controller that we've done for outbound
* connections. On the other hand, we do nothing for inbound connections.
*/
NS_ENSURE_TRUE_VOID(mController);
mController->NotifyCompletion(aErrorStr);
mController = nullptr;
}
void
BluetoothHfpManager::OnDisconnect(const nsAString& aErrorStr)
{
MOZ_ASSERT(NS_IsMainThread());
/**
* On the one hand, notify the controller that we've done for outbound
* connections. On the other hand, we do nothing for inbound connections.
*/
NS_ENSURE_TRUE_VOID(mController);
mController->NotifyCompletion(aErrorStr);
mController = nullptr;
}
void
BluetoothHfpManager::OnUpdateSdpRecords(
const BluetoothAddress& aDeviceAddress)
{
// Bluedroid handles this part
MOZ_ASSERT(false);
}
void
BluetoothHfpManager::OnGetServiceChannel(
const BluetoothAddress& aDeviceAddress,
const BluetoothUuid& aServiceUuid,
int aChannel)
{
// Bluedroid handles this part
MOZ_ASSERT(false);
}
void
BluetoothHfpManager::GetAddress(BluetoothAddress& aDeviceAddress)
{
aDeviceAddress = mDeviceAddress;
}
//
// Bluetooth notifications
//
void
BluetoothHfpManager::ConnectionStateNotification(
BluetoothHandsfreeConnectionState aState, const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
BT_LOGR("state %d", aState);
mPrevConnectionState = mConnectionState;
mConnectionState = aState;
if (aState == HFP_CONNECTION_STATE_SLC_CONNECTED) {
mDeviceAddress = aBdAddress;
NotifyConnectionStateChanged(
NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID));
} else if (aState == HFP_CONNECTION_STATE_DISCONNECTED) {
DisconnectSco();
NotifyConnectionStateChanged(
NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID));
} else if (aState == HFP_CONNECTION_STATE_CONNECTED) {
// Once RFCOMM is connected, enable NREC before each new SLC connection
NRECNotification(HFP_NREC_STARTED, mDeviceAddress);
}
}
void
BluetoothHfpManager::AudioStateNotification(
BluetoothHandsfreeAudioState aState, const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
BT_LOGR("state %d", aState);
mAudioState = aState;
if (aState == HFP_AUDIO_STATE_CONNECTED ||
aState == HFP_AUDIO_STATE_DISCONNECTED) {
NotifyConnectionStateChanged(
NS_LITERAL_STRING(BLUETOOTH_SCO_STATUS_CHANGED_ID));
}
}
void
BluetoothHfpManager::AnswerCallNotification(const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
NotifyDialer(NS_LITERAL_STRING("ATA"));
}
void
BluetoothHfpManager::HangupCallNotification(const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
NotifyDialer(NS_LITERAL_STRING("CHUP"));
}
void
BluetoothHfpManager::VolumeNotification(
BluetoothHandsfreeVolumeType aType, int aVolume, const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(aVolume >= 0 && aVolume <= 15);
if (aType == HFP_VOLUME_TYPE_MICROPHONE) {
mCurrentVgm = aVolume;
} else if (aType == HFP_VOLUME_TYPE_SPEAKER) {
mReceiveVgsFlag = true;
if (aVolume == mCurrentVgs) {
// Keep current volume
return;
}
nsString data;
data.AppendInt(aVolume);
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
NS_ENSURE_TRUE_VOID(os);
os->NotifyObservers(nullptr, "bluetooth-volume-change", data.get());
}
}
void
BluetoothHfpManager::DtmfNotification(char aDtmf, const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(IsValidDtmf(aDtmf));
nsAutoCString message("VTS=");
message += aDtmf;
NotifyDialer(NS_ConvertUTF8toUTF16(message));
}
/**
* NREC status will be set when:
* 1. Get an AT command from HF device.
* (Bluetooth HFP spec v1.6 merely defines for the "Disable" part.)
* 2. Once RFCOMM is connected, enable NREC before each new SLC connection.
*/
void
BluetoothHfpManager::NRECNotification(BluetoothHandsfreeNRECState aNrec,
const BluetoothAddress& aBdAddr)
{
MOZ_ASSERT(NS_IsMainThread());
// Notify Gecko observers
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
NS_ENSURE_TRUE_VOID(obs);
mNrecEnabled = static_cast<bool>(aNrec);
nsAutoString deviceAddressStr;
AddressToString(mDeviceAddress, deviceAddressStr);
// Notify audio manager
if (NS_FAILED(obs->NotifyObservers(this,
BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID,
deviceAddressStr.get()))) {
BT_WARNING("Failed to notify bluetooth-hfp-nrec-status-changed observsers!");
}
}
void
BluetoothHfpManager::CallHoldNotification(BluetoothHandsfreeCallHoldType aChld,
const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsSupportedChld((int)aChld)) {
// We currently don't support Enhanced Call Control.
// AT+CHLD=1x and AT+CHLD=2x will be ignored
SendResponse(HFP_AT_RESPONSE_ERROR);
return;
}
SendResponse(HFP_AT_RESPONSE_OK);
nsAutoCString message("CHLD=");
message.AppendInt((int)aChld);
NotifyDialer(NS_ConvertUTF8toUTF16(message));
}
void BluetoothHfpManager::DialCallNotification(const nsAString& aNumber,
const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
nsCString message = NS_ConvertUTF16toUTF8(aNumber);
// There are three cases based on aNumber,
// 1) Empty value: Redial, BLDN
// 2) >xxx: Memory dial, ATD>xxx
// 3) xxx: Normal dial, ATDxxx
// We need to respond OK/Error for dial requests for every case listed above,
// 1) and 2): Respond in either RespondToBLDNTask or
// HandleCallStateChanged()
// 3): Respond here
if (message.IsEmpty()) {
mDialingRequestProcessed = false;
NotifyDialer(NS_LITERAL_STRING("BLDN"));
MessageLoop::current()->PostDelayedTask(FROM_HERE,
new RespondToBLDNTask(),
sWaitingForDialingInterval);
} else if (message[0] == '>') {
mDialingRequestProcessed = false;
nsAutoCString newMsg("ATD");
newMsg += StringHead(message, message.Length() - 1);
NotifyDialer(NS_ConvertUTF8toUTF16(newMsg));
MessageLoop::current()->PostDelayedTask(FROM_HERE,
new RespondToBLDNTask(),
sWaitingForDialingInterval);
} else {
SendResponse(HFP_AT_RESPONSE_OK);
nsAutoCString newMsg("ATD");
newMsg += StringHead(message, message.Length() - 1);
NotifyDialer(NS_ConvertUTF8toUTF16(newMsg));
}
}
void
BluetoothHfpManager::CnumNotification(const BluetoothAddress& aBdAddress)
{
static const uint8_t sAddressType[] {
[HFP_CALL_ADDRESS_TYPE_UNKNOWN] = 0x81,
[HFP_CALL_ADDRESS_TYPE_INTERNATIONAL] = 0x91 // for completeness
};
MOZ_ASSERT(NS_IsMainThread());
if (!mMsisdn.IsEmpty()) {
nsAutoCString message("+CNUM: ,\"");
message.Append(NS_ConvertUTF16toUTF8(mMsisdn).get());
message.AppendLiteral("\",");
message.AppendInt(sAddressType[HFP_CALL_ADDRESS_TYPE_UNKNOWN]);
message.AppendLiteral(",,4");
SendLine(message.get());
}
SendResponse(HFP_AT_RESPONSE_OK);
}
class BluetoothHfpManager::CindResponseResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::CindResponse failed: %d",
(int)aStatus);
}
};
void
BluetoothHfpManager::CindNotification(const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
int numActive = GetNumberOfCalls(nsITelephonyService::CALL_STATE_CONNECTED);
int numHeld = GetNumberOfCalls(nsITelephonyService::CALL_STATE_HELD);
BluetoothHandsfreeCallState callState =
ConvertToBluetoothHandsfreeCallState(GetCallSetupState());
sBluetoothHfpInterface->CindResponse(
mService, numActive, numHeld,
callState, mSignal, mRoam, mBattChg,
aBdAddress,
new CindResponseResultHandler());
}
class BluetoothHfpManager::CopsResponseResultHandler final
: public BluetoothHandsfreeResultHandler
{
public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::CopsResponse failed: %d",
(int)aStatus);
}
};
void
BluetoothHfpManager::CopsNotification(const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
sBluetoothHfpInterface->CopsResponse(
NS_ConvertUTF16toUTF8(mOperatorName).get(),
aBdAddress, new CopsResponseResultHandler());
}
void
BluetoothHfpManager::ClccNotification(const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
uint32_t callNumbers = mCurrentCallArray.Length();
uint32_t i;
for (i = 1; i < callNumbers; i++) {
SendCLCC(mCurrentCallArray[i], i);
}
if (!mCdmaSecondCall.mNumber.IsEmpty()) {
MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
MOZ_ASSERT(i == 2);
SendCLCC(mCdmaSecondCall, 2);
}
SendResponse(HFP_AT_RESPONSE_OK);
}
void
BluetoothHfpManager::UnknownAtNotification(const nsACString& aAtString,
const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
BT_LOGR("[%s]", nsCString(aAtString).get());
SendResponse(HFP_AT_RESPONSE_ERROR);
}
void
BluetoothHfpManager::KeyPressedNotification(const BluetoothAddress& aBdAddress)
{
MOZ_ASSERT(NS_IsMainThread());
bool hasActiveCall =
(FindFirstCall(nsITelephonyService::CALL_STATE_CONNECTED) > 0);
// Refer to AOSP HeadsetStateMachine.processKeyPressed
if (FindFirstCall(nsITelephonyService::CALL_STATE_INCOMING)
&& !hasActiveCall) {
/*
* Bluetooth HSP spec 4.2.2
* There is an incoming call, notify Dialer to pick up the phone call
* and SCO will be established after we get the CallStateChanged event
* indicating the call is answered successfully.
*/
NotifyDialer(NS_LITERAL_STRING("ATA"));
} else if (hasActiveCall) {
if (!IsScoConnected()) {
/*
* Bluetooth HSP spec 4.3
* If there's no SCO, set up a SCO link.
*/
ConnectSco();
} else {
/*
* Bluetooth HSP spec 4.5
* There are two ways to release SCO: sending CHUP to dialer or closing
* SCO socket directly. We notify dialer only if there is at least one
* active call.
*/
NotifyDialer(NS_LITERAL_STRING("CHUP"));
}
} else {
// BLDN
NotifyDialer(NS_LITERAL_STRING("BLDN"));
MessageLoop::current()->PostDelayedTask(FROM_HERE,
new RespondToBLDNTask(),
sWaitingForDialingInterval);
}
}
NS_IMPL_ISUPPORTS(BluetoothHfpManager, nsIObserver)