mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 00:35:44 +00:00
3643741917
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.
1506 lines
39 KiB
C++
1506 lines
39 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 "BluetoothOppManager.h"
|
|
|
|
#include "BluetoothProfileController.h"
|
|
#include "BluetoothReplyRunnable.h"
|
|
#include "BluetoothService.h"
|
|
#include "BluetoothSocket.h"
|
|
#include "BluetoothUtils.h"
|
|
#include "BluetoothUuid.h"
|
|
#include "ObexBase.h"
|
|
|
|
#include "mozilla/dom/bluetooth/BluetoothTypes.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsCExternalHandlerService.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIDOMFile.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIMIMEService.h"
|
|
#include "nsIOutputStream.h"
|
|
#include "nsIVolumeService.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
|
|
#define TARGET_SUBDIR "Download/Bluetooth/"
|
|
|
|
USING_BLUETOOTH_NAMESPACE
|
|
using namespace mozilla;
|
|
using namespace mozilla::ipc;
|
|
|
|
namespace {
|
|
// Sending system message "bluetooth-opp-update-progress" every 50kb
|
|
static const uint32_t kUpdateProgressBase = 50 * 1024;
|
|
|
|
/*
|
|
* The format of the header of an PUT request is
|
|
* [opcode:1][packet length:2][headerId:1][header length:2]
|
|
*/
|
|
static const uint32_t kPutRequestHeaderSize = 6;
|
|
|
|
StaticRefPtr<BluetoothOppManager> sBluetoothOppManager;
|
|
static bool sInShutdown = false;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BluetoothOppManager::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const PRUnichar* aData)
|
|
{
|
|
MOZ_ASSERT(sBluetoothOppManager);
|
|
|
|
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
|
|
HandleShutdown();
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(false, "BluetoothOppManager got unexpected topic!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
class SendSocketDataTask : public nsRunnable
|
|
{
|
|
public:
|
|
SendSocketDataTask(uint8_t* aStream, uint32_t aSize)
|
|
: mStream(aStream)
|
|
, mSize(aSize)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
sBluetoothOppManager->SendPutRequest(mStream, mSize);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsAutoArrayPtr<uint8_t> mStream;
|
|
uint32_t mSize;
|
|
};
|
|
|
|
class ReadFileTask : public nsRunnable
|
|
{
|
|
public:
|
|
ReadFileTask(nsIInputStream* aInputStream,
|
|
uint32_t aRemoteMaxPacketSize) : mInputStream(aInputStream)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mAvailablePacketSize = aRemoteMaxPacketSize - kPutRequestHeaderSize;
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
uint32_t numRead;
|
|
nsAutoArrayPtr<char> buf(new char[mAvailablePacketSize]);
|
|
|
|
// function inputstream->Read() only works on non-main thread
|
|
nsresult rv = mInputStream->Read(buf, mAvailablePacketSize, &numRead);
|
|
if (NS_FAILED(rv)) {
|
|
// Needs error handling here
|
|
BT_WARNING("Failed to read from input stream");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (numRead > 0) {
|
|
sBluetoothOppManager->CheckPutFinal(numRead);
|
|
|
|
nsRefPtr<SendSocketDataTask> task =
|
|
new SendSocketDataTask((uint8_t*)buf.forget(), numRead);
|
|
if (NS_FAILED(NS_DispatchToMainThread(task))) {
|
|
BT_WARNING("Failed to dispatch to main thread!");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
};
|
|
|
|
private:
|
|
nsCOMPtr<nsIInputStream> mInputStream;
|
|
uint32_t mAvailablePacketSize;
|
|
};
|
|
|
|
class CloseSocketTask : public Task
|
|
{
|
|
public:
|
|
CloseSocketTask(BluetoothSocket* aSocket) : mSocket(aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
}
|
|
|
|
void Run() MOZ_OVERRIDE
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mSocket->GetConnectionStatus() ==
|
|
SocketConnectionStatus::SOCKET_CONNECTED) {
|
|
mSocket->Disconnect();
|
|
}
|
|
}
|
|
|
|
private:
|
|
nsRefPtr<BluetoothSocket> mSocket;
|
|
};
|
|
|
|
BluetoothOppManager::BluetoothOppManager() : mConnected(false)
|
|
, mRemoteObexVersion(0)
|
|
, mRemoteConnectionFlags(0)
|
|
, mRemoteMaxPacketLength(0)
|
|
, mLastCommand(0)
|
|
, mPacketLeftLength(0)
|
|
, mBodySegmentLength(0)
|
|
, mReceivedDataBufferOffset(0)
|
|
, mAbortFlag(false)
|
|
, mNewFileFlag(false)
|
|
, mPutFinalFlag(false)
|
|
, mSendTransferCompleteFlag(false)
|
|
, mSuccessFlag(false)
|
|
, mIsServer(true)
|
|
, mWaitingForConfirmationFlag(false)
|
|
, mFileLength(0)
|
|
, mSentFileLength(0)
|
|
, mWaitingToSendPutFinal(false)
|
|
, mCurrentBlobIndex(-1)
|
|
, mController(nullptr)
|
|
{
|
|
mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
|
|
}
|
|
|
|
BluetoothOppManager::~BluetoothOppManager()
|
|
{
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
NS_ENSURE_TRUE_VOID(obs);
|
|
if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
|
|
BT_WARNING("Failed to remove shutdown observer!");
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::Init()
|
|
{
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
NS_ENSURE_TRUE(obs, false);
|
|
if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
|
|
BT_WARNING("Failed to add shutdown observer!");
|
|
return false;
|
|
}
|
|
|
|
Listen();
|
|
|
|
return true;
|
|
}
|
|
|
|
//static
|
|
BluetoothOppManager*
|
|
BluetoothOppManager::Get()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// If sBluetoothOppManager already exists, exit early
|
|
if (sBluetoothOppManager) {
|
|
return sBluetoothOppManager;
|
|
}
|
|
|
|
// If we're in shutdown, don't create a new instance
|
|
NS_ENSURE_FALSE(sInShutdown, nullptr);
|
|
|
|
// Create a new instance, register, and return
|
|
BluetoothOppManager *manager = new BluetoothOppManager();
|
|
NS_ENSURE_TRUE(manager->Init(), nullptr);
|
|
|
|
sBluetoothOppManager = manager;
|
|
return sBluetoothOppManager;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::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 (mConnectedDeviceAddress == aDeviceAddress) {
|
|
aController->OnConnect(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED));
|
|
} else {
|
|
aController->OnConnect(NS_LITERAL_STRING(ERR_REACHED_CONNECTION_LIMIT));
|
|
}
|
|
return;
|
|
}
|
|
|
|
mNeedsUpdatingSdpRecords = true;
|
|
|
|
nsString uuid;
|
|
BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
|
|
|
|
if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) {
|
|
aController->OnConnect(NS_LITERAL_STRING(ERR_SERVICE_CHANNEL_NOT_FOUND));
|
|
return;
|
|
}
|
|
|
|
// Stop listening because currently we only support one connection at a time.
|
|
if (mRfcommSocket) {
|
|
mRfcommSocket->Disconnect();
|
|
mRfcommSocket = nullptr;
|
|
}
|
|
|
|
if (mL2capSocket) {
|
|
mL2capSocket->Disconnect();
|
|
mL2capSocket = nullptr;
|
|
}
|
|
|
|
mController = aController;
|
|
mSocket =
|
|
new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::Disconnect(BluetoothProfileController* aController)
|
|
{
|
|
if (!mSocket) {
|
|
if (aController) {
|
|
aController->OnDisconnect(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED));
|
|
}
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!mController);
|
|
|
|
mController = aController;
|
|
mSocket->Disconnect();
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::HandleShutdown()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
sInShutdown = true;
|
|
Disconnect(nullptr);
|
|
sBluetoothOppManager = nullptr;
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::Listen()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mSocket) {
|
|
BT_WARNING("mSocket exists. Failed to listen.");
|
|
return false;
|
|
}
|
|
|
|
if (!mRfcommSocket) {
|
|
mRfcommSocket =
|
|
new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
|
|
|
|
if (!mRfcommSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH)) {
|
|
BT_WARNING("[OPP] Can't listen on RFCOMM socket!");
|
|
mRfcommSocket = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!mL2capSocket) {
|
|
mL2capSocket =
|
|
new BluetoothSocket(this, BluetoothSocketType::EL2CAP, true, true);
|
|
|
|
if (!mL2capSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH_L2CAP)) {
|
|
BT_WARNING("[OPP] Can't listen on L2CAP socket!");
|
|
mRfcommSocket->Disconnect();
|
|
mRfcommSocket = nullptr;
|
|
mL2capSocket = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::StartSendingNextFile()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!IsConnected());
|
|
MOZ_ASSERT(mBlobs.Length() > mCurrentBlobIndex + 1);
|
|
|
|
mIsServer = false;
|
|
mBlob = mBlobs[++mCurrentBlobIndex];
|
|
|
|
// Before sending content, we have to send a header including
|
|
// information such as file name, file length and content type.
|
|
ExtractBlobHeaders();
|
|
StartFileTransfer();
|
|
|
|
if (mCurrentBlobIndex == 0) {
|
|
// We may have more than one file waiting for transferring, but only one
|
|
// CONNECT request would be sent. Therefore check if this is the very first
|
|
// file at the head of queue.
|
|
SendConnectRequest();
|
|
} else {
|
|
SendPutHeaderRequest(mFileName, mFileLength);
|
|
AfterFirstPut();
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::SendFile(const nsAString& aDeviceAddress,
|
|
BlobParent* aActor)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mCurrentBlobIndex >= 0) {
|
|
if (mConnectedDeviceAddress != aDeviceAddress) {
|
|
return false;
|
|
}
|
|
|
|
mBlobs.AppendElement(aActor->GetBlob().get());
|
|
return true;
|
|
}
|
|
|
|
mBlobs.AppendElement(aActor->GetBlob().get());
|
|
StartSendingNextFile();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::StopSendingFile()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mIsServer) {
|
|
mAbortFlag = true;
|
|
} else {
|
|
Disconnect(nullptr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
|
|
{
|
|
NS_ENSURE_TRUE(mConnected, false);
|
|
NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false);
|
|
|
|
MOZ_ASSERT(mPacketLeftLength == 0);
|
|
|
|
mWaitingForConfirmationFlag = false;
|
|
|
|
// For the first packet of first file
|
|
bool success = false;
|
|
if (aConfirm) {
|
|
StartFileTransfer();
|
|
if (CreateFile()) {
|
|
success = WriteToFile(mBodySegment.get(), mBodySegmentLength);
|
|
}
|
|
}
|
|
|
|
if (success && mPutFinalFlag) {
|
|
mSuccessFlag = true;
|
|
FileTransferComplete();
|
|
NotifyAboutFileChange();
|
|
}
|
|
|
|
ReplyToPut(mPutFinalFlag, success);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::AfterFirstPut()
|
|
{
|
|
mUpdateProgressCounter = 1;
|
|
mPutFinalFlag = false;
|
|
mReceivedDataBufferOffset = 0;
|
|
mSendTransferCompleteFlag = false;
|
|
mSentFileLength = 0;
|
|
mWaitingToSendPutFinal = false;
|
|
mSuccessFlag = false;
|
|
mBodySegmentLength = 0;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::AfterOppConnected()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mConnected = true;
|
|
mAbortFlag = false;
|
|
mWaitingForConfirmationFlag = true;
|
|
AfterFirstPut();
|
|
// Get a mount lock to prevent the sdcard from being shared with
|
|
// the PC while we're doing a OPP file transfer. After OPP transaction
|
|
// were done, the mount lock will be freed.
|
|
if (!AcquireSdcardMountLock()) {
|
|
// If we fail to get a mount lock, abort this transaction
|
|
// Directly sending disconnect-request is better than abort-request
|
|
BT_WARNING("BluetoothOPPManager couldn't get a mount lock!");
|
|
Disconnect(nullptr);
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::AfterOppDisconnected()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mConnected = false;
|
|
mIsServer = true;
|
|
mLastCommand = 0;
|
|
mPacketLeftLength = 0;
|
|
mDsFile = nullptr;
|
|
|
|
ClearQueue();
|
|
|
|
// We can't reset mSuccessFlag here since this function may be called
|
|
// before we send system message of transfer complete
|
|
// mSuccessFlag = false;
|
|
|
|
if (mInputStream) {
|
|
mInputStream->Close();
|
|
mInputStream = nullptr;
|
|
}
|
|
|
|
if (mOutputStream) {
|
|
mOutputStream->Close();
|
|
mOutputStream = nullptr;
|
|
}
|
|
|
|
if (mReadFileThread) {
|
|
mReadFileThread->Shutdown();
|
|
mReadFileThread = nullptr;
|
|
}
|
|
// Release the Mount lock if file transfer completed
|
|
if (mMountLock) {
|
|
// The mount lock will be implicitly unlocked
|
|
mMountLock = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::DeleteReceivedFile()
|
|
{
|
|
if (mOutputStream) {
|
|
mOutputStream->Close();
|
|
mOutputStream = nullptr;
|
|
}
|
|
|
|
if (mDsFile && mDsFile->mFile) {
|
|
mDsFile->mFile->Remove(false);
|
|
mDsFile = nullptr;
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::CreateFile()
|
|
{
|
|
MOZ_ASSERT(mPacketLeftLength == 0);
|
|
|
|
nsString path;
|
|
path.AssignLiteral(TARGET_SUBDIR);
|
|
path.Append(mFileName);
|
|
|
|
mDsFile = DeviceStorageFile::CreateUnique(path, nsIFile::NORMAL_FILE_TYPE, 0644);
|
|
NS_ENSURE_TRUE(mDsFile, false);
|
|
|
|
nsCOMPtr<nsIFile> f;
|
|
mDsFile->mFile->Clone(getter_AddRefs(f));
|
|
|
|
/*
|
|
* The function CreateUnique() may create a file with a different file
|
|
* name from the original mFileName. Therefore we have to retrieve
|
|
* the file name again.
|
|
*/
|
|
f->GetLeafName(mFileName);
|
|
|
|
NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), f);
|
|
NS_ENSURE_TRUE(mOutputStream, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength)
|
|
{
|
|
NS_ENSURE_TRUE(mOutputStream, false);
|
|
|
|
uint32_t wrote = 0;
|
|
mOutputStream->Write((const char*)aData, aDataLength, &wrote);
|
|
NS_ENSURE_TRUE(aDataLength == wrote, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Virtual function of class SocketConsumer
|
|
void
|
|
BluetoothOppManager::ExtractPacketHeaders(const ObexHeaderSet& aHeader)
|
|
{
|
|
if (aHeader.Has(ObexHeaderId::Name)) {
|
|
aHeader.GetName(mFileName);
|
|
}
|
|
|
|
if (aHeader.Has(ObexHeaderId::Type)) {
|
|
aHeader.GetContentType(mContentType);
|
|
}
|
|
|
|
if (aHeader.Has(ObexHeaderId::Length)) {
|
|
aHeader.GetLength(&mFileLength);
|
|
}
|
|
|
|
if (aHeader.Has(ObexHeaderId::Body) ||
|
|
aHeader.Has(ObexHeaderId::EndOfBody)) {
|
|
uint8_t* bodyPtr;
|
|
aHeader.GetBody(&bodyPtr);
|
|
mBodySegment = bodyPtr;
|
|
|
|
aHeader.GetBodyLength(&mBodySegmentLength);
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::ExtractBlobHeaders()
|
|
{
|
|
RetrieveSentFileName();
|
|
|
|
nsresult rv = mBlob->GetType(mContentType);
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Can't get content type");
|
|
SendDisconnectRequest();
|
|
return false;
|
|
}
|
|
|
|
uint64_t fileLength;
|
|
rv = mBlob->GetSize(&fileLength);
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Can't get file size");
|
|
SendDisconnectRequest();
|
|
return false;
|
|
}
|
|
|
|
// Currently we keep the size of files which were sent/received via
|
|
// Bluetooth not exceed UINT32_MAX because the Length header in OBEX
|
|
// is only 4-byte long. Although it is possible to transfer a file
|
|
// larger than UINT32_MAX, it needs to parse another OBEX Header
|
|
// and I would like to leave it as a feature.
|
|
if (fileLength > (uint64_t)UINT32_MAX) {
|
|
BT_WARNING("The file size is too large for now");
|
|
SendDisconnectRequest();
|
|
return false;
|
|
}
|
|
|
|
mFileLength = fileLength;
|
|
rv = NS_NewThread(getter_AddRefs(mReadFileThread));
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Can't create thread");
|
|
SendDisconnectRequest();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::RetrieveSentFileName()
|
|
{
|
|
mFileName.Truncate();
|
|
|
|
nsCOMPtr<nsIDOMFile> file = do_QueryInterface(mBlob);
|
|
if (file) {
|
|
file->GetName(mFileName);
|
|
}
|
|
|
|
/**
|
|
* We try our best to get the file extention to avoid interoperability issues.
|
|
* However, once we found that we are unable to get suitable extension or
|
|
* information about the content type, sending a pre-defined file name without
|
|
* extension would be fine.
|
|
*/
|
|
if (mFileName.IsEmpty()) {
|
|
mFileName.AssignLiteral("Unknown");
|
|
}
|
|
|
|
int32_t offset = mFileName.RFindChar('/');
|
|
if (offset != kNotFound) {
|
|
mFileName = Substring(mFileName, offset + 1);
|
|
}
|
|
|
|
offset = mFileName.RFindChar('.');
|
|
if (offset == kNotFound) {
|
|
nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
|
|
|
|
if (mimeSvc) {
|
|
nsString mimeType;
|
|
mBlob->GetType(mimeType);
|
|
|
|
nsCString extension;
|
|
nsresult rv =
|
|
mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
|
|
EmptyCString(),
|
|
extension);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mFileName.AppendLiteral(".");
|
|
AppendUTF8toUTF16(extension, mFileName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::IsReservedChar(PRUnichar c)
|
|
{
|
|
return (c < 0x0020 ||
|
|
c == PRUnichar('?') || c == PRUnichar('|') || c == PRUnichar('<') ||
|
|
c == PRUnichar('>') || c == PRUnichar('"') || c == PRUnichar(':') ||
|
|
c == PRUnichar('/') || c == PRUnichar('*') || c == PRUnichar('\\'));
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ValidateFileName()
|
|
{
|
|
int length = mFileName.Length();
|
|
|
|
for (int i = 0; i < length; ++i) {
|
|
// Replace reserved char of fat file system with '_'
|
|
if (IsReservedChar(mFileName.CharAt(i))) {
|
|
mFileName.Replace(i, 1, PRUnichar('_'));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
|
|
{
|
|
uint8_t opCode;
|
|
int packetLength;
|
|
int receivedLength = aMessage->mSize;
|
|
|
|
if (mPacketLeftLength > 0) {
|
|
opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put;
|
|
packetLength = mPacketLeftLength;
|
|
} else {
|
|
opCode = aMessage->mData[0];
|
|
packetLength = (((int)aMessage->mData[1]) << 8) | aMessage->mData[2];
|
|
|
|
// When there's a Put packet right after a PutFinal packet,
|
|
// which means it's the start point of a new file.
|
|
if (mPutFinalFlag &&
|
|
(opCode == ObexRequestCode::Put ||
|
|
opCode == ObexRequestCode::PutFinal)) {
|
|
mNewFileFlag = true;
|
|
AfterFirstPut();
|
|
}
|
|
}
|
|
|
|
ObexHeaderSet pktHeaders(opCode);
|
|
if (opCode == ObexRequestCode::Connect) {
|
|
mIsServer = true;
|
|
|
|
// Section 3.3.1 "Connect", IrOBEX 1.2
|
|
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
|
|
// [Headers:var]
|
|
ParseHeaders(&aMessage->mData[7],
|
|
receivedLength - 7,
|
|
&pktHeaders);
|
|
ReplyToConnect();
|
|
AfterOppConnected();
|
|
} else if (opCode == ObexRequestCode::Abort) {
|
|
// Section 3.3.5 "Abort", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
ParseHeaders(&aMessage->mData[3],
|
|
receivedLength - 3,
|
|
&pktHeaders);
|
|
ReplyToDisconnectOrAbort();
|
|
DeleteReceivedFile();
|
|
} else if (opCode == ObexRequestCode::Disconnect) {
|
|
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
ParseHeaders(&aMessage->mData[3],
|
|
receivedLength - 3,
|
|
&pktHeaders);
|
|
ReplyToDisconnectOrAbort();
|
|
AfterOppDisconnected();
|
|
FileTransferComplete();
|
|
} else if (opCode == ObexRequestCode::Put ||
|
|
opCode == ObexRequestCode::PutFinal) {
|
|
int headerStartIndex = 0;
|
|
|
|
// The first part of each Put packet
|
|
if (mReceivedDataBufferOffset == 0) {
|
|
// Section 3.3.3 "Put", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
headerStartIndex = 3;
|
|
// The largest buffer size is 65535 because packetLength is a
|
|
// 2-byte value (0 ~ 0xFFFF).
|
|
mReceivedDataBuffer = new uint8_t[packetLength];
|
|
mPacketLeftLength = packetLength;
|
|
|
|
/*
|
|
* A PUT request from remote devices may be divided into multiple parts.
|
|
* In other words, one request may need to be received multiple times,
|
|
* so here we keep a variable mPacketLeftLength to indicate if current
|
|
* PUT request is done.
|
|
*/
|
|
mPutFinalFlag = (opCode == ObexRequestCode::PutFinal);
|
|
}
|
|
|
|
memcpy(mReceivedDataBuffer.get() + mReceivedDataBufferOffset,
|
|
&aMessage->mData[headerStartIndex],
|
|
receivedLength - headerStartIndex);
|
|
|
|
mPacketLeftLength -= receivedLength;
|
|
mReceivedDataBufferOffset += receivedLength - headerStartIndex;
|
|
if (mPacketLeftLength) {
|
|
return;
|
|
}
|
|
|
|
// A Put packet is received completely
|
|
ParseHeaders(mReceivedDataBuffer.get(),
|
|
mReceivedDataBufferOffset,
|
|
&pktHeaders);
|
|
ExtractPacketHeaders(pktHeaders);
|
|
ValidateFileName();
|
|
|
|
mReceivedDataBufferOffset = 0;
|
|
|
|
// When we cancel the transfer, delete the file and notify completion
|
|
if (mAbortFlag) {
|
|
ReplyToPut(mPutFinalFlag, false);
|
|
mSentFileLength += mBodySegmentLength;
|
|
DeleteReceivedFile();
|
|
FileTransferComplete();
|
|
return;
|
|
}
|
|
|
|
// Wait until get confirmation from user, then create file and write to it
|
|
if (mWaitingForConfirmationFlag) {
|
|
ReceivingFileConfirmation();
|
|
mSentFileLength += mBodySegmentLength;
|
|
return;
|
|
}
|
|
|
|
// Already get confirmation from user, create a new file if needed and
|
|
// write to output stream
|
|
if (mNewFileFlag) {
|
|
StartFileTransfer();
|
|
if (!CreateFile()) {
|
|
ReplyToPut(mPutFinalFlag, false);
|
|
return;
|
|
}
|
|
mNewFileFlag = false;
|
|
}
|
|
|
|
if (!WriteToFile(mBodySegment.get(), mBodySegmentLength)) {
|
|
ReplyToPut(mPutFinalFlag, false);
|
|
return;
|
|
}
|
|
|
|
ReplyToPut(mPutFinalFlag, true);
|
|
|
|
// Send progress update
|
|
mSentFileLength += mBodySegmentLength;
|
|
if (mSentFileLength > kUpdateProgressBase * mUpdateProgressCounter) {
|
|
UpdateProgress();
|
|
mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1;
|
|
}
|
|
|
|
// Success to receive a file and notify completion
|
|
if (mPutFinalFlag) {
|
|
mSuccessFlag = true;
|
|
FileTransferComplete();
|
|
NotifyAboutFileChange();
|
|
}
|
|
} else if (opCode == ObexRequestCode::Get ||
|
|
opCode == ObexRequestCode::GetFinal ||
|
|
opCode == ObexRequestCode::SetPath) {
|
|
ReplyError(ObexResponseCode::BadRequest);
|
|
BT_WARNING("Unsupported ObexRequestCode");
|
|
} else {
|
|
ReplyError(ObexResponseCode::NotImplemented);
|
|
BT_WARNING("Unrecognized ObexRequestCode");
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ClearQueue()
|
|
{
|
|
mCurrentBlobIndex = -1;
|
|
mBlob = nullptr;
|
|
|
|
while (!mBlobs.IsEmpty()) {
|
|
mBlobs.RemoveElement(mBlobs[0]);
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage)
|
|
{
|
|
uint8_t opCode;
|
|
int packetLength;
|
|
|
|
if (mPacketLeftLength > 0) {
|
|
opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put;
|
|
packetLength = mPacketLeftLength;
|
|
} else {
|
|
opCode = aMessage->mData[0];
|
|
packetLength = (((int)aMessage->mData[1]) << 8) | aMessage->mData[2];
|
|
}
|
|
|
|
// Check response code and send out system message as finished if the response
|
|
// code is somehow incorrect.
|
|
uint8_t expectedOpCode = ObexResponseCode::Success;
|
|
if (mLastCommand == ObexRequestCode::Put) {
|
|
expectedOpCode = ObexResponseCode::Continue;
|
|
}
|
|
|
|
if (opCode != expectedOpCode) {
|
|
if (mLastCommand == ObexRequestCode::Put ||
|
|
mLastCommand == ObexRequestCode::Abort ||
|
|
mLastCommand == ObexRequestCode::PutFinal) {
|
|
SendDisconnectRequest();
|
|
}
|
|
nsAutoCString str;
|
|
str += "[OPP] 0x";
|
|
str.AppendInt(mLastCommand, 16);
|
|
str += " failed";
|
|
BT_WARNING(str.get());
|
|
FileTransferComplete();
|
|
return;
|
|
}
|
|
|
|
if (mLastCommand == ObexRequestCode::PutFinal) {
|
|
mSuccessFlag = true;
|
|
FileTransferComplete();
|
|
|
|
if (mInputStream) {
|
|
mInputStream->Close();
|
|
mInputStream = nullptr;
|
|
}
|
|
|
|
if (mCurrentBlobIndex + 1 == mBlobs.Length()) {
|
|
SendDisconnectRequest();
|
|
} else {
|
|
StartSendingNextFile();
|
|
}
|
|
} else if (mLastCommand == ObexRequestCode::Abort) {
|
|
SendDisconnectRequest();
|
|
FileTransferComplete();
|
|
} else if (mLastCommand == ObexRequestCode::Disconnect) {
|
|
AfterOppDisconnected();
|
|
// Most devices will directly terminate connection after receiving
|
|
// Disconnect request, so we make a delay here. If the socket hasn't been
|
|
// disconnected, we will close it.
|
|
if (mSocket) {
|
|
MessageLoop::current()->
|
|
PostDelayedTask(FROM_HERE, new CloseSocketTask(mSocket), 1000);
|
|
}
|
|
} else if (mLastCommand == ObexRequestCode::Connect) {
|
|
MOZ_ASSERT(!mFileName.IsEmpty());
|
|
MOZ_ASSERT(mBlob);
|
|
|
|
AfterOppConnected();
|
|
|
|
// Keep remote information
|
|
mRemoteObexVersion = aMessage->mData[3];
|
|
mRemoteConnectionFlags = aMessage->mData[4];
|
|
mRemoteMaxPacketLength =
|
|
(((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]);
|
|
|
|
sBluetoothOppManager->SendPutHeaderRequest(mFileName, mFileLength);
|
|
} else if (mLastCommand == ObexRequestCode::Put) {
|
|
if (mWaitingToSendPutFinal) {
|
|
SendPutFinalRequest();
|
|
return;
|
|
}
|
|
|
|
if (kUpdateProgressBase * mUpdateProgressCounter < mSentFileLength) {
|
|
UpdateProgress();
|
|
mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1;
|
|
}
|
|
|
|
nsresult rv;
|
|
if (!mInputStream) {
|
|
rv = mBlob->GetInternalStream(getter_AddRefs(mInputStream));
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Can't get internal stream of blob");
|
|
SendDisconnectRequest();
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsRefPtr<ReadFileTask> task = new ReadFileTask(mInputStream,
|
|
mRemoteMaxPacketLength);
|
|
rv = mReadFileThread->Dispatch(task, NS_DISPATCH_NORMAL);
|
|
if (NS_FAILED(rv)) {
|
|
BT_WARNING("Cannot dispatch read file task!");
|
|
SendDisconnectRequest();
|
|
}
|
|
} else {
|
|
BT_WARNING("Unhandled ObexRequestCode");
|
|
}
|
|
}
|
|
|
|
// Virtual function of class SocketConsumer
|
|
void
|
|
BluetoothOppManager::ReceiveSocketData(BluetoothSocket* aSocket,
|
|
nsAutoPtr<UnixSocketRawData>& aMessage)
|
|
{
|
|
if (mIsServer) {
|
|
ServerDataHandler(aMessage);
|
|
} else {
|
|
ClientDataHandler(aMessage);
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::SendConnectRequest()
|
|
{
|
|
if (mConnected) return;
|
|
|
|
// Section 3.3.1 "Connect", IrOBEX 1.2
|
|
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
|
|
// [Headers:var]
|
|
uint8_t req[255];
|
|
int index = 7;
|
|
|
|
req[3] = 0x10; // version=1.0
|
|
req[4] = 0x00; // flag=0x00
|
|
req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8;
|
|
req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH;
|
|
|
|
SendObexData(req, ObexRequestCode::Connect, index);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName,
|
|
int aFileSize)
|
|
{
|
|
if (!mConnected) return;
|
|
|
|
uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
|
|
|
|
int len = aFileName.Length();
|
|
uint8_t* fileName = new uint8_t[(len + 1) * 2];
|
|
const PRUnichar* fileNamePtr = aFileName.BeginReading();
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8);
|
|
fileName[i * 2 + 1] = (uint8_t)fileNamePtr[i];
|
|
}
|
|
|
|
fileName[len * 2] = 0x00;
|
|
fileName[len * 2 + 1] = 0x00;
|
|
|
|
int index = 3;
|
|
index += AppendHeaderName(&req[index], (char*)fileName, (len + 1) * 2);
|
|
index += AppendHeaderLength(&req[index], aFileSize);
|
|
|
|
SendObexData(req, ObexRequestCode::Put, index);
|
|
|
|
delete [] fileName;
|
|
delete [] req;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::SendPutRequest(uint8_t* aFileBody,
|
|
int aFileBodyLength)
|
|
{
|
|
int packetLeftSpace = mRemoteMaxPacketLength - kPutRequestHeaderSize;
|
|
|
|
if (!mConnected) return;
|
|
if (aFileBodyLength > packetLeftSpace) {
|
|
BT_WARNING("Not allowed such a small MaxPacketLength value");
|
|
return;
|
|
}
|
|
|
|
// Section 3.3.3 "Put", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
|
|
|
|
int index = 3;
|
|
index += AppendHeaderBody(&req[index], aFileBody, aFileBodyLength);
|
|
|
|
SendObexData(req, ObexRequestCode::Put, index);
|
|
delete [] req;
|
|
|
|
mSentFileLength += aFileBodyLength;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::SendPutFinalRequest()
|
|
{
|
|
if (!mConnected) return;
|
|
|
|
/**
|
|
* Section 2.2.9, "End-of-Body", IrObex 1.2
|
|
* End-of-Body is used to identify the last chunk of the object body.
|
|
* For most platforms, a PutFinal packet is sent with an zero length
|
|
* End-of-Body header.
|
|
*/
|
|
|
|
// [opcode:1][length:2]
|
|
int index = 3;
|
|
uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
|
|
index += AppendHeaderEndOfBody(&req[index]);
|
|
|
|
SendObexData(req, ObexRequestCode::PutFinal, index);
|
|
delete [] req;
|
|
|
|
mWaitingToSendPutFinal = false;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::SendDisconnectRequest()
|
|
{
|
|
if (!mConnected) return;
|
|
|
|
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[255];
|
|
int index = 3;
|
|
|
|
SendObexData(req, ObexRequestCode::Disconnect, index);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::CheckPutFinal(uint32_t aNumRead)
|
|
{
|
|
if (mSentFileLength + aNumRead >= mFileLength) {
|
|
mWaitingToSendPutFinal = true;
|
|
}
|
|
}
|
|
|
|
bool
|
|
BluetoothOppManager::IsConnected()
|
|
{
|
|
return (mConnected && !mSendTransferCompleteFlag);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::GetAddress(nsAString& aDeviceAddress)
|
|
{
|
|
return mSocket->GetAddress(aDeviceAddress);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ReplyToConnect()
|
|
{
|
|
if (mConnected) return;
|
|
|
|
// Section 3.3.1 "Connect", IrOBEX 1.2
|
|
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
|
|
// [Headers:var]
|
|
uint8_t req[255];
|
|
int index = 7;
|
|
|
|
req[3] = 0x10; // version=1.0
|
|
req[4] = 0x00; // flag=0x00
|
|
req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8;
|
|
req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH;
|
|
|
|
SendObexData(req, ObexResponseCode::Success, index);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ReplyToDisconnectOrAbort()
|
|
{
|
|
if (!mConnected) return;
|
|
|
|
// Section 3.3.2 "Disconnect" and Section 3.3.5 "Abort", IrOBEX 1.2
|
|
// The format of response packet of "Disconnect" and "Abort" are the same
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[255];
|
|
int index = 3;
|
|
|
|
SendObexData(req, ObexResponseCode::Success, index);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue)
|
|
{
|
|
if (!mConnected) return;
|
|
|
|
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[255];
|
|
int index = 3;
|
|
uint8_t opcode;
|
|
|
|
if (aContinue) {
|
|
opcode = (aFinal)? ObexResponseCode::Success :
|
|
ObexResponseCode::Continue;
|
|
} else {
|
|
opcode = (aFinal)? ObexResponseCode::Unauthorized :
|
|
ObexResponseCode::Unauthorized & (~FINAL_BIT);
|
|
}
|
|
|
|
SendObexData(req, opcode, index);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ReplyError(uint8_t aError)
|
|
{
|
|
if (!mConnected) return;
|
|
|
|
// Section 3.2 "Response Format", IrOBEX 1.2
|
|
// [opcode:1][length:2][Headers:var]
|
|
uint8_t req[255];
|
|
int index = 3;
|
|
|
|
SendObexData(req, aError, index);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize)
|
|
{
|
|
SetObexPacketInfo(aData, aOpcode, aSize);
|
|
|
|
if (!mIsServer) {
|
|
mLastCommand = aOpcode;
|
|
}
|
|
|
|
UnixSocketRawData* s = new UnixSocketRawData(aSize);
|
|
memcpy(s->mData, aData, s->mSize);
|
|
mSocket->SendSocketData(s);
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::FileTransferComplete()
|
|
{
|
|
if (mSendTransferCompleteFlag) {
|
|
return;
|
|
}
|
|
|
|
nsString type, name;
|
|
BluetoothValue v;
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
type.AssignLiteral("bluetooth-opp-transfer-complete");
|
|
|
|
name.AssignLiteral("address");
|
|
v = mConnectedDeviceAddress;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("success");
|
|
v = mSuccessFlag;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("received");
|
|
v = mIsServer;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("fileName");
|
|
v = mFileName;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("fileLength");
|
|
v = mSentFileLength;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("contentType");
|
|
v = mContentType;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
if (!BroadcastSystemMessage(type, parameters)) {
|
|
BT_WARNING("Failed to broadcast [bluetooth-opp-transfer-complete]");
|
|
return;
|
|
}
|
|
|
|
mSendTransferCompleteFlag = true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::StartFileTransfer()
|
|
{
|
|
nsString type, name;
|
|
BluetoothValue v;
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
type.AssignLiteral("bluetooth-opp-transfer-start");
|
|
|
|
name.AssignLiteral("address");
|
|
v = mConnectedDeviceAddress;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("received");
|
|
v = mIsServer;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("fileName");
|
|
v = mFileName;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("fileLength");
|
|
v = mFileLength;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("contentType");
|
|
v = mContentType;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
if (!BroadcastSystemMessage(type, parameters)) {
|
|
BT_WARNING("Failed to broadcast [bluetooth-opp-transfer-start]");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::UpdateProgress()
|
|
{
|
|
nsString type, name;
|
|
BluetoothValue v;
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
type.AssignLiteral("bluetooth-opp-update-progress");
|
|
|
|
name.AssignLiteral("address");
|
|
v = mConnectedDeviceAddress;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("received");
|
|
v = mIsServer;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("processedLength");
|
|
v = mSentFileLength;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("fileLength");
|
|
v = mFileLength;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
if (!BroadcastSystemMessage(type, parameters)) {
|
|
BT_WARNING("Failed to broadcast [bluetooth-opp-update-progress]");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::ReceivingFileConfirmation()
|
|
{
|
|
nsString type, name;
|
|
BluetoothValue v;
|
|
InfallibleTArray<BluetoothNamedValue> parameters;
|
|
type.AssignLiteral("bluetooth-opp-receiving-file-confirmation");
|
|
|
|
name.AssignLiteral("address");
|
|
v = mConnectedDeviceAddress;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("fileName");
|
|
v = mFileName;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("fileLength");
|
|
v = mFileLength;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
name.AssignLiteral("contentType");
|
|
v = mContentType;
|
|
parameters.AppendElement(BluetoothNamedValue(name, v));
|
|
|
|
if (!BroadcastSystemMessage(type, parameters)) {
|
|
BT_WARNING("Failed to send [bluetooth-opp-receiving-file-confirmation]");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::NotifyAboutFileChange()
|
|
{
|
|
NS_NAMED_LITERAL_STRING(data, "modified");
|
|
|
|
nsCOMPtr<nsIObserverService> obs =
|
|
mozilla::services::GetObserverService();
|
|
NS_ENSURE_TRUE_VOID(obs);
|
|
|
|
obs->NotifyObservers(mDsFile, "file-watcher-notify", data.get());
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnSocketConnectSuccess(BluetoothSocket* aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
|
|
/**
|
|
* If the created connection is an inbound connection, close another server
|
|
* socket because currently only one file-transfer session is allowed. After
|
|
* that, we need to make sure that both server socket would be nulled out.
|
|
* As for outbound connections, we just notify the controller that it's done.
|
|
*/
|
|
if (aSocket == mRfcommSocket) {
|
|
MOZ_ASSERT(!mSocket);
|
|
mRfcommSocket.swap(mSocket);
|
|
|
|
mL2capSocket->Disconnect();
|
|
mL2capSocket = nullptr;
|
|
} else if (aSocket == mL2capSocket) {
|
|
MOZ_ASSERT(!mSocket);
|
|
mL2capSocket.swap(mSocket);
|
|
|
|
mRfcommSocket->Disconnect();
|
|
mRfcommSocket = nullptr;
|
|
}
|
|
|
|
// Cache device address since we can't get socket address when a remote
|
|
// device disconnect with us.
|
|
mSocket->GetAddress(mConnectedDeviceAddress);
|
|
|
|
OnConnect(EmptyString());
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket)
|
|
{
|
|
mRfcommSocket = nullptr;
|
|
mL2capSocket = nullptr;
|
|
|
|
OnConnect(NS_LITERAL_STRING("SocketConnectionError"));
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket)
|
|
{
|
|
MOZ_ASSERT(aSocket);
|
|
|
|
if (aSocket != mSocket) {
|
|
// Do nothing when a listening server socket is closed.
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* It is valid for a bluetooth device which is transfering file via OPP
|
|
* closing socket without sending OBEX disconnect request first. So we
|
|
* delete the broken file when we failed to receive a file from the remote,
|
|
* and notify the transfer has been completed (but failed). We also call
|
|
* AfterOppDisconnected here to ensure all variables will be cleaned.
|
|
*/
|
|
if (!mSuccessFlag) {
|
|
if (mIsServer) {
|
|
DeleteReceivedFile();
|
|
}
|
|
FileTransferComplete();
|
|
}
|
|
|
|
AfterOppDisconnected();
|
|
mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
|
|
mSuccessFlag = false;
|
|
|
|
OnDisconnect(EmptyString());
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::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 (mNeedsUpdatingSdpRecords) {
|
|
mNeedsUpdatingSdpRecords = false;
|
|
bs->UpdateSdpRecords(aDeviceAddress, this);
|
|
} else {
|
|
OnConnect(NS_LITERAL_STRING(ERR_SERVICE_CHANNEL_NOT_FOUND));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!mSocket->Connect(NS_ConvertUTF16toUTF8(aDeviceAddress), aChannel)) {
|
|
OnConnect(NS_LITERAL_STRING("SocketConnectionError"));
|
|
}
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!aDeviceAddress.IsEmpty());
|
|
|
|
BluetoothService* bs = BluetoothService::Get();
|
|
NS_ENSURE_TRUE_VOID(bs);
|
|
|
|
nsString uuid;
|
|
BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
|
|
|
|
if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) {
|
|
OnConnect(NS_LITERAL_STRING(ERR_SERVICE_CHANNEL_NOT_FOUND));
|
|
}
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS1(BluetoothOppManager, nsIObserver)
|
|
|
|
bool
|
|
BluetoothOppManager::AcquireSdcardMountLock()
|
|
{
|
|
nsCOMPtr<nsIVolumeService> volumeSrv =
|
|
do_GetService(NS_VOLUMESERVICE_CONTRACTID);
|
|
NS_ENSURE_TRUE(volumeSrv, false);
|
|
nsresult rv;
|
|
rv = volumeSrv->CreateMountLock(NS_LITERAL_STRING("sdcard"),
|
|
getter_AddRefs(mMountLock));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
BluetoothOppManager::OnConnect(const nsAString& aErrorStr)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
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
|
|
BluetoothOppManager::OnDisconnect(const nsAString& aErrorStr)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
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);
|
|
}
|