mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
74e132a6bd
For each profile manager, we'd like to make sure that the data member of mController is nulled out before invoking the callback function. Because, in the callback function, we continue to handle the next connect/disconnect request, and we'd like to null out mController of previous request before handling the new one.
1841 lines
51 KiB
C++
1841 lines
51 KiB
C++
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
|
|
/* vim: set ts=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 "BluetoothReplyRunnable.h"
|
|
#include "BluetoothService.h"
|
|
#include "BluetoothSocket.h"
|
|
#include "BluetoothUtils.h"
|
|
#include "BluetoothUuid.h"
|
|
|
|
#include "MobileConnection.h"
|
|
#include "mozilla/dom/bluetooth/BluetoothTypes.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIAudioManager.h"
|
|
#include "nsIDOMIccInfo.h"
|
|
#include "nsIIccProvider.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsISettingsService.h"
|
|
#include "nsITelephonyProvider.h"
|
|
#include "nsRadioInterfaceLayer.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
|
|
/**
|
|
* BRSF bitmask of AG supported features. See 4.34.1 "Bluetooth Defined AT
|
|
* Capabilities" in Bluetooth hands-free profile 1.6
|
|
*/
|
|
#define BRSF_BIT_THREE_WAY_CALLING 1
|
|
#define BSRF_BIT_EC_NR_FUNCTION (1 << 1)
|
|
#define BRSF_BIT_VOICE_RECOGNITION (1 << 2)
|
|
#define BRSF_BIT_IN_BAND_RING_TONE (1 << 3)
|
|
#define BRSF_BIT_ATTACH_NUM_TO_VOICE_TAG (1 << 4)
|
|
#define BRSF_BIT_ABILITY_TO_REJECT_CALL (1 << 5)
|
|
#define BRSF_BIT_ENHANCED_CALL_STATUS (1 << 6)
|
|
#define BRSF_BIT_ENHANCED_CALL_CONTROL (1 << 7)
|
|
#define BRSF_BIT_EXTENDED_ERR_RESULT_CODES (1 << 8)
|
|
#define BRSF_BIT_CODEC_NEGOTIATION (1 << 9)
|
|
|
|
/**
|
|
* These constants are used in result code such as +CLIP and +CCWA. The value
|
|
* of these constants is the same as TOA_INTERNATIONAL/TOA_UNKNOWN defined in
|
|
* ril_consts.js
|
|
*/
|
|
#define TOA_UNKNOWN 0x81
|
|
#define TOA_INTERNATIONAL 0x91
|
|
|
|
#define CR_LF "\xd\xa";
|
|
|
|
#define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
|
|
#define AUDIO_VOLUME_BT_SCO_ID "audio.volume.bt_sco"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::ipc;
|
|
USING_BLUETOOTH_NAMESPACE
|
|
|
|
namespace {
|
|
StaticRefPtr<BluetoothHfpManager> sBluetoothHfpManager;
|
|
bool sInShutdown = false;
|
|
static const char kHfpCrlf[] = "\xd\xa";
|
|
|
|
// Sending ringtone related
|
|
static bool sStopSendingRingFlag = true;
|
|
static int sRingInterval = 3000; //unit: ms
|
|
|
|
// 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
|
|
} // anonymous namespace
|
|
|
|
/* CallState for sCINDItems[CINDType::CALL].value
|
|
* - NO_CALL: there are no calls in progress
|
|
* - IN_PROGRESS: at least one call is in progress
|
|
*/
|
|
enum CallState {
|
|
NO_CALL,
|
|
IN_PROGRESS
|
|
};
|
|
|
|
/* CallSetupState for sCINDItems[CINDType::CALLSETUP].value
|
|
* - NO_CALLSETUP: not currently in call set up
|
|
* - INCOMING: an incoming call process ongoing
|
|
* - OUTGOING: an outgoing call set up is ongoing
|
|
* - OUTGOING_ALERTING: remote party being alerted in an outgoing call
|
|
*/
|
|
enum CallSetupState {
|
|
NO_CALLSETUP,
|
|
INCOMING,
|
|
OUTGOING,
|
|
OUTGOING_ALERTING
|
|
};
|
|
|
|
/* CallHeldState for sCINDItems[CINDType::CALLHELD].value
|
|
* - NO_CALLHELD: no calls held
|
|
* - ONHOLD_ACTIVE: both an active and a held call
|
|
* - ONHOLD_NOACTIVE: call on hold, no active call
|
|
*/
|
|
enum CallHeldState {
|
|
NO_CALLHELD,
|
|
ONHOLD_ACTIVE,
|
|
ONHOLD_NOACTIVE
|
|
};
|
|
|
|
typedef struct {
|
|
const char* name;
|
|
const char* range;
|
|
int value;
|
|
bool activated;
|
|
} CINDItem;
|
|
|
|
enum CINDType {
|
|
BATTCHG = 1,
|
|
CALL,
|
|
CALLHELD,
|
|
CALLSETUP,
|
|
SERVICE,
|
|
SIGNAL,
|
|
ROAM
|
|
};
|
|
|
|
static CINDItem sCINDItems[] = {
|
|
{},
|
|
{"battchg", "0-5", 5, true},
|
|
{"call", "0,1", CallState::NO_CALL, true},
|
|
{"callheld", "0-2", CallHeldState::NO_CALLHELD, true},
|
|
{"callsetup", "0-3", CallSetupState::NO_CALLSETUP, true},
|
|
{"service", "0,1", 0, true},
|
|
{"signal", "0-5", 0, true},
|
|
{"roam", "0,1", 0, true}
|
|
};
|
|
|
|
class BluetoothHfpManager::GetVolumeTask : public nsISettingsServiceCallback
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
NS_IMETHOD
|
|
Handle(const nsAString& aName, const 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;
|
|
}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS1(BluetoothHfpManager::GetVolumeTask,
|
|
nsISettingsServiceCallback);
|
|
|
|
NS_IMETHODIMP
|
|
BluetoothHfpManager::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const PRUnichar* aData)
|
|
{
|
|
if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
|
|
HandleVolumeChanged(nsDependentString(aData));
|
|
} 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
|
|
int level = ceil(aBatteryInfo.level() * 5.0);
|
|
if (level != sCINDItems[CINDType::BATTCHG].value) {
|
|
sCINDItems[CINDType::BATTCHG].value = level;
|
|
SendCommand("+CIEV:", CINDType::BATTCHG);
|
|
}
|
|
}
|
|
|
|
class BluetoothHfpManager::RespondToBLDNTask : public Task
|
|
{
|
|
private:
|
|
void Run() MOZ_OVERRIDE
|
|
{
|
|
MOZ_ASSERT(sBluetoothHfpManager);
|
|
|
|
if (!sBluetoothHfpManager->mDialingRequestProcessed) {
|
|
sBluetoothHfpManager->mDialingRequestProcessed = true;
|
|
sBluetoothHfpManager->SendLine("ERROR");
|
|
}
|
|
}
|
|
};
|
|
|
|
class BluetoothHfpManager::SendRingIndicatorTask : public Task
|
|
{
|
|
public:
|
|
SendRingIndicatorTask(const nsAString& aNumber, int aType)
|
|
: mNumber(aNumber)
|
|
, mType(aType)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
void Run() MOZ_OVERRIDE
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Stop sending RING indicator
|
|
if (sStopSendingRingFlag) {
|
|
return;
|
|
}
|
|
|
|
if (!sBluetoothHfpManager) {
|
|
BT_WARNING("BluetoothHfpManager no longer exists, cannot send ring!");
|
|
return;
|
|
}
|
|
|
|
nsAutoCString ringMsg("RING");
|
|
sBluetoothHfpManager->SendLine(ringMsg.get());
|
|
|
|
if (!mNumber.IsEmpty()) {
|
|
nsAutoCString clipMsg("+CLIP: \"");
|
|
clipMsg.Append(NS_ConvertUTF16toUTF8(mNumber).get());
|
|
clipMsg.AppendLiteral("\",");
|
|
clipMsg.AppendInt(mType);
|
|
sBluetoothHfpManager->SendLine(clipMsg.get());
|
|
}
|
|
|
|
MessageLoop::current()->
|
|
PostDelayedTask(FROM_HERE,
|
|
new SendRingIndicatorTask(mNumber, mType),
|
|
sRingInterval);
|
|
}
|
|
|
|
private:
|
|
nsString mNumber;
|
|
int mType;
|
|
};
|
|
|
|
class BluetoothHfpManager::CloseScoTask : public Task
|
|
{
|
|
private:
|
|
void Run() MOZ_OVERRIDE
|
|
{
|
|
MOZ_ASSERT(sBluetoothHfpManager);
|
|
|
|
sBluetoothHfpManager->DisconnectSco();
|
|
}
|
|
};
|
|
|
|
static bool
|
|
IsValidDtmf(const char aChar) {
|
|
// Valid DTMF: [*#0-9ABCD]
|
|
if (aChar == '*' || aChar == '#') {
|
|
return true;
|
|
} else if (aChar >= '0' && aChar <= '9') {
|
|
return true;
|
|
} else if (aChar >= 'A' && aChar <= 'D') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
IsMandatoryIndicator(const CINDType aType) {
|
|
return (aType == CINDType::CALL) ||
|
|
(aType == CINDType::CALLHELD) ||
|
|
(aType == CINDType::CALLSETUP);
|
|
}
|
|
|
|
/**
|
|
* Call
|
|
*/
|
|
Call::Call()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
void
|
|
Call::Reset()
|
|
{
|
|
mState = nsITelephonyProvider::CALL_STATE_DISCONNECTED;
|
|
mDirection = false;
|
|
mNumber.Truncate();
|
|
mType = TOA_UNKNOWN;
|
|
}
|
|
|
|
bool
|
|
Call::IsActive()
|
|
{
|
|
return (mState == nsITelephonyProvider::CALL_STATE_CONNECTED);
|
|
}
|
|
|
|
/**
|
|
* BluetoothHfpManager
|
|
*/
|
|
BluetoothHfpManager::BluetoothHfpManager() : mController(nullptr)
|
|
{
|
|
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::Reset()
|
|
{
|
|
sStopSendingRingFlag = true;
|
|
sCINDItems[CINDType::CALL].value = CallState::NO_CALL;
|
|
sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP;
|
|
sCINDItems[CINDType::CALLHELD].value = CallHeldState::NO_CALLHELD;
|
|
for (uint8_t i = 1; i < ArrayLength(sCINDItems); i++) {
|
|
sCINDItems[i].activated = true;
|
|
}
|
|
|
|
mCCWA = false;
|
|
mCLIP = false;
|
|
mCMEE = false;
|
|
mCMER = false;
|
|
mReceiveVgsFlag = false;
|
|
mDialingRequestProcessed = true;
|
|
|
|
// We disable BSIR by default as it requires OEM implement BT SCO + SPEAKER
|
|
// output audio path in audio driver. OEM can enable BSIR by setting
|
|
// mBSIR=true here.
|
|
//
|
|
// Please see Bug 878728 for more information.
|
|
mBSIR = false;
|
|
|
|
ResetCallArray();
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::Init()
|
|
{
|
|
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);
|
|
|
|
mListener = new BluetoothRilListener();
|
|
if (!mListener->StartListening()) {
|
|
BT_WARNING("Failed to start listening RIL");
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsISettingsService> settings =
|
|
do_GetService("@mozilla.org/settingsService;1");
|
|
NS_ENSURE_TRUE(settings, false);
|
|
|
|
nsCOMPtr<nsISettingsServiceLock> settingsLock;
|
|
nsresult rv = settings->CreateLock(getter_AddRefs(settingsLock));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
nsRefPtr<GetVolumeTask> callback = new GetVolumeTask();
|
|
rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO_ID, callback);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
Listen();
|
|
|
|
mScoSocket = new BluetoothSocket(this,
|
|
BluetoothSocketType::SCO,
|
|
true,
|
|
false);
|
|
mScoSocketStatus = mScoSocket->GetConnectionStatus();
|
|
ListenSco();
|
|
return true;
|
|
}
|
|
|
|
BluetoothHfpManager::~BluetoothHfpManager()
|
|
{
|
|
if (!mListener->StopListening()) {
|
|
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);
|
|
}
|
|
|
|
//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;
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::NotifyConnectionStatusChanged(const nsAString& aType)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Notify Gecko observers
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
NS_ENSURE_TRUE_VOID(obs);
|
|
|
|
if (NS_FAILED(obs->NotifyObservers(this, NS_ConvertUTF16toUTF8(aType).get(),
|
|
mDeviceAddress.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, mDeviceAddress, status);
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::NotifyDialer(const nsAString& aCommand)
|
|
{
|
|
nsString type, name;
|
|
BluetoothValue v;
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
type.AssignLiteral("bluetooth-dialer-command");
|
|
|
|
name.AssignLiteral("command");
|
|
v = nsString(aCommand);
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
if (!BroadcastSystemMessage(type, parameters)) {
|
|
BT_WARNING("Failed to broadcast system message to dialer");
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
|
|
{
|
|
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}
|
|
|
|
JSContext* cx = nsContentUtils::GetSafeJSContext();
|
|
NS_ENSURE_TRUE_VOID(cx);
|
|
|
|
JS::Rooted<JS::Value> val(cx);
|
|
NS_ENSURE_TRUE_VOID(JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val));
|
|
NS_ENSURE_TRUE_VOID(val.isObject());
|
|
|
|
JS::Rooted<JSObject*> obj(cx, &val.toObject());
|
|
JS::Rooted<JS::Value> key(cx);
|
|
if (!JS_GetProperty(cx, obj, "key", &key) || !key.isString()) {
|
|
return;
|
|
}
|
|
|
|
bool match;
|
|
if (!JS_StringEqualsAscii(cx, key.toString(), AUDIO_VOLUME_BT_SCO_ID, &match) ||
|
|
!match) {
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(cx);
|
|
if (!JS_GetProperty(cx, obj, "value", &value)||
|
|
!value.isNumber()) {
|
|
return;
|
|
}
|
|
|
|
mCurrentVgs = value.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()) {
|
|
SendCommand("+VGS: ", mCurrentVgs);
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::HandleVoiceConnectionChanged()
|
|
{
|
|
nsCOMPtr<nsIMobileConnectionProvider> connection =
|
|
do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
|
|
NS_ENSURE_TRUE_VOID(connection);
|
|
|
|
nsCOMPtr<nsIDOMMozMobileConnectionInfo> voiceInfo;
|
|
connection->GetVoiceConnectionInfo(getter_AddRefs(voiceInfo));
|
|
NS_ENSURE_TRUE_VOID(voiceInfo);
|
|
|
|
nsString type;
|
|
voiceInfo->GetType(type);
|
|
mPhoneType = GetPhoneType(type);
|
|
|
|
bool roaming;
|
|
voiceInfo->GetRoaming(&roaming);
|
|
UpdateCIND(CINDType::ROAM, roaming);
|
|
|
|
bool service = false;
|
|
nsString regState;
|
|
voiceInfo->GetState(regState);
|
|
if (regState.EqualsLiteral("registered")) {
|
|
service = true;
|
|
}
|
|
UpdateCIND(CINDType::SERVICE, service);
|
|
|
|
uint8_t signal;
|
|
JS::Value value;
|
|
voiceInfo->GetRelSignalStrength(&value);
|
|
if (!value.isNumber()) {
|
|
BT_WARNING("Failed to get relSignalStrength in BluetoothHfpManager");
|
|
return;
|
|
}
|
|
signal = ceil(value.toNumber() / 20.0);
|
|
UpdateCIND(CINDType::SIGNAL, signal);
|
|
|
|
/**
|
|
* Possible return values for mode are:
|
|
* - null (unknown): set mNetworkSelectionMode to 0 (auto)
|
|
* - automatic: set mNetworkSelectionMode to 0 (auto)
|
|
* - manual: set mNetworkSelectionMode to 1 (manual)
|
|
*/
|
|
nsString mode;
|
|
connection->GetNetworkSelectionMode(mode);
|
|
if (mode.EqualsLiteral("manual")) {
|
|
mNetworkSelectionMode = 1;
|
|
} else {
|
|
mNetworkSelectionMode = 0;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMMozMobileNetworkInfo> network;
|
|
voiceInfo->GetNetwork(getter_AddRefs(network));
|
|
NS_ENSURE_TRUE_VOID(network);
|
|
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()
|
|
{
|
|
nsCOMPtr<nsIIccProvider> icc =
|
|
do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
|
|
NS_ENSURE_TRUE_VOID(icc);
|
|
|
|
nsCOMPtr<nsIDOMMozIccInfo> iccInfo;
|
|
icc->GetIccInfo(getter_AddRefs(iccInfo));
|
|
NS_ENSURE_TRUE_VOID(iccInfo);
|
|
|
|
nsCOMPtr<nsIDOMMozGsmIccInfo> 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;
|
|
}
|
|
|
|
// Virtual function of class SocketConsumer
|
|
void
|
|
BluetoothHfpManager::ReceiveSocketData(BluetoothSocket* aSocket,
|
|
nsAutoPtr<UnixSocketRawData>& aMessage)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aSocket);
|
|
|
|
nsAutoCString msg((const char*)aMessage->mData.get(), aMessage->mSize);
|
|
msg.StripWhitespace();
|
|
|
|
nsTArray<nsCString> atCommandValues;
|
|
|
|
// For more information, please refer to 4.34.1 "Bluetooth Defined AT
|
|
// Capabilities" in Bluetooth hands-free profile 1.6
|
|
if (msg.Find("AT+BRSF=") != -1) {
|
|
uint32_t brsf = BRSF_BIT_ABILITY_TO_REJECT_CALL |
|
|
BRSF_BIT_ENHANCED_CALL_STATUS;
|
|
|
|
// No support for three way calling in CDMA since
|
|
// CDMA disallows to hang existing call for CHLD=1
|
|
if (mPhoneType != PhoneType::CDMA) {
|
|
brsf |= BRSF_BIT_THREE_WAY_CALLING;
|
|
}
|
|
|
|
if (mBSIR) {
|
|
brsf |= BRSF_BIT_IN_BAND_RING_TONE;
|
|
}
|
|
|
|
SendCommand("+BRSF: ", brsf);
|
|
} else if (msg.Find("AT+CIND=?") != -1) {
|
|
// Asking for CIND range
|
|
SendCommand("+CIND: ", 0);
|
|
} else if (msg.Find("AT+CIND?") != -1) {
|
|
// Asking for CIND value
|
|
SendCommand("+CIND: ", 1);
|
|
} else if (msg.Find("AT+CMER=") != -1) {
|
|
/**
|
|
* SLC establishment is done when AT+CMER has been received.
|
|
* Do nothing but respond with "OK".
|
|
*/
|
|
ParseAtCommand(msg, 8, atCommandValues);
|
|
|
|
if (atCommandValues.Length() < 4) {
|
|
BT_WARNING("Could't get the value of command [AT+CMER=]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
if (!atCommandValues[0].EqualsLiteral("3") ||
|
|
!atCommandValues[1].EqualsLiteral("0") ||
|
|
!atCommandValues[2].EqualsLiteral("0")) {
|
|
BT_WARNING("Wrong value of CMER");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
mCMER = atCommandValues[3].EqualsLiteral("1");
|
|
} else if (msg.Find("AT+CMEE=") != -1) {
|
|
ParseAtCommand(msg, 8, atCommandValues);
|
|
|
|
if (atCommandValues.IsEmpty()) {
|
|
BT_WARNING("Could't get the value of command [AT+CMEE=]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
// AT+CMEE = 0: +CME ERROR shall not be used
|
|
// AT+CMEE = 1: use numeric <err>
|
|
// AT+CMEE = 2: use verbose <err>
|
|
mCMEE = !atCommandValues[0].EqualsLiteral("0");
|
|
} else if (msg.Find("AT+COPS=") != -1) {
|
|
ParseAtCommand(msg, 8, atCommandValues);
|
|
|
|
if (atCommandValues.Length() != 2) {
|
|
BT_WARNING("Could't get the value of command [AT+COPS=]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
// Handsfree only support AT+COPS=3,0
|
|
if (!atCommandValues[0].EqualsLiteral("3") ||
|
|
!atCommandValues[1].EqualsLiteral("0")) {
|
|
if (mCMEE) {
|
|
SendCommand("+CME ERROR: ", BluetoothCmeError::OPERATION_NOT_SUPPORTED);
|
|
} else {
|
|
SendLine("ERROR");
|
|
}
|
|
return;
|
|
}
|
|
} else if (msg.Find("AT+COPS?") != -1) {
|
|
nsAutoCString message("+COPS: ");
|
|
message.AppendInt(mNetworkSelectionMode);
|
|
message.AppendLiteral(",0,\"");
|
|
message.Append(NS_ConvertUTF16toUTF8(mOperatorName));
|
|
message.AppendLiteral("\"");
|
|
SendLine(message.get());
|
|
} else if (msg.Find("AT+VTS=") != -1) {
|
|
ParseAtCommand(msg, 7, atCommandValues);
|
|
|
|
if (atCommandValues.Length() != 1) {
|
|
BT_WARNING("Couldn't get the value of command [AT+VTS=]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
if (IsValidDtmf(atCommandValues[0].get()[0])) {
|
|
nsAutoCString message("VTS=");
|
|
message += atCommandValues[0].get()[0];
|
|
NotifyDialer(NS_ConvertUTF8toUTF16(message));
|
|
}
|
|
} else if (msg.Find("AT+VGM=") != -1) {
|
|
ParseAtCommand(msg, 7, atCommandValues);
|
|
|
|
if (atCommandValues.IsEmpty()) {
|
|
BT_WARNING("Couldn't get the value of command [AT+VGM]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
nsresult rv;
|
|
int vgm = atCommandValues[0].ToInteger(&rv);
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Failed to extract microphone volume from bluetooth headset!");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
NS_ASSERTION(vgm >= 0 && vgm <= 15, "Received invalid VGM value");
|
|
mCurrentVgm = vgm;
|
|
} else if (msg.Find("AT+CHLD=?") != -1) {
|
|
SendLine("+CHLD: (0,1,2)");
|
|
} else if (msg.Find("AT+CHLD=") != -1) {
|
|
ParseAtCommand(msg, 8, atCommandValues);
|
|
|
|
if (atCommandValues.IsEmpty()) {
|
|
BT_WARNING("Could't get the value of command [AT+CHLD=]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
/**
|
|
* The following three cases are supported:
|
|
* AT+CHLD=0 - Releases all held calls or sets User Determined User Busy
|
|
* (UDUB) for a waiting call
|
|
* AT+CHLD=1 - Releases active calls and accepts the other (held or
|
|
* waiting) call
|
|
* AT+CHLD=2 - Places active calls on hold and accepts the other (held
|
|
* or waiting) call
|
|
*
|
|
* The following cases are NOT supported yet:
|
|
* AT+CHLD=1<idx>, AT+CHLD=2<idx>, AT+CHLD=3, AT+CHLD=4
|
|
* Please see 4.33.2 in Bluetooth hands-free profile 1.6 for more
|
|
* information.
|
|
*/
|
|
char chld = atCommandValues[0][0];
|
|
bool valid = true;
|
|
if (atCommandValues[0].Length() > 1) {
|
|
BT_WARNING("No index should be included in command [AT+CHLD]");
|
|
valid = false;
|
|
} else if (chld == '3' || chld == '4') {
|
|
BT_WARNING("The value of command [AT+CHLD] is not supported");
|
|
valid = false;
|
|
} else if (chld == '0') {
|
|
// We need to rename these dialer commands for better readability
|
|
// and expandability.
|
|
// See bug 884190 for more information.
|
|
NotifyDialer(NS_LITERAL_STRING("CHLD=0"));
|
|
} else if (chld == '1') {
|
|
NotifyDialer(NS_LITERAL_STRING("CHLD=1"));
|
|
} else if (chld == '2') {
|
|
NotifyDialer(NS_LITERAL_STRING("CHLD=2"));
|
|
} else {
|
|
BT_WARNING("Wrong value of command [AT+CHLD]");
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid) {
|
|
SendLine("ERROR");
|
|
return;
|
|
}
|
|
} else if (msg.Find("AT+VGS=") != -1) {
|
|
// Adjust volume by headset
|
|
mReceiveVgsFlag = true;
|
|
ParseAtCommand(msg, 7, atCommandValues);
|
|
|
|
if (atCommandValues.IsEmpty()) {
|
|
BT_WARNING("Could't get the value of command [AT+VGS=]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
nsresult rv;
|
|
int newVgs = atCommandValues[0].ToInteger(&rv);
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Failed to extract volume value from bluetooth headset!");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
if (newVgs == mCurrentVgs) {
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
NS_ASSERTION(newVgs >= 0 && newVgs <= 15, "Received invalid VGS value");
|
|
|
|
nsString data;
|
|
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
|
data.AppendInt(newVgs);
|
|
os->NotifyObservers(nullptr, "bluetooth-volume-change", data.get());
|
|
} else if ((msg.Find("AT+BLDN") != -1) || (msg.Find("ATD>") != -1)) {
|
|
// Dialer app of FFOS v1 does not have plan to support Memory Dailing.
|
|
// However, in order to pass Bluetooth HFP certification, we still have to
|
|
// make a call when we receive AT command 'ATD>n'.
|
|
mDialingRequestProcessed = false;
|
|
|
|
if (msg.Find("AT+BLDN") != -1) {
|
|
NotifyDialer(NS_LITERAL_STRING("BLDN"));
|
|
} else {
|
|
NotifyDialer(NS_ConvertUTF8toUTF16(msg));
|
|
}
|
|
|
|
MessageLoop::current()->
|
|
PostDelayedTask(FROM_HERE, new RespondToBLDNTask(),
|
|
sWaitingForDialingInterval);
|
|
|
|
// Don't send response 'OK' here because we'll respond later in either
|
|
// RespondToBLDNTask or HandleCallStateChanged()
|
|
return;
|
|
} else if (msg.Find("ATA") != -1) {
|
|
NotifyDialer(NS_LITERAL_STRING("ATA"));
|
|
} else if (msg.Find("AT+CHUP") != -1) {
|
|
NotifyDialer(NS_LITERAL_STRING("CHUP"));
|
|
} else if (msg.Find("AT+CLCC") != -1) {
|
|
SendCommand("+CLCC: ");
|
|
} else if (msg.Find("ATD") != -1) {
|
|
nsAutoCString message(msg), newMsg;
|
|
int end = message.FindChar(';');
|
|
if (end < 0) {
|
|
BT_WARNING("Could't get the value of command [ATD]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
newMsg += nsDependentCSubstring(message, 0, end);
|
|
NotifyDialer(NS_ConvertUTF8toUTF16(newMsg));
|
|
} else if (msg.Find("AT+CLIP=") != -1) {
|
|
ParseAtCommand(msg, 8, atCommandValues);
|
|
|
|
if (atCommandValues.IsEmpty()) {
|
|
BT_WARNING("Could't get the value of command [AT+CLIP=]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
mCLIP = atCommandValues[0].EqualsLiteral("1");
|
|
} else if (msg.Find("AT+CCWA=") != -1) {
|
|
ParseAtCommand(msg, 8, atCommandValues);
|
|
|
|
if (atCommandValues.IsEmpty()) {
|
|
BT_WARNING("Could't get the value of command [AT+CCWA=]");
|
|
goto respond_with_ok;
|
|
}
|
|
|
|
mCCWA = atCommandValues[0].EqualsLiteral("1");
|
|
} else if (msg.Find("AT+CKPD") != -1) {
|
|
if (!sStopSendingRingFlag) {
|
|
// 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 (!IsScoConnected()) {
|
|
// Bluetooth HSP spec 4.3
|
|
// If there's no SCO, set up a SCO link.
|
|
ConnectSco();
|
|
} else if (!mFirstCKPD) {
|
|
// 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.
|
|
if (mCurrentCallArray.Length() > 1) {
|
|
NotifyDialer(NS_LITERAL_STRING("CHUP"));
|
|
} else {
|
|
DisconnectSco();
|
|
}
|
|
} else {
|
|
// Three conditions have to be matched to come in here:
|
|
// (1) Not sending RING indicator
|
|
// (2) A SCO link exists
|
|
// (3) This is the very first AT+CKPD=200 of this session
|
|
// It is the case of Figure 4.3, Bluetooth HSP spec. Do nothing.
|
|
BT_WARNING("AT+CKPD=200: Do nothing");
|
|
}
|
|
}
|
|
|
|
mFirstCKPD = false;
|
|
} else if (msg.Find("AT+CNUM") != -1) {
|
|
if (!mMsisdn.IsEmpty()) {
|
|
nsAutoCString message("+CNUM: ,\"");
|
|
message.Append(NS_ConvertUTF16toUTF8(mMsisdn).get());
|
|
message.AppendLiteral("\",");
|
|
message.AppendInt(TOA_UNKNOWN);
|
|
message.AppendLiteral(",,4");
|
|
SendLine(message.get());
|
|
}
|
|
} else if (msg.Find("AT+BIA=") != -1) {
|
|
ParseAtCommand(msg, 7, atCommandValues);
|
|
|
|
for (uint8_t i = 0; i < atCommandValues.Length(); i++) {
|
|
CINDType indicatorType = (CINDType) (i + 1);
|
|
if (indicatorType >= ArrayLength(sCINDItems)) {
|
|
// Ignore excess parameters at the end
|
|
break;
|
|
}
|
|
|
|
if (!IsMandatoryIndicator(indicatorType)) {
|
|
/**
|
|
* Accept only following indicator states:
|
|
* - "1": activate
|
|
* - "0": deactivate
|
|
* - "" : maintain current state
|
|
* Otherwise we regard the command incorrectly formatted.
|
|
*/
|
|
if (atCommandValues[i].EqualsLiteral("1")) {
|
|
sCINDItems[indicatorType].activated = 1;
|
|
} else if (atCommandValues[i].EqualsLiteral("0")) {
|
|
sCINDItems[indicatorType].activated = 0;
|
|
} else if (!atCommandValues[i].EqualsLiteral("")) {
|
|
SendLine("ERROR");
|
|
return;
|
|
}
|
|
} else {
|
|
// Ignore requests to activate/deactivate mandatory indicators
|
|
}
|
|
}
|
|
} else {
|
|
nsCString warningMsg;
|
|
warningMsg.Append(NS_LITERAL_CSTRING("Unsupported AT command: "));
|
|
warningMsg.Append(msg);
|
|
warningMsg.Append(NS_LITERAL_CSTRING(", reply with ERROR"));
|
|
BT_WARNING(warningMsg.get());
|
|
|
|
SendLine("ERROR");
|
|
return;
|
|
}
|
|
|
|
respond_with_ok:
|
|
// We always respond to remote device with "OK" in general cases.
|
|
SendLine("OK");
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::Connect(const nsAString& aDeviceAddress,
|
|
BluetoothProfileController* aController)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aController && !mController);
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
if (!bs || sInShutdown) {
|
|
aController->OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
|
|
return;
|
|
}
|
|
|
|
if (mSocket) {
|
|
if (mDeviceAddress == aDeviceAddress) {
|
|
aController->OnConnect(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED));
|
|
} else {
|
|
aController->OnConnect(NS_LITERAL_STRING(ERR_REACHED_CONNECTION_LIMIT));
|
|
}
|
|
return;
|
|
}
|
|
|
|
nsString uuid;
|
|
BluetoothUuidHelper::GetString(BluetoothServiceClass::HANDSFREE, uuid);
|
|
|
|
if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) {
|
|
aController->OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
|
|
return;
|
|
}
|
|
|
|
// Stop listening because currently we only support one connection at a time.
|
|
if (mHandsfreeSocket) {
|
|
mHandsfreeSocket->Disconnect();
|
|
mHandsfreeSocket = nullptr;
|
|
}
|
|
|
|
if (mHeadsetSocket) {
|
|
mHeadsetSocket->Disconnect();
|
|
mHeadsetSocket = nullptr;
|
|
}
|
|
|
|
mController = aController;
|
|
mSocket =
|
|
new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::Listen()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (sInShutdown) {
|
|
BT_WARNING("Listen called while in shutdown!");
|
|
return false;
|
|
}
|
|
|
|
if (mSocket) {
|
|
BT_WARNING("mSocket exists. Failed to listen.");
|
|
return false;
|
|
}
|
|
|
|
if (!mHandsfreeSocket) {
|
|
mHandsfreeSocket =
|
|
new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
|
|
|
|
if (!mHandsfreeSocket->Listen(
|
|
BluetoothReservedChannels::CHANNEL_HANDSFREE_AG)) {
|
|
BT_WARNING("[HFP] Can't listen on RFCOMM socket!");
|
|
mHandsfreeSocket = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!mHeadsetSocket) {
|
|
mHeadsetSocket =
|
|
new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
|
|
|
|
if (!mHeadsetSocket->Listen(
|
|
BluetoothReservedChannels::CHANNEL_HEADSET_AG)) {
|
|
BT_WARNING("[HSP] Can't listen on RFCOMM socket!");
|
|
mHandsfreeSocket->Disconnect();
|
|
mHandsfreeSocket = nullptr;
|
|
mHeadsetSocket = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::Disconnect(BluetoothProfileController* aController)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mSocket) {
|
|
if (aController) {
|
|
aController->OnDisconnect(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED));
|
|
}
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!mController);
|
|
|
|
mController = aController;
|
|
mSocket->Disconnect();
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::SendCCWA(const nsAString& aNumber, int aType)
|
|
{
|
|
if (mCCWA) {
|
|
nsAutoCString ccwaMsg("+CCWA: \"");
|
|
ccwaMsg.Append(NS_ConvertUTF16toUTF8(aNumber));
|
|
ccwaMsg.AppendLiteral("\",");
|
|
ccwaMsg.AppendInt(aType);
|
|
SendLine(ccwaMsg.get());
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::SendCLCC(const Call& aCall, int aIndex)
|
|
{
|
|
if (aCall.mState == nsITelephonyProvider::CALL_STATE_DISCONNECTED) {
|
|
return true;
|
|
}
|
|
|
|
nsAutoCString message("+CLCC: ");
|
|
message.AppendInt(aIndex);
|
|
message.AppendLiteral(",");
|
|
message.AppendInt(aCall.mDirection);
|
|
message.AppendLiteral(",");
|
|
|
|
int status = 0;
|
|
switch (aCall.mState) {
|
|
case nsITelephonyProvider::CALL_STATE_CONNECTED:
|
|
if (mPhoneType == PhoneType::CDMA && aIndex == 1) {
|
|
status = (mCdmaSecondCall.IsActive()) ? 1 : 0;
|
|
}
|
|
message.AppendInt(status);
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_HELD:
|
|
message.AppendInt(1);
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_DIALING:
|
|
message.AppendInt(2);
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_ALERTING:
|
|
message.AppendInt(3);
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_INCOMING:
|
|
if (!FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) {
|
|
message.AppendInt(4);
|
|
} else {
|
|
message.AppendInt(5);
|
|
}
|
|
break;
|
|
default:
|
|
BT_WARNING("Not handling call status for CLCC");
|
|
break;
|
|
}
|
|
|
|
message.AppendLiteral(",0,0,\"");
|
|
message.Append(NS_ConvertUTF16toUTF8(aCall.mNumber));
|
|
message.AppendLiteral("\",");
|
|
message.AppendInt(aCall.mType);
|
|
|
|
return SendLine(message.get());
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::SendLine(const char* aMessage)
|
|
{
|
|
MOZ_ASSERT(mSocket);
|
|
|
|
nsAutoCString msg;
|
|
|
|
msg.AppendLiteral(kHfpCrlf);
|
|
msg.Append(aMessage);
|
|
msg.AppendLiteral(kHfpCrlf);
|
|
|
|
return mSocket->SendSocketData(msg);
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::SendCommand(const char* aCommand, uint32_t aValue)
|
|
{
|
|
if (!IsConnected()) {
|
|
BT_WARNING("Trying to SendCommand() without a SLC");
|
|
return false;
|
|
}
|
|
|
|
nsAutoCString message;
|
|
message += aCommand;
|
|
|
|
if (!strcmp(aCommand, "+CIEV: ")) {
|
|
if (!mCMER || !sCINDItems[aValue].activated) {
|
|
// Indicator status update is disabled
|
|
return true;
|
|
}
|
|
|
|
if ((aValue < 1) || (aValue > ArrayLength(sCINDItems) - 1)) {
|
|
BT_WARNING("unexpected CINDType for CIEV command");
|
|
return false;
|
|
}
|
|
|
|
message.AppendInt(aValue);
|
|
message.AppendLiteral(",");
|
|
message.AppendInt(sCINDItems[aValue].value);
|
|
} else if (!strcmp(aCommand, "+CIND: ")) {
|
|
if (!aValue) {
|
|
// Query for range
|
|
for (uint8_t i = 1; i < ArrayLength(sCINDItems); i++) {
|
|
message.AppendLiteral("(\"");
|
|
message.Append(sCINDItems[i].name);
|
|
message.AppendLiteral("\",(");
|
|
message.Append(sCINDItems[i].range);
|
|
message.AppendLiteral(")");
|
|
if (i == (ArrayLength(sCINDItems) - 1)) {
|
|
message.AppendLiteral(")");
|
|
break;
|
|
}
|
|
message.AppendLiteral("),");
|
|
}
|
|
} else {
|
|
// Query for value
|
|
for (uint8_t i = 1; i < ArrayLength(sCINDItems); i++) {
|
|
message.AppendInt(sCINDItems[i].value);
|
|
if (i == (ArrayLength(sCINDItems) - 1)) {
|
|
break;
|
|
}
|
|
message.AppendLiteral(",");
|
|
}
|
|
}
|
|
} else if (!strcmp(aCommand, "+CLCC: ")) {
|
|
bool rv = true;
|
|
uint32_t callNumbers = mCurrentCallArray.Length();
|
|
uint32_t i;
|
|
for (i = 1; i < callNumbers; i++) {
|
|
rv &= SendCLCC(mCurrentCallArray[i], i);
|
|
}
|
|
|
|
if (!mCdmaSecondCall.mNumber.IsEmpty()) {
|
|
MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
|
|
MOZ_ASSERT(i == 2);
|
|
|
|
rv &= SendCLCC(mCdmaSecondCall, 2);
|
|
}
|
|
|
|
return rv;
|
|
} else {
|
|
message.AppendInt(aValue);
|
|
}
|
|
|
|
return SendLine(message.get());
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::UpdateCIND(uint8_t aType, uint8_t aValue, bool aSend)
|
|
{
|
|
if (sCINDItems[aType].value != aValue) {
|
|
sCINDItems[aType].value = aValue;
|
|
if (aSend) {
|
|
SendCommand("+CIEV: ", aType);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex,
|
|
uint16_t aCallState,
|
|
const nsAString& aError,
|
|
const nsAString& aNumber,
|
|
const bool aIsOutgoing,
|
|
bool aSend)
|
|
{
|
|
if (!IsConnected()) {
|
|
// Normal case. No need to print out warnings.
|
|
return;
|
|
}
|
|
|
|
while (aCallIndex >= mCurrentCallArray.Length()) {
|
|
Call call;
|
|
mCurrentCallArray.AppendElement(call);
|
|
}
|
|
|
|
uint16_t prevCallState = mCurrentCallArray[aCallIndex].mState;
|
|
mCurrentCallArray[aCallIndex].mState = aCallState;
|
|
mCurrentCallArray[aCallIndex].mDirection = !aIsOutgoing;
|
|
|
|
// Same logic as implementation in ril_worker.js
|
|
if (aNumber.Length() && aNumber[0] == '+') {
|
|
mCurrentCallArray[aCallIndex].mType = TOA_INTERNATIONAL;
|
|
}
|
|
mCurrentCallArray[aCallIndex].mNumber = aNumber;
|
|
|
|
nsRefPtr<nsRunnable> sendRingTask;
|
|
nsString address;
|
|
|
|
switch (aCallState) {
|
|
case nsITelephonyProvider::CALL_STATE_HELD:
|
|
if (!FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) {
|
|
sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_NOACTIVE;
|
|
} else {
|
|
sCINDItems[CINDType::CALLHELD].value = CallHeldState::ONHOLD_ACTIVE;
|
|
}
|
|
SendCommand("+CIEV: ", CINDType::CALLHELD);
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_INCOMING:
|
|
if (FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) {
|
|
SendCCWA(aNumber, mCurrentCallArray[aCallIndex].mType);
|
|
UpdateCIND(CINDType::CALLSETUP, CallSetupState::INCOMING, aSend);
|
|
} else {
|
|
// Start sending RING indicator to HF
|
|
sStopSendingRingFlag = false;
|
|
UpdateCIND(CINDType::CALLSETUP, CallSetupState::INCOMING, aSend);
|
|
|
|
if (mBSIR) {
|
|
// Setup audio connection for in-band ring tone
|
|
ConnectSco();
|
|
}
|
|
|
|
nsAutoString number(aNumber);
|
|
if (!mCLIP) {
|
|
number.AssignLiteral("");
|
|
}
|
|
|
|
MessageLoop::current()->PostDelayedTask(
|
|
FROM_HERE,
|
|
new SendRingIndicatorTask(number,
|
|
mCurrentCallArray[aCallIndex].mType),
|
|
sRingInterval);
|
|
}
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_DIALING:
|
|
if (!mDialingRequestProcessed) {
|
|
SendLine("OK");
|
|
mDialingRequestProcessed = true;
|
|
}
|
|
|
|
UpdateCIND(CINDType::CALLSETUP, CallSetupState::OUTGOING, aSend);
|
|
ConnectSco();
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_ALERTING:
|
|
UpdateCIND(CINDType::CALLSETUP, CallSetupState::OUTGOING_ALERTING, aSend);
|
|
|
|
// If there's an ongoing call when the headset is just connected, we have
|
|
// to open a sco socket here.
|
|
ConnectSco();
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_CONNECTED:
|
|
switch (prevCallState) {
|
|
case nsITelephonyProvider::CALL_STATE_INCOMING:
|
|
case nsITelephonyProvider::CALL_STATE_DISCONNECTED:
|
|
// Incoming call, no break
|
|
sStopSendingRingFlag = true;
|
|
ConnectSco();
|
|
case nsITelephonyProvider::CALL_STATE_DIALING:
|
|
case nsITelephonyProvider::CALL_STATE_ALERTING:
|
|
// Outgoing call
|
|
UpdateCIND(CINDType::CALL, CallState::IN_PROGRESS, aSend);
|
|
UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, aSend);
|
|
break;
|
|
default:
|
|
BT_WARNING("Not handling state changed");
|
|
}
|
|
|
|
// = Handle callheld separately =
|
|
// Besides checking if there is still held calls, another thing we
|
|
// need to consider is the state change when receiving AT+CHLD=2.
|
|
// Assume that there is one active call(c1) and one call on hold(c2).
|
|
// We got AT+CHLD=2, which swaps active/held position. The first
|
|
// action would be c2 -> ACTIVE, then c1 -> HELD. When we get the
|
|
// CallStateChanged event of c2 becoming ACTIVE, we enter here.
|
|
// However we can't send callheld=0 at this time because we should
|
|
// see c2 -> ACTIVE + c1 -> HELD as one operation. That's the reason
|
|
// why I added the GetNumberOfCalls() condition check.
|
|
if (GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_CONNECTED) == 1) {
|
|
if (FindFirstCall(nsITelephonyProvider::CALL_STATE_HELD)) {
|
|
UpdateCIND(CINDType::CALLHELD, CallHeldState::ONHOLD_ACTIVE, aSend);
|
|
} else if (prevCallState == nsITelephonyProvider::CALL_STATE_HELD) {
|
|
UpdateCIND(CINDType::CALLHELD, CallHeldState::NO_CALLHELD, aSend);
|
|
}
|
|
}
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_DISCONNECTED:
|
|
switch (prevCallState) {
|
|
case nsITelephonyProvider::CALL_STATE_INCOMING:
|
|
// Incoming call, no break
|
|
sStopSendingRingFlag = true;
|
|
case nsITelephonyProvider::CALL_STATE_DIALING:
|
|
case nsITelephonyProvider::CALL_STATE_ALERTING:
|
|
// Outgoing call
|
|
UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, aSend);
|
|
break;
|
|
case nsITelephonyProvider::CALL_STATE_CONNECTED:
|
|
// No call is ongoing
|
|
if (sCINDItems[CINDType::CALLHELD].value ==
|
|
CallHeldState::NO_CALLHELD) {
|
|
UpdateCIND(CINDType::CALL, CallState::NO_CALL, aSend);
|
|
}
|
|
break;
|
|
default:
|
|
BT_WARNING("Not handling state changed");
|
|
}
|
|
|
|
// Handle held calls separately
|
|
if (!FindFirstCall(nsITelephonyProvider::CALL_STATE_HELD)) {
|
|
UpdateCIND(CINDType::CALLHELD, CallHeldState::NO_CALLHELD, aSend);
|
|
} else if (!FindFirstCall(nsITelephonyProvider::CALL_STATE_CONNECTED)) {
|
|
UpdateCIND(CINDType::CALLHELD, CallHeldState::ONHOLD_NOACTIVE, aSend);
|
|
} else {
|
|
UpdateCIND(CINDType::CALLHELD, CallHeldState::ONHOLD_ACTIVE, aSend);
|
|
}
|
|
|
|
// -1 is necessary because call 0 is an invalid (padding) call object.
|
|
if (mCurrentCallArray.Length() - 1 ==
|
|
GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_DISCONNECTED)) {
|
|
// In order to let user hear busy tone via connected Bluetooth headset,
|
|
// we postpone the timing of dropping SCO.
|
|
if (!(aError.Equals(NS_LITERAL_STRING("BusyError")))) {
|
|
DisconnectSco();
|
|
} else {
|
|
// Close Sco later since Dialer is still playing busy tone via HF.
|
|
MessageLoop::current()->PostDelayedTask(FROM_HERE,
|
|
new CloseScoTask(),
|
|
sBusyToneInterval);
|
|
}
|
|
|
|
ResetCallArray();
|
|
}
|
|
break;
|
|
default:
|
|
BT_WARNING("Not handling state changed");
|
|
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.mDirection = true;
|
|
|
|
mCdmaSecondCall.mNumber = aNumber;
|
|
mCdmaSecondCall.mType = (aNumber[0] == '+') ? TOA_INTERNATIONAL :
|
|
TOA_UNKNOWN;
|
|
|
|
SendCCWA(aNumber, mCdmaSecondCall.mType);
|
|
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 = nsITelephonyProvider::CALL_STATE_CONNECTED;
|
|
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();
|
|
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()) ?
|
|
nsITelephonyProvider::CALL_STATE_HELD :
|
|
nsITelephonyProvider::CALL_STATE_CONNECTED;
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnSocketConnectSuccess(BluetoothSocket* aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
MOZ_ASSERT(mListener);
|
|
|
|
// Success to create a SCO socket
|
|
if (aSocket == mScoSocket) {
|
|
OnScoConnectSuccess();
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* If the created connection is an inbound connection, close another server
|
|
* socket because currently only one SLC is allowed. After that, we need to
|
|
* make sure that both server socket would be nulled out. As for outbound
|
|
* connections, we do nothing since sockets have been already handled in
|
|
* function Connect().
|
|
*/
|
|
if (aSocket == mHandsfreeSocket) {
|
|
MOZ_ASSERT(!mSocket);
|
|
mHandsfreeSocket.swap(mSocket);
|
|
|
|
mHeadsetSocket->Disconnect();
|
|
mHeadsetSocket = nullptr;
|
|
} else if (aSocket == mHeadsetSocket) {
|
|
MOZ_ASSERT(!mSocket);
|
|
mHeadsetSocket.swap(mSocket);
|
|
|
|
mHandsfreeSocket->Disconnect();
|
|
mHandsfreeSocket = nullptr;
|
|
}
|
|
|
|
// Enumerate current calls
|
|
mListener->EnumerateCalls();
|
|
|
|
mFirstCKPD = true;
|
|
|
|
// Cache device path for NotifySettings() since we can't get socket address
|
|
// when a headset disconnect with us
|
|
mSocket->GetAddress(mDeviceAddress);
|
|
NotifyConnectionStatusChanged(
|
|
NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID));
|
|
|
|
ListenSco();
|
|
|
|
OnConnect(EmptyString());
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnSocketConnectError(BluetoothSocket* aSocket)
|
|
{
|
|
// Failed to create a SCO socket
|
|
if (aSocket == mScoSocket) {
|
|
OnScoConnectError();
|
|
return;
|
|
}
|
|
|
|
mHandsfreeSocket = nullptr;
|
|
mHeadsetSocket = nullptr;
|
|
|
|
OnConnect(NS_LITERAL_STRING("SocketConnectionError"));
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnSocketDisconnect(BluetoothSocket* aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
|
|
if (aSocket == mScoSocket) {
|
|
// SCO socket is closed
|
|
OnScoDisconnect();
|
|
return;
|
|
}
|
|
|
|
if (aSocket != mSocket) {
|
|
// Do nothing when a listening server socket is closed.
|
|
return;
|
|
}
|
|
|
|
DisconnectSco();
|
|
|
|
NotifyConnectionStatusChanged(
|
|
NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID));
|
|
OnDisconnect(EmptyString());
|
|
|
|
Reset();
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
|
|
{
|
|
// UpdateSdpRecord() is not called so this callback function should not
|
|
// be invoked.
|
|
MOZ_ASSUME_UNREACHABLE("UpdateSdpRecords() should be called somewhere");
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
|
|
const nsAString& aServiceUuid,
|
|
int aChannel)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!aDeviceAddress.IsEmpty());
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
NS_ENSURE_TRUE_VOID(bs);
|
|
|
|
if (aChannel < 0) {
|
|
// If we can't find Handsfree server channel number on the remote device,
|
|
// try to create HSP connection instead.
|
|
nsString hspUuid;
|
|
BluetoothUuidHelper::GetString(BluetoothServiceClass::HEADSET, hspUuid);
|
|
|
|
if (aServiceUuid.Equals(hspUuid)) {
|
|
OnConnect(NS_LITERAL_STRING(ERR_SERVICE_CHANNEL_NOT_FOUND));
|
|
} else if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress,
|
|
hspUuid, this))) {
|
|
OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mSocket);
|
|
|
|
if (!mSocket->Connect(NS_ConvertUTF16toUTF8(aDeviceAddress), aChannel)) {
|
|
OnConnect(NS_LITERAL_STRING("SocketConnectionError"));
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnScoConnectSuccess()
|
|
{
|
|
// For active connection request, we need to reply the DOMRequest
|
|
if (mScoRunnable) {
|
|
DispatchBluetoothReply(mScoRunnable,
|
|
BluetoothValue(true), EmptyString());
|
|
mScoRunnable = nullptr;
|
|
}
|
|
|
|
NotifyConnectionStatusChanged(
|
|
NS_LITERAL_STRING(BLUETOOTH_SCO_STATUS_CHANGED_ID));
|
|
|
|
mScoSocketStatus = mScoSocket->GetConnectionStatus();
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnScoConnectError()
|
|
{
|
|
if (mScoRunnable) {
|
|
NS_NAMED_LITERAL_STRING(replyError, "Failed to create SCO socket!");
|
|
DispatchBluetoothReply(mScoRunnable, BluetoothValue(), replyError);
|
|
|
|
mScoRunnable = nullptr;
|
|
}
|
|
|
|
ListenSco();
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnScoDisconnect()
|
|
{
|
|
if (mScoSocketStatus == SocketConnectionStatus::SOCKET_CONNECTED) {
|
|
ListenSco();
|
|
NotifyConnectionStatusChanged(
|
|
NS_LITERAL_STRING(BLUETOOTH_SCO_STATUS_CHANGED_ID));
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::IsConnected()
|
|
{
|
|
if (mSocket) {
|
|
return mSocket->GetConnectionStatus() ==
|
|
SocketConnectionStatus::SOCKET_CONNECTED;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::GetAddress(nsAString& aDeviceAddress)
|
|
{
|
|
return mSocket->GetAddress(aDeviceAddress);
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::ConnectSco(BluetoothReplyRunnable* aRunnable)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (sInShutdown) {
|
|
BT_WARNING("ConnecteSco called while in shutdown!");
|
|
return false;
|
|
}
|
|
|
|
if (!IsConnected()) {
|
|
BT_WARNING("BluetoothHfpManager is not connected");
|
|
return false;
|
|
}
|
|
|
|
SocketConnectionStatus status = mScoSocket->GetConnectionStatus();
|
|
if (status == SocketConnectionStatus::SOCKET_CONNECTED ||
|
|
status == SocketConnectionStatus::SOCKET_CONNECTING ||
|
|
(mScoRunnable && (mScoRunnable != aRunnable))) {
|
|
BT_WARNING("SCO connection exists or is being established");
|
|
return false;
|
|
}
|
|
|
|
mScoSocket->Disconnect();
|
|
|
|
mScoRunnable = aRunnable;
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
NS_ENSURE_TRUE(bs, false);
|
|
nsresult rv = bs->GetScoSocket(mDeviceAddress, true, false, mScoSocket);
|
|
|
|
mScoSocketStatus = mSocket->GetConnectionStatus();
|
|
return NS_SUCCEEDED(rv);
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::DisconnectSco()
|
|
{
|
|
if (!IsScoConnected()) {
|
|
BT_WARNING("SCO has been already disconnected.");
|
|
return false;
|
|
}
|
|
|
|
mScoSocket->Disconnect();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::ListenSco()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (sInShutdown) {
|
|
BT_WARNING("ListenSco called while in shutdown!");
|
|
return false;
|
|
}
|
|
|
|
if (mScoSocket->GetConnectionStatus() ==
|
|
SocketConnectionStatus::SOCKET_LISTENING) {
|
|
BT_WARNING("SCO socket has been already listening");
|
|
return false;
|
|
}
|
|
|
|
mScoSocket->Disconnect();
|
|
|
|
if (!mScoSocket->Listen(-1)) {
|
|
BT_WARNING("Can't listen on SCO socket!");
|
|
return false;
|
|
}
|
|
|
|
mScoSocketStatus = mScoSocket->GetConnectionStatus();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothHfpManager::IsScoConnected()
|
|
{
|
|
if (mScoSocket) {
|
|
return mScoSocket->GetConnectionStatus() ==
|
|
SocketConnectionStatus::SOCKET_CONNECTED;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnConnect(const nsAString& aErrorStr)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// When we failed to create a socket, restart listening.
|
|
if (!aErrorStr.IsEmpty()) {
|
|
mSocket = nullptr;
|
|
Listen();
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
nsRefPtr<BluetoothProfileController> controller = mController.forget();
|
|
controller->OnConnect(aErrorStr);
|
|
}
|
|
|
|
void
|
|
BluetoothHfpManager::OnDisconnect(const nsAString& aErrorStr)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Start listening
|
|
mSocket = nullptr;
|
|
Listen();
|
|
|
|
/**
|
|
* 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);
|
|
|
|
nsRefPtr<BluetoothProfileController> controller = mController.forget();
|
|
controller->OnDisconnect(aErrorStr);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS1(BluetoothHfpManager, nsIObserver)
|