Merge b2g-inbound to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-07-10 18:09:42 -04:00
commit ca6a1228e6
54 changed files with 1897 additions and 325 deletions

View File

@ -107,7 +107,7 @@ const Observer = {
};
const AppFrames = this.AppFrames = {
let AppFrames = this.AppFrames = {
list: () => SystemAppProxy.getAppFrames(),

View File

@ -19,8 +19,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="ee6e7320bb83409ebd4685fbd87a8ae033704182"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>

View File

@ -19,8 +19,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="ee6e7320bb83409ebd4685fbd87a8ae033704182"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
@ -121,7 +121,7 @@
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="e8a318f7690092e639ba88891606f4183e846d3f"/>
<project name="device/qcom/common" path="device/qcom/common" revision="34ed8345250bb97262d70a052217a92e83444ede"/>
<project name="device-flame" path="device/t2m/flame" remote="b2g" revision="a928d2ae29244dfec3753ea695e6b98e38e8849a"/>
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="30a441fb7275fc5bc347f84ccb29e977a7eca34e"/>
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="30d40a5636ff28a554f1d8e9d975bfd04c2463d3"/>
<project name="kernel_lk" path="bootable/bootloader/lk" remote="b2g" revision="2b1d8b5b7a760230f4c94c02e733e3929f44253a"/>
<project name="platform_bootable_recovery" path="bootable/recovery" remote="b2g" revision="e81502511cda303c803e63f049574634bc96f9f2"/>
<project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="81c4a859d75d413ad688067829d21b7ba9205f81"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "056dbe15b2aac2b252a119c211a85cb14165aa81",
"revision": "ccd8b59df6230fc0eb5d47e9c617e58ddb4673dd",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,8 +17,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>

View File

@ -15,8 +15,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="ee6e7320bb83409ebd4685fbd87a8ae033704182"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>

View File

@ -17,8 +17,8 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="09642e74e250fbc62db860c808ef188628fca55d"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2580a49ddeb99f4bdaaae6716ea99c9547cb6d9f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>

View File

@ -702,6 +702,7 @@ GK_ATOM(oncut, "oncut")
GK_ATOM(ondatachange, "ondatachange")
GK_ATOM(ondataerror, "ondataerror")
GK_ATOM(ondblclick, "ondblclick")
GK_ATOM(ondeleted, "ondeleted")
GK_ATOM(ondeliverysuccess, "ondeliverysuccess")
GK_ATOM(ondeliveryerror, "ondeliveryerror")
GK_ATOM(ondevicefound, "ondevicefound")

View File

@ -4,9 +4,12 @@
* 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 <sys/socket.h>
#include "base/message_loop.h"
#include "BluetoothInterface.h"
#include "nsAutoPtr.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
BEGIN_BLUETOOTH_NAMESPACE
@ -69,6 +72,40 @@ private:
Arg1 mArg1;
};
template <typename Obj, typename Res,
typename Arg1, typename Arg2, typename Arg3>
class BluetoothInterfaceRunnable3 : public nsRunnable
{
public:
BluetoothInterfaceRunnable3(Obj* aObj,
Res (Obj::*aMethod)(Arg1, Arg2, Arg3),
const Arg1& aArg1, const Arg2& aArg2,
const Arg3& aArg3)
: mObj(aObj)
, mMethod(aMethod)
, mArg1(aArg1)
, mArg2(aArg2)
, mArg3(aArg3)
{
MOZ_ASSERT(mObj);
MOZ_ASSERT(mMethod);
}
NS_METHOD
Run() MOZ_OVERRIDE
{
((*mObj).*mMethod)(mArg1, mArg2, mArg3);
return NS_OK;
}
private:
nsRefPtr<Obj> mObj;
void (Obj::*mMethod)(Arg1, Arg2, Arg3);
Arg1 mArg1;
Arg2 mArg2;
Arg3 mArg3;
};
//
// Socket Interface
//
@ -84,23 +121,432 @@ struct interface_traits<BluetoothSocketInterface>
}
};
bt_status_t
BluetoothSocketInterface::Listen(btsock_type_t aType,
const char* aServiceName,
const uint8_t* aServiceUuid, int aChannel,
int& aSockFd, int aFlags)
typedef
BluetoothInterfaceRunnable1<BluetoothSocketResultHandler, void, int>
BluetoothSocketIntResultRunnable;
typedef
BluetoothInterfaceRunnable3<BluetoothSocketResultHandler,
void, int, const nsAString_internal&, int>
BluetoothSocketIntStringIntResultRunnable;
typedef
BluetoothInterfaceRunnable1<BluetoothSocketResultHandler, void, bt_status_t>
BluetoothSocketErrorRunnable;
static nsresult
DispatchBluetoothSocketResult(BluetoothSocketResultHandler* aRes,
void (BluetoothSocketResultHandler::*aMethod)(int),
int aArg, bt_status_t aStatus)
{
return mInterface->listen(aType, aServiceName, aServiceUuid, aChannel,
&aSockFd, aFlags);
MOZ_ASSERT(aRes);
nsRunnable* runnable;
if (aStatus == BT_STATUS_SUCCESS) {
runnable = new BluetoothSocketIntResultRunnable(aRes, aMethod, aArg);
} else {
runnable = new BluetoothSocketErrorRunnable(aRes,
&BluetoothSocketResultHandler::OnError, aStatus);
}
nsresult rv = NS_DispatchToMainThread(runnable);
if (NS_FAILED(rv)) {
BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
}
return rv;
}
bt_status_t
static nsresult
DispatchBluetoothSocketResult(
BluetoothSocketResultHandler* aRes,
void (BluetoothSocketResultHandler::*aMethod)(int, const nsAString&, int),
int aArg1, const nsAString& aArg2, int aArg3, bt_status_t aStatus)
{
MOZ_ASSERT(aRes);
nsRunnable* runnable;
if (aStatus == BT_STATUS_SUCCESS) {
runnable = new BluetoothSocketIntStringIntResultRunnable(aRes, aMethod,
aArg1, aArg2,
aArg3);
} else {
runnable = new BluetoothSocketErrorRunnable(aRes,
&BluetoothSocketResultHandler::OnError, aStatus);
}
nsresult rv = NS_DispatchToMainThread(runnable);
if (NS_FAILED(rv)) {
BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
}
return rv;
}
void
BluetoothSocketInterface::Listen(btsock_type_t aType,
const char* aServiceName,
const uint8_t* aServiceUuid,
int aChannel, int aFlags,
BluetoothSocketResultHandler* aRes)
{
int fd;
bt_status_t status = mInterface->listen(aType, aServiceName, aServiceUuid,
aChannel, &fd, aFlags);
if (aRes) {
DispatchBluetoothSocketResult(aRes, &BluetoothSocketResultHandler::Listen,
fd, status);
}
}
#define CMSGHDR_CONTAINS_FD(_cmsghdr) \
( ((_cmsghdr)->cmsg_level == SOL_SOCKET) && \
((_cmsghdr)->cmsg_type == SCM_RIGHTS) )
/* |SocketMessageWatcher| receives Bluedroid's socket setup
* messages on the I/O thread. You need to inherit from this
* class to make use of it.
*
* Bluedroid sends two socket info messages (20 bytes) at
* the beginning of a connection to both peers.
*
* - 1st message: [channel:4]
* - 2nd message: [size:2][bd address:6][channel:4][connection status:4]
*
* On the server side, the second message will contain a
* socket file descriptor for the connection. The client
* uses the original file descriptor.
*/
class SocketMessageWatcher : public MessageLoopForIO::Watcher
{
public:
static const unsigned char MSG1_SIZE = 4;
static const unsigned char MSG2_SIZE = 16;
static const unsigned char OFF_CHANNEL1 = 0;
static const unsigned char OFF_SIZE = 4;
static const unsigned char OFF_BDADDRESS = 6;
static const unsigned char OFF_CHANNEL2 = 12;
static const unsigned char OFF_STATUS = 16;
SocketMessageWatcher(int aFd)
: mFd(aFd)
, mClientFd(-1)
, mLen(0)
{ }
virtual ~SocketMessageWatcher()
{ }
virtual void Proceed(bt_status_t aStatus) = 0;
void OnFileCanReadWithoutBlocking(int aFd) MOZ_OVERRIDE
{
bt_status_t status;
switch (mLen) {
case 0:
status = RecvMsg1();
break;
case MSG1_SIZE:
status = RecvMsg2();
break;
default:
/* message-size error */
status = BT_STATUS_FAIL;
break;
}
if (IsComplete() || status != BT_STATUS_SUCCESS) {
mWatcher.StopWatchingFileDescriptor();
Proceed(status);
}
}
void OnFileCanWriteWithoutBlocking(int aFd) MOZ_OVERRIDE
{ }
void Watch()
{
MessageLoopForIO::current()->WatchFileDescriptor(
mFd,
true,
MessageLoopForIO::WATCH_READ,
&mWatcher,
this);
}
bool IsComplete() const
{
return mLen == (MSG1_SIZE + MSG2_SIZE);
}
int GetFd() const
{
return mFd;
}
int32_t GetChannel1() const
{
return ReadInt32(OFF_CHANNEL1);
}
int32_t GetSize() const
{
return ReadInt16(OFF_SIZE);
}
nsString GetBdAddress() const
{
nsString bdAddress;
ReadBdAddress(OFF_BDADDRESS, bdAddress);
return bdAddress;
}
int32_t GetChannel2() const
{
return ReadInt32(OFF_CHANNEL2);
}
int32_t GetConnectionStatus() const
{
return ReadInt32(OFF_STATUS);
}
int GetClientFd() const
{
return mClientFd;
}
private:
bt_status_t RecvMsg1()
{
struct iovec iv;
memset(&iv, 0, sizeof(iv));
iv.iov_base = mBuf;
iv.iov_len = MSG1_SIZE;
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iv;
msg.msg_iovlen = 1;
ssize_t res = TEMP_FAILURE_RETRY(recvmsg(mFd, &msg, MSG_NOSIGNAL));
if (res < 0) {
return BT_STATUS_FAIL;
}
mLen += res;
return BT_STATUS_SUCCESS;
}
bt_status_t RecvMsg2()
{
struct iovec iv;
memset(&iv, 0, sizeof(iv));
iv.iov_base = mBuf + MSG1_SIZE;
iv.iov_len = MSG2_SIZE;
struct msghdr msg;
struct cmsghdr cmsgbuf[2 * sizeof(cmsghdr) + 0x100];
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iv;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
ssize_t res = TEMP_FAILURE_RETRY(recvmsg(mFd, &msg, MSG_NOSIGNAL));
if (res < 0) {
return BT_STATUS_FAIL;
}
mLen += res;
if (msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE)) {
return BT_STATUS_FAIL;
}
struct cmsghdr *cmsgptr = CMSG_FIRSTHDR(&msg);
// Extract client fd from message header
for (; cmsgptr; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
if (CMSGHDR_CONTAINS_FD(cmsgptr)) {
// if multiple file descriptors have been sent, we close
// all but the final one.
if (mClientFd != -1) {
TEMP_FAILURE_RETRY(close(mClientFd));
}
// retrieve sent client fd
mClientFd = *(static_cast<int*>(CMSG_DATA(cmsgptr)));
}
}
return BT_STATUS_SUCCESS;
}
int16_t ReadInt16(unsigned long aOffset) const
{
/* little-endian buffer */
return (static_cast<int16_t>(mBuf[aOffset + 1]) << 8) |
static_cast<int16_t>(mBuf[aOffset]);
}
int32_t ReadInt32(unsigned long aOffset) const
{
/* little-endian buffer */
return (static_cast<int32_t>(mBuf[aOffset + 3]) << 24) |
(static_cast<int32_t>(mBuf[aOffset + 2]) << 16) |
(static_cast<int32_t>(mBuf[aOffset + 1]) << 8) |
static_cast<int32_t>(mBuf[aOffset]);
}
void ReadBdAddress(unsigned long aOffset, nsAString& aBdAddress) const
{
char str[18];
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
mBuf[aOffset + 0], mBuf[aOffset + 1], mBuf[aOffset + 2],
mBuf[aOffset + 3], mBuf[aOffset + 4], mBuf[aOffset + 5]);
aBdAddress.AssignLiteral(str);
}
MessageLoopForIO::FileDescriptorWatcher mWatcher;
int mFd;
int mClientFd;
unsigned char mLen;
uint8_t mBuf[MSG1_SIZE + MSG2_SIZE];
};
/* |SocketMessageWatcherTask| starts a SocketMessageWatcher
* on the I/O task
*/
class SocketMessageWatcherTask MOZ_FINAL : public Task
{
public:
SocketMessageWatcherTask(SocketMessageWatcher* aWatcher)
: mWatcher(aWatcher)
{
MOZ_ASSERT(mWatcher);
}
void Run() MOZ_OVERRIDE
{
mWatcher->Watch();
}
private:
SocketMessageWatcher* mWatcher;
};
/* |DeleteTask| deletes a class instance on the I/O thread
*/
template <typename T>
class DeleteTask MOZ_FINAL : public Task
{
public:
DeleteTask(T* aPtr)
: mPtr(aPtr)
{ }
void Run() MOZ_OVERRIDE
{
mPtr = nullptr;
}
private:
nsAutoPtr<T> mPtr;
};
/* |ConnectWatcher| specializes SocketMessageWatcher for
* connect operations by reading the socket messages from
* Bluedroid and forwarding the connected socket to the
* resource handler.
*/
class ConnectWatcher MOZ_FINAL : public SocketMessageWatcher
{
public:
ConnectWatcher(int aFd, BluetoothSocketResultHandler* aRes)
: SocketMessageWatcher(aFd)
, mRes(aRes)
{ }
void Proceed(bt_status_t aStatus) MOZ_OVERRIDE
{
if (mRes) {
DispatchBluetoothSocketResult(mRes,
&BluetoothSocketResultHandler::Connect,
GetFd(), GetBdAddress(),
GetConnectionStatus(), aStatus);
}
MessageLoopForIO::current()->PostTask(
FROM_HERE, new DeleteTask<ConnectWatcher>(this));
}
private:
nsRefPtr<BluetoothSocketResultHandler> mRes;
};
void
BluetoothSocketInterface::Connect(const bt_bdaddr_t* aBdAddr,
btsock_type_t aType, const uint8_t* aUuid,
int aChannel, int& aSockFd, int aFlags)
int aChannel, int aFlags,
BluetoothSocketResultHandler* aRes)
{
return mInterface->connect(aBdAddr, aType, aUuid, aChannel, &aSockFd,
aFlags);
int fd;
bt_status_t status = mInterface->connect(aBdAddr, aType, aUuid, aChannel,
&fd, aFlags);
if (status == BT_STATUS_SUCCESS) {
/* receive Bluedroid's socket-setup messages */
Task* t = new SocketMessageWatcherTask(new ConnectWatcher(fd, aRes));
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t);
} else if (aRes) {
DispatchBluetoothSocketResult(aRes,
&BluetoothSocketResultHandler::Connect,
-1, EmptyString(), 0, status);
}
}
/* Specializes SocketMessageWatcher for Accept operations by
* reading the socket messages from Bluedroid and forwarding
* the received client socket to the resource handler. The
* first message is received immediately. When there's a new
* connection, Bluedroid sends the 2nd message with the socket
* info and socket file descriptor.
*/
class AcceptWatcher MOZ_FINAL : public SocketMessageWatcher
{
public:
AcceptWatcher(int aFd, BluetoothSocketResultHandler* aRes)
: SocketMessageWatcher(aFd)
, mRes(aRes)
{
/* not supplying a result handler leaks received file descriptor */
MOZ_ASSERT(mRes);
}
void Proceed(bt_status_t aStatus) MOZ_OVERRIDE
{
if (mRes) {
DispatchBluetoothSocketResult(mRes,
&BluetoothSocketResultHandler::Accept,
GetClientFd(), GetBdAddress(),
GetConnectionStatus(),
aStatus);
}
MessageLoopForIO::current()->PostTask(
FROM_HERE, new DeleteTask<AcceptWatcher>(this));
}
private:
nsRefPtr<BluetoothSocketResultHandler> mRes;
};
void
BluetoothSocketInterface::Accept(int aFd, BluetoothSocketResultHandler* aRes)
{
/* receive Bluedroid's socket-setup messages and client fd */
Task* t = new SocketMessageWatcherTask(new AcceptWatcher(aFd, aRes));
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t);
}
BluetoothSocketInterface::BluetoothSocketInterface(

View File

@ -24,6 +24,25 @@ class BluetoothInterface;
// Socket Interface
//
class BluetoothSocketResultHandler
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothSocketResultHandler)
virtual ~BluetoothSocketResultHandler() { }
virtual void OnError(bt_status_t aStatus)
{
BT_WARNING("received error code %d", (int)aStatus);
}
virtual void Listen(int aSockFd) { }
virtual void Connect(int aSockFd, const nsAString& aBdAddress,
int aConnectionState) { }
virtual void Accept(int aSockFd, const nsAString& aBdAddress,
int aConnectionState) { }
};
class BluetoothSocketInterface
{
public:
@ -31,13 +50,15 @@ public:
// Init and Cleanup is handled by BluetoothInterface
bt_status_t Listen(btsock_type_t aType,
const char* aServiceName, const uint8_t* aServiceUuid,
int aChannel, int& aSockFd, int aFlags);
void Listen(btsock_type_t aType,
const char* aServiceName, const uint8_t* aServiceUuid,
int aChannel, int aFlags, BluetoothSocketResultHandler* aRes);
bt_status_t Connect(const bt_bdaddr_t* aBdAddr, btsock_type_t aType,
const uint8_t* aUuid, int aChannel, int& aSockFd,
int aFlags);
void Connect(const bt_bdaddr_t* aBdAddr, btsock_type_t aType,
const uint8_t* aUuid, int aChannel, int aFlags,
BluetoothSocketResultHandler* aRes);
void Accept(int aFd, BluetoothSocketResultHandler* aRes);
protected:
BluetoothSocketInterface(const btsock_interface_t* aInterface);

View File

@ -18,9 +18,6 @@
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#define FIRST_SOCKET_INFO_MSG_LENGTH 4
#define TOTAL_SOCKET_INFO_LENGTH 20
using namespace mozilla::ipc;
USING_BLUETOOTH_NAMESPACE
@ -48,61 +45,55 @@ EnsureBluetoothSocketHalLoad()
return true;
}
static int16_t
ReadInt16(const uint8_t* aData, size_t* aOffset)
{
int16_t value = (aData[*aOffset + 1] << 8) | aData[*aOffset];
*aOffset += 2;
return value;
}
static int32_t
ReadInt32(const uint8_t* aData, size_t* aOffset)
{
int32_t value = (aData[*aOffset + 3] << 24) |
(aData[*aOffset + 2] << 16) |
(aData[*aOffset + 1] << 8) |
aData[*aOffset];
*aOffset += 4;
return value;
}
static void
ReadBdAddress(const uint8_t* aData, size_t* aOffset, nsAString& aDeviceAddress)
{
char bdstr[18];
sprintf(bdstr, "%02x:%02x:%02x:%02x:%02x:%02x",
aData[*aOffset], aData[*aOffset + 1], aData[*aOffset + 2],
aData[*aOffset + 3], aData[*aOffset + 4], aData[*aOffset + 5]);
aDeviceAddress.AssignLiteral(bdstr);
*aOffset += 6;
}
class mozilla::dom::bluetooth::DroidSocketImpl : public ipc::UnixFdWatcher
{
public:
/* The connection status in DroidSocketImpl indicates the current
* phase of the socket connection. The initial settign should always
* be DISCONNECTED, when no connection is present.
*
* To establish a connection on the server, DroidSocketImpl moves
* to LISTENING. It now waits for incoming connection attempts by
* installing a read watcher on the I/O thread. When its socket file
* descriptor becomes readable, DroidSocketImpl accepts the connection
* and finally moves DroidSocketImpl to CONNECTED. DroidSocketImpl now
* uses read and write watchers during data transfers. Any socket setup
* is handled internally by the accept method.
*
* On the client side, DroidSocketImpl moves to CONNECTING and installs
* a write watcher on the I/O thread to wait until the connection is
* ready. The socket setup is handled internally by the connect method.
* Installing the write handler makes the code compatible with POSIX
* semantics for non-blocking connects and gives a clear signal when the
* conncetion is ready. DroidSocketImpl then moves to CONNECTED and uses
* read and write watchers during data transfers.
*/
enum ConnectionStatus {
SOCKET_IS_DISCONNECTED = 0,
SOCKET_IS_LISTENING,
SOCKET_IS_CONNECTING,
SOCKET_IS_CONNECTED
};
DroidSocketImpl(MessageLoop* aIOLoop, BluetoothSocket* aConsumer, int aFd)
: ipc::UnixFdWatcher(aIOLoop, aFd)
, mConsumer(aConsumer)
, mReadMsgForClientFd(false)
, mShuttingDownOnIOThread(false)
, mChannel(0)
, mAuth(false)
, mEncrypt(false)
{
}
, mConnectionStatus(SOCKET_IS_DISCONNECTED)
{ }
DroidSocketImpl(MessageLoop* aIOLoop, BluetoothSocket* aConsumer,
int aChannel, bool aAuth, bool aEncrypt)
: ipc::UnixFdWatcher(aIOLoop)
, mConsumer(aConsumer)
, mReadMsgForClientFd(false)
, mShuttingDownOnIOThread(false)
, mChannel(aChannel)
, mAuth(aAuth)
, mEncrypt(aEncrypt)
, mConnectionStatus(SOCKET_IS_DISCONNECTED)
{ }
DroidSocketImpl(MessageLoop* aIOLoop, BluetoothSocket* aConsumer,
@ -110,12 +101,12 @@ public:
int aChannel, bool aAuth, bool aEncrypt)
: ipc::UnixFdWatcher(aIOLoop)
, mConsumer(aConsumer)
, mReadMsgForClientFd(false)
, mShuttingDownOnIOThread(false)
, mDeviceAddress(aDeviceAddress)
, mChannel(aChannel)
, mAuth(aAuth)
, mEncrypt(aEncrypt)
, mConnectionStatus(SOCKET_IS_DISCONNECTED)
{
MOZ_ASSERT(!mDeviceAddress.IsEmpty());
}
@ -159,24 +150,20 @@ public:
mShuttingDownOnIOThread = true;
}
void Connect();
void Listen();
void SetUpIO(bool aWrite)
{
AddWatchers(READ_WATCHER, true);
if (aWrite) {
AddWatchers(WRITE_WATCHER, false);
}
}
void Connect(int aFd);
void Listen(int aFd);
void Accept(int aFd);
void ConnectClientFd()
{
// Stop current read watch
RemoveWatchers(READ_WATCHER);
mConnectionStatus = SOCKET_IS_CONNECTED;
// Restart read & write watch on client fd
SetUpIO(true);
AddWatchers(READ_WATCHER, true);
AddWatchers(WRITE_WATCHER, false);
}
/**
@ -186,11 +173,6 @@ public:
*/
RefPtr<BluetoothSocket> mConsumer;
/**
* If true, read message header to get client fd.
*/
bool mReadMsgForClientFd;
private:
/**
* libevent triggered functions that reads data from socket when available and
@ -208,14 +190,10 @@ private:
*/
virtual void OnFileCanWriteWithoutBlocking(int aFd);
/**
* Read message to get data and client fd wrapped in message header
*
* @param aFd [in] File descriptor to read message from
* @param aBuffer [out] Data buffer read
* @param aLength [out] Number of bytes read
*/
ssize_t ReadMsg(int aFd, void *aBuffer, size_t aLength);
void OnSocketCanReceiveWithoutBlocking(int aFd);
void OnSocketCanAcceptWithoutBlocking(int aFd);
void OnSocketCanSendWithoutBlocking(int aFd);
void OnSocketCanConnectWithoutBlocking(int aFd);
/**
* Raw data queue. Must be pushed/popped from IO thread only.
@ -232,6 +210,7 @@ private:
int mChannel;
bool mAuth;
bool mEncrypt;
ConnectionStatus mConnectionStatus;
};
template<class T>
@ -253,6 +232,70 @@ private:
T* mInstance;
};
class DroidSocketImplRunnable : public nsRunnable
{
public:
DroidSocketImpl* GetImpl() const
{
return mImpl;
}
protected:
DroidSocketImplRunnable(DroidSocketImpl* aImpl)
: mImpl(aImpl)
{
MOZ_ASSERT(aImpl);
}
virtual ~DroidSocketImplRunnable()
{ }
private:
DroidSocketImpl* mImpl;
};
class OnSocketEventRunnable : public DroidSocketImplRunnable
{
public:
enum SocketEvent {
CONNECT_SUCCESS,
CONNECT_ERROR,
DISCONNECT
};
OnSocketEventRunnable(DroidSocketImpl* aImpl, SocketEvent e)
: DroidSocketImplRunnable(aImpl)
, mEvent(e)
{
MOZ_ASSERT(!NS_IsMainThread());
}
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
DroidSocketImpl* impl = GetImpl();
if (impl->IsShutdownOnMainThread()) {
NS_WARNING("CloseSocket has already been called!");
// Since we've already explicitly closed and the close happened before
// this, this isn't really an error. Since we've warned, return OK.
return NS_OK;
}
if (mEvent == CONNECT_SUCCESS) {
impl->mConsumer->NotifySuccess();
} else if (mEvent == CONNECT_ERROR) {
impl->mConsumer->NotifyError();
} else if (mEvent == DISCONNECT) {
impl->mConsumer->NotifyDisconnect();
}
return NS_OK;
}
private:
SocketEvent mEvent;
};
class RequestClosingSocketTask : public nsRunnable
{
public:
@ -392,32 +435,40 @@ private:
class SocketConnectTask : public DroidSocketImplTask
{
public:
SocketConnectTask(DroidSocketImpl* aDroidSocketImpl)
SocketConnectTask(DroidSocketImpl* aDroidSocketImpl, int aFd)
: DroidSocketImplTask(aDroidSocketImpl)
, mFd(aFd)
{ }
void Run() MOZ_OVERRIDE
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsCanceled());
GetDroidSocketImpl()->Connect();
GetDroidSocketImpl()->Connect(mFd);
}
private:
int mFd;
};
class SocketListenTask : public DroidSocketImplTask
{
public:
SocketListenTask(DroidSocketImpl* aDroidSocketImpl)
SocketListenTask(DroidSocketImpl* aDroidSocketImpl, int aFd)
: DroidSocketImplTask(aDroidSocketImpl)
, mFd(aFd)
{ }
void Run() MOZ_OVERRIDE
{
MOZ_ASSERT(!NS_IsMainThread());
if (!IsCanceled()) {
GetDroidSocketImpl()->Listen();
GetDroidSocketImpl()->Listen(mFd);
}
}
private:
int mFd;
};
class SocketConnectClientFdTask : public Task
@ -434,127 +485,83 @@ public:
};
void
DroidSocketImpl::Connect()
DroidSocketImpl::Connect(int aFd)
{
MOZ_ASSERT(sBluetoothSocketInterface);
MOZ_ASSERT(aFd >= 0);
bt_bdaddr_t remoteBdAddress;
StringToBdAddressType(mDeviceAddress, &remoteBdAddress);
// TODO: uuid as argument
int fd = -1;
bt_status_t status =
sBluetoothSocketInterface->Connect(&remoteBdAddress,
BTSOCK_RFCOMM,
UUID_OBEX_OBJECT_PUSH,
mChannel,
fd,
(BTSOCK_FLAG_ENCRYPT * mEncrypt) |
(BTSOCK_FLAG_AUTH * mAuth));
NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS);
NS_ENSURE_TRUE_VOID(fd >= 0);
int flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
int flags = TEMP_FAILURE_RETRY(fcntl(aFd, F_GETFL));
NS_ENSURE_TRUE_VOID(flags >= 0);
if (!(flags & O_NONBLOCK)) {
int res = TEMP_FAILURE_RETRY(fcntl(fd, F_SETFL, flags | O_NONBLOCK));
int res = TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags | O_NONBLOCK));
NS_ENSURE_TRUE_VOID(!res);
}
SetFd(fd);
SetFd(aFd);
mConnectionStatus = SOCKET_IS_CONNECTING;
AddWatchers(READ_WATCHER, true);
AddWatchers(WRITE_WATCHER, false);
}
void
DroidSocketImpl::Listen()
DroidSocketImpl::Listen(int aFd)
{
MOZ_ASSERT(sBluetoothSocketInterface);
MOZ_ASSERT(aFd >= 0);
// TODO: uuid and service name as arguments
int fd = -1;
bt_status_t status =
sBluetoothSocketInterface->Listen(BTSOCK_RFCOMM,
"OBEX Object Push",
UUID_OBEX_OBJECT_PUSH,
mChannel,
fd,
(BTSOCK_FLAG_ENCRYPT * mEncrypt) |
(BTSOCK_FLAG_AUTH * mAuth));
NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS);
NS_ENSURE_TRUE_VOID(fd >= 0);
int flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
int flags = TEMP_FAILURE_RETRY(fcntl(aFd, F_GETFL));
NS_ENSURE_TRUE_VOID(flags >= 0);
if (!(flags & O_NONBLOCK)) {
int res = TEMP_FAILURE_RETRY(fcntl(fd, F_SETFL, flags | O_NONBLOCK));
int res = TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags | O_NONBLOCK));
NS_ENSURE_TRUE_VOID(!res);
}
SetFd(fd);
SetFd(aFd);
mConnectionStatus = SOCKET_IS_LISTENING;
AddWatchers(READ_WATCHER, true);
}
ssize_t
DroidSocketImpl::ReadMsg(int aFd, void *aBuffer, size_t aLength)
void
DroidSocketImpl::Accept(int aFd)
{
ssize_t ret;
struct msghdr msg;
struct iovec iv;
struct cmsghdr cmsgbuf[2 * sizeof(cmsghdr) + 0x100];
Close();
memset(&msg, 0, sizeof(msg));
memset(&iv, 0, sizeof(iv));
int flags = TEMP_FAILURE_RETRY(fcntl(aFd, F_GETFL));
NS_ENSURE_TRUE_VOID(flags >= 0);
iv.iov_base = (unsigned char *)aBuffer;
iv.iov_len = aLength;
msg.msg_iov = &iv;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
ret = recvmsg(GetFd(), &msg, MSG_NOSIGNAL);
if (ret < 0 && errno == EPIPE) {
// Treat this as an end of stream
return 0;
if (!(flags & O_NONBLOCK)) {
int res = TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags | O_NONBLOCK));
NS_ENSURE_TRUE_VOID(!res);
}
NS_ENSURE_FALSE(ret < 0, -1);
NS_ENSURE_FALSE(msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE), -1);
SetFd(aFd);
mConnectionStatus = SOCKET_IS_CONNECTED;
// Extract client fd from message header
for (struct cmsghdr *cmsgptr = CMSG_FIRSTHDR(&msg);
cmsgptr != nullptr; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
if (cmsgptr->cmsg_level != SOL_SOCKET) {
continue;
}
if (cmsgptr->cmsg_type == SCM_RIGHTS) {
int *pDescriptors = (int *)CMSG_DATA(cmsgptr);
nsRefPtr<OnSocketEventRunnable> r =
new OnSocketEventRunnable(this, OnSocketEventRunnable::CONNECT_SUCCESS);
NS_DispatchToMainThread(r);
// Overwrite fd with client fd
int fd = pDescriptors[0];
int flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
NS_ENSURE_TRUE(flags >= 0, 0);
if (!(flags & O_NONBLOCK)) {
int res = TEMP_FAILURE_RETRY(fcntl(fd, F_SETFL, flags | O_NONBLOCK));
NS_ENSURE_TRUE(!res, 0);
}
Close();
SetFd(fd);
break;
}
AddWatchers(READ_WATCHER, true);
if (!mOutgoingQ.IsEmpty()) {
AddWatchers(WRITE_WATCHER, false);
}
return ret;
}
void
DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
{
if (mConnectionStatus == SOCKET_IS_CONNECTED) {
OnSocketCanReceiveWithoutBlocking(aFd);
} else if (mConnectionStatus == SOCKET_IS_LISTENING) {
OnSocketCanAcceptWithoutBlocking(aFd);
} else {
NS_NOTREACHED("invalid connection state for reading");
}
}
void
DroidSocketImpl::OnSocketCanReceiveWithoutBlocking(int aFd)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!mShuttingDownOnIOThread);
@ -563,12 +570,7 @@ DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
while (true) {
nsAutoPtr<UnixSocketRawData> incoming(new UnixSocketRawData(MAX_READ_SIZE));
ssize_t ret;
if (!mReadMsgForClientFd) {
ret = read(aFd, incoming->mData, incoming->mSize);
} else {
ret = ReadMsg(aFd, incoming->mData, incoming->mSize);
}
ssize_t ret = read(aFd, incoming->mData, incoming->mSize);
if (ret <= 0) {
if (ret == -1) {
@ -606,8 +608,113 @@ DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
MOZ_CRASH("We returned early");
}
class AcceptTask MOZ_FINAL : public DroidSocketImplTask
{
public:
AcceptTask(DroidSocketImpl* aDroidSocketImpl, int aFd)
: DroidSocketImplTask(aDroidSocketImpl)
, mFd(aFd)
{ }
void Run() MOZ_OVERRIDE
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsCanceled());
GetDroidSocketImpl()->Accept(mFd);
}
private:
int mFd;
};
class AcceptResultHandler MOZ_FINAL : public BluetoothSocketResultHandler
{
public:
AcceptResultHandler(DroidSocketImpl* aImpl)
: mImpl(aImpl)
{
MOZ_ASSERT(mImpl);
}
void Accept(int aFd, const nsAString& aBdAddress,
int aConnectionStatus) MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
if (mImpl->IsShutdownOnMainThread()) {
BT_LOGD("mConsumer is null, aborting receive!");
return;
}
mImpl->mConsumer->SetAddress(aBdAddress);
XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new AcceptTask(mImpl, aFd));
}
void OnError(bt_status_t aStatus) MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
BT_LOGR("BluetoothSocketInterface::Accept failed: %d", (int)aStatus);
}
private:
DroidSocketImpl* mImpl;
};
class AcceptRunnable MOZ_FINAL : public nsRunnable
{
public:
AcceptRunnable(int aFd, DroidSocketImpl* aImpl)
: mFd(aFd)
, mImpl(aImpl)
{
MOZ_ASSERT(mImpl);
}
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(sBluetoothSocketInterface);
sBluetoothSocketInterface->Accept(mFd, new AcceptResultHandler(mImpl));
return NS_OK;
}
private:
int mFd;
DroidSocketImpl* mImpl;
};
void
DroidSocketImpl::OnSocketCanAcceptWithoutBlocking(int aFd)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!mShuttingDownOnIOThread);
/* When a listening socket is ready for receiving data,
* we can call |Accept| on it.
*/
RemoveWatchers(READ_WATCHER);
nsRefPtr<AcceptRunnable> t = new AcceptRunnable(aFd, this);
NS_DispatchToMainThread(t);
}
void
DroidSocketImpl::OnFileCanWriteWithoutBlocking(int aFd)
{
if (mConnectionStatus == SOCKET_IS_CONNECTED) {
OnSocketCanSendWithoutBlocking(aFd);
} else if (mConnectionStatus == SOCKET_IS_CONNECTING) {
OnSocketCanConnectWithoutBlocking(aFd);
} else {
NS_NOTREACHED("invalid connection state for writing");
}
}
void
DroidSocketImpl::OnSocketCanSendWithoutBlocking(int aFd)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!mShuttingDownOnIOThread);
@ -649,6 +756,28 @@ DroidSocketImpl::OnFileCanWriteWithoutBlocking(int aFd)
}
}
void
DroidSocketImpl::OnSocketCanConnectWithoutBlocking(int aFd)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!mShuttingDownOnIOThread);
/* We follow Posix behaviour here: Connect operations are
* complete once we can write to the connecting socket.
*/
mConnectionStatus = SOCKET_IS_CONNECTED;
nsRefPtr<OnSocketEventRunnable> r =
new OnSocketEventRunnable(this, OnSocketEventRunnable::CONNECT_SUCCESS);
NS_DispatchToMainThread(r);
AddWatchers(READ_WATCHER, true);
if (!mOutgoingQ.IsEmpty()) {
AddWatchers(WRITE_WATCHER, false);
}
}
BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
BluetoothSocketType aType,
bool aAuth,
@ -657,7 +786,6 @@ BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
, mImpl(nullptr)
, mAuth(aAuth)
, mEncrypt(aEncrypt)
, mReceivedSocketInfoLength(0)
{
MOZ_ASSERT(aObserver);
@ -681,35 +809,107 @@ BluetoothSocket::CloseDroidSocket()
new ShutdownSocketTask(mImpl));
mImpl = nullptr;
OnDisconnect();
NotifyDisconnect();
}
class ConnectResultHandler MOZ_FINAL : public BluetoothSocketResultHandler
{
public:
ConnectResultHandler(DroidSocketImpl* aImpl)
: mImpl(aImpl)
{
MOZ_ASSERT(mImpl);
}
void Connect(int aFd, const nsAString& aBdAddress,
int aConnectionStatus) MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
if (!mImpl->IsShutdownOnMainThread()) {
mImpl->mConsumer->SetAddress(aBdAddress);
}
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
new SocketConnectTask(mImpl, aFd));
}
void OnError(bt_status_t aStatus) MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("Connect failed: %d", (int)aStatus);
}
private:
DroidSocketImpl* mImpl;
};
bool
BluetoothSocket::Connect(const nsAString& aDeviceAddress, int aChannel)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_FALSE(mImpl, false);
mIsServer = false;
mImpl = new DroidSocketImpl(XRE_GetIOMessageLoop(), this, aDeviceAddress,
aChannel, mAuth, mEncrypt);
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
new SocketConnectTask(mImpl));
bt_bdaddr_t remoteBdAddress;
StringToBdAddressType(aDeviceAddress, &remoteBdAddress);
// TODO: uuid as argument
sBluetoothSocketInterface->Connect(&remoteBdAddress,
BTSOCK_RFCOMM,
UUID_OBEX_OBJECT_PUSH,
aChannel,
(BTSOCK_FLAG_ENCRYPT * mEncrypt) |
(BTSOCK_FLAG_AUTH * mAuth),
new ConnectResultHandler(mImpl));
return true;
}
class ListenResultHandler MOZ_FINAL : public BluetoothSocketResultHandler
{
public:
ListenResultHandler(DroidSocketImpl* aImpl)
: mImpl(aImpl)
{
MOZ_ASSERT(mImpl);
}
void Listen(int aFd) MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
new SocketListenTask(mImpl, aFd));
}
void OnError(bt_status_t aStatus) MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("Listen failed: %d", (int)aStatus);
}
private:
DroidSocketImpl* mImpl;
};
bool
BluetoothSocket::Listen(int aChannel)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_FALSE(mImpl, false);
mIsServer = true;
mImpl = new DroidSocketImpl(XRE_GetIOMessageLoop(), this, aChannel, mAuth,
mEncrypt);
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
new SocketListenTask(mImpl));
sBluetoothSocketInterface->Listen(BTSOCK_RFCOMM,
"OBEX Object Push",
UUID_OBEX_OBJECT_PUSH,
aChannel,
(BTSOCK_FLAG_ENCRYPT * mEncrypt) |
(BTSOCK_FLAG_AUTH * mAuth),
new ListenResultHandler(mImpl));
return true;
}
@ -725,64 +925,9 @@ BluetoothSocket::SendDroidSocketData(UnixSocketRawData* aData)
return true;
}
bool
BluetoothSocket::ReceiveSocketInfo(nsAutoPtr<UnixSocketRawData>& aMessage)
{
MOZ_ASSERT(NS_IsMainThread());
/**
* 2 socket info messages (20 bytes) to receive at the beginning:
* - 1st message: [channel:4]
* - 2nd message: [size:2][bd address:6][channel:4][connection status:4]
*/
if (mReceivedSocketInfoLength >= TOTAL_SOCKET_INFO_LENGTH) {
// We've got both socket info messages
return false;
}
mReceivedSocketInfoLength += aMessage->mSize;
size_t offset = 0;
if (mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH) {
// 1st message: [channel:4]
int32_t channel = ReadInt32(aMessage->mData, &offset);
BT_LOGR("channel %d", channel);
// If this is server socket, read header of next message for client fd
mImpl->mReadMsgForClientFd = mIsServer;
} else if (mReceivedSocketInfoLength == TOTAL_SOCKET_INFO_LENGTH) {
// 2nd message: [size:2][bd address:6][channel:4][connection status:4]
int16_t size = ReadInt16(aMessage->mData, &offset);
ReadBdAddress(aMessage->mData, &offset, mDeviceAddress);
int32_t channel = ReadInt32(aMessage->mData, &offset);
int32_t connectionStatus = ReadInt32(aMessage->mData, &offset);
BT_LOGR("size %d channel %d remote addr %s status %d",
size, channel, NS_ConvertUTF16toUTF8(mDeviceAddress).get(), connectionStatus);
if (connectionStatus != 0) {
OnConnectError();
return true;
}
if (mIsServer) {
mImpl->mReadMsgForClientFd = false;
// Connect client fd on IO thread
XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
new SocketConnectClientFdTask(mImpl));
}
OnConnectSuccess();
}
return true;
}
void
BluetoothSocket::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
{
if (ReceiveSocketInfo(aMessage)) {
return;
}
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mObserver);
mObserver->ReceiveSocketData(this, aMessage);
@ -811,4 +956,3 @@ BluetoothSocket::OnDisconnect()
MOZ_ASSERT(mObserver);
mObserver->OnSocketDisconnect(this);
}

View File

@ -23,30 +23,8 @@ public:
bool aAuth,
bool aEncrypt);
/**
* Connect to remote server as a client.
*
* The steps are as following:
* 1) BluetoothSocket acquires fd from bluedroid, and creates
* a DroidSocketImpl to watch read/write of the fd.
* 2) DroidSocketImpl receives first 2 messages to get socket info.
* 3) Obex client session starts.
*/
bool Connect(const nsAString& aDeviceAddress, int aChannel);
/**
* Listen to incoming connection as a server.
*
* The steps are as following:
* 1) BluetoothSocket acquires fd from bluedroid, and creates
* a DroidSocketImpl to watch read of the fd. DroidSocketImpl
* receives the 1st message immediately.
* 2) When there's incoming connection, DroidSocketImpl receives
* 2nd message to get socket info and client fd.
* 3) DroidSocketImpl stops watching read of original fd and
* starts to watch read/write of client fd.
* 4) Obex server session starts.
*/
bool Listen(int aChannel);
inline void Disconnect()
@ -65,6 +43,11 @@ public:
aDeviceAddress = mDeviceAddress;
}
inline void SetAddress(const nsAString& aDeviceAddress)
{
mDeviceAddress = aDeviceAddress;
}
void CloseDroidSocket();
bool SendDroidSocketData(mozilla::ipc::UnixSocketRawData* aData);
@ -74,10 +57,6 @@ private:
nsString mDeviceAddress;
bool mAuth;
bool mEncrypt;
bool mIsServer;
int mReceivedSocketInfoLength;
bool ReceiveSocketInfo(nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage);
};
END_BLUETOOTH_NAMESPACE

View File

@ -249,6 +249,10 @@ const kEventConstructors = {
return new MozEmergencyCbModeEvent(aName, aProps);
},
},
MozMessageDeletedEvent: { create: function (aName, aProps) {
return new MozMessageDeletedEvent(aName, aProps);
},
},
MozMmsEvent: { create: function (aName, aProps) {
return new MozMmsEvent(aName, aProps);
},

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
'nsIDeletedMessageInfo.idl',
'nsIDOMMozMmsMessage.idl',
'nsIDOMMozMobileMessageThread.idl',
'nsIDOMMozSmsMessage.idl',

View File

@ -0,0 +1,17 @@
/* 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 "nsISupports.idl"
interface nsIVariant;
[scriptable, builtinclass, uuid(174139d8-00e1-11e4-818a-f38357d90920)]
interface nsIDeletedMessageInfo : nsISupports
{
/* Array of messages ids in long if available. */
readonly attribute nsIVariant deletedMessageIds;
/* Array of thread ids in unsigned long long. if available */
readonly attribute nsIVariant deletedThreadIds;
};

View File

@ -4,6 +4,7 @@
#include "nsISupports.idl"
interface nsIDeletedMessageInfo;
interface nsIDOMMozSmsMessage;
interface nsIDOMMozMmsMessage;
interface nsIDOMMozMobileMessageThread;
@ -14,7 +15,7 @@ interface nsIDOMMozSmsSegmentInfo;
#define MOBILE_MESSAGE_SERVICE_CONTRACTID "@mozilla.org/mobilemessage/mobilemessageservice;1"
%}
[scriptable, builtinclass, uuid(17fce9e4-af56-11e3-83d9-b71055e95493)]
[scriptable, builtinclass, uuid(afb83948-003a-11e4-899b-bb8a8d45e7f0)]
interface nsIMobileMessageService : nsISupports
{
[implicit_jscontext]
@ -61,4 +62,10 @@ interface nsIMobileMessageService : nsISupports
in DOMString body,
in unsigned long long unreadCount,
in DOMString aLastMessageType);
nsIDeletedMessageInfo createDeletedMessageInfo(
[array, size_is(msgCount)] in long messageIds,
in uint32_t msgCount,
[array, size_is(threadCount)] in unsigned long long threadIds,
in uint32_t threadCount);
};

View File

@ -17,6 +17,7 @@ const char* kSmsDeliveryErrorObserverTopic = "sms-delivery-error";
const char* kSilentSmsReceivedObserverTopic = "silent-sms-received";
const char* kSmsReadSuccessObserverTopic = "sms-read-success";
const char* kSmsReadErrorObserverTopic = "sms-read-error";
const char* kSmsDeletedObserverTopic = "sms-deleted";
} // namespace mobilemessage
} // namespace dom

View File

@ -21,6 +21,7 @@ extern const char* kSmsDeliveryErrorObserverTopic;
extern const char* kSilentSmsReceivedObserverTopic;
extern const char* kSmsReadSuccessObserverTopic;
extern const char* kSmsReadErrorObserverTopic;
extern const char* kSmsDeletedObserverTopic;
#define DELIVERY_RECEIVED NS_LITERAL_STRING("received")
#define DELIVERY_SENDING NS_LITERAL_STRING("sending")

View File

@ -0,0 +1,124 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "DeletedMessageInfo.h"
#include "nsComponentManagerUtils.h" // for do_CreateInstance
#include "nsVariant.h"
namespace mozilla {
namespace dom {
namespace mobilemessage {
NS_IMPL_ISUPPORTS(DeletedMessageInfo, nsIDeletedMessageInfo)
DeletedMessageInfo::DeletedMessageInfo(const DeletedMessageInfoData& aData)
: mData(aData)
{
}
DeletedMessageInfo::DeletedMessageInfo(int32_t* aMessageIds,
uint32_t aMsgCount,
uint64_t* aThreadIds,
uint32_t aThreadCount)
{
mData.deletedMessageIds().AppendElements(aMessageIds, aMsgCount);
mData.deletedThreadIds().AppendElements(aThreadIds, aThreadCount);
}
DeletedMessageInfo::~DeletedMessageInfo()
{
}
/* static */ nsresult
DeletedMessageInfo::Create(int32_t* aMessageIds,
uint32_t aMsgCount,
uint64_t* aThreadIds,
uint32_t aThreadCount,
nsIDeletedMessageInfo** aDeletedInfo)
{
NS_ENSURE_ARG_POINTER(aDeletedInfo);
NS_ENSURE_TRUE(aMsgCount || aThreadCount, NS_ERROR_INVALID_ARG);
nsCOMPtr<nsIDeletedMessageInfo> deletedInfo =
new DeletedMessageInfo(aMessageIds,
aMsgCount,
aThreadIds,
aThreadCount);
deletedInfo.forget(aDeletedInfo);
return NS_OK;
}
NS_IMETHODIMP
DeletedMessageInfo::GetDeletedMessageIds(nsIVariant** aDeletedMessageIds)
{
NS_ENSURE_ARG_POINTER(aDeletedMessageIds);
if (mDeletedMessageIds) {
NS_ADDREF(*aDeletedMessageIds = mDeletedMessageIds);
return NS_OK;
}
uint32_t length = mData.deletedMessageIds().Length();
if (length == 0) {
*aDeletedMessageIds = nullptr;
return NS_OK;
}
nsresult rv;
mDeletedMessageIds = do_CreateInstance(NS_VARIANT_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDeletedMessageIds->SetAsArray(nsIDataType::VTYPE_INT32,
nullptr,
length,
mData.deletedMessageIds().Elements());
NS_ENSURE_SUCCESS(rv, rv);
mDeletedMessageIds->SetWritable(false);
NS_ADDREF(*aDeletedMessageIds = mDeletedMessageIds);
return NS_OK;
}
NS_IMETHODIMP
DeletedMessageInfo::GetDeletedThreadIds(nsIVariant** aDeletedThreadIds)
{
NS_ENSURE_ARG_POINTER(aDeletedThreadIds);
if (mDeletedThreadIds) {
NS_ADDREF(*aDeletedThreadIds = mDeletedThreadIds);
return NS_OK;
}
uint32_t length = mData.deletedThreadIds().Length();
if (length == 0) {
*aDeletedThreadIds = nullptr;
return NS_OK;
}
nsresult rv;
mDeletedThreadIds = do_CreateInstance(NS_VARIANT_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDeletedThreadIds->SetAsArray(nsIDataType::VTYPE_UINT64,
nullptr,
length,
mData.deletedThreadIds().Elements());
NS_ENSURE_SUCCESS(rv, rv);
mDeletedThreadIds->SetWritable(false);
NS_ADDREF(*aDeletedThreadIds = mDeletedThreadIds);
return NS_OK;
}
} // namespace mobilemessage
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_dom_mobilemessage_DeletedMessageInfo_h
#define mozilla_dom_mobilemessage_DeletedMessageInfo_h
#include "mozilla/dom/mobilemessage/SmsTypes.h"
#include "nsIDeletedMessageInfo.h"
class nsIWritableVariant;
namespace mozilla {
namespace dom {
namespace mobilemessage {
class DeletedMessageInfo MOZ_FINAL : public nsIDeletedMessageInfo
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDELETEDMESSAGEINFO
DeletedMessageInfo(const DeletedMessageInfoData& aData);
DeletedMessageInfo(int32_t* aMessageIds,
uint32_t aMsgCount,
uint64_t* aThreadIds,
uint32_t aThreadCount);
static nsresult Create(int32_t* aMessageIds,
uint32_t aMsgCount,
uint64_t* aThreadIds,
uint32_t aThreadCount,
nsIDeletedMessageInfo** aDeletedInfo);
const DeletedMessageInfoData& GetData() const { return mData; }
private:
// Don't try to use the default constructor.
DeletedMessageInfo();
~DeletedMessageInfo();
DeletedMessageInfoData mData;
nsCOMPtr<nsIWritableVariant> mDeletedMessageIds;
nsCOMPtr<nsIWritableVariant> mDeletedThreadIds;
protected:
/* additional members */
};
} // namespace mobilemessage
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_mobilemessage_DeletedMessageInfo_h

View File

@ -5,11 +5,13 @@
#include "MobileMessageManager.h"
#include "DeletedMessageInfo.h"
#include "DOMCursor.h"
#include "DOMRequest.h"
#include "MobileMessageCallback.h"
#include "MobileMessageCursorCallback.h"
#include "mozilla/dom/mobilemessage/Constants.h" // For kSms*ObserverTopic
#include "mozilla/dom/MozMessageDeletedEvent.h"
#include "mozilla/dom/MozMmsEvent.h"
#include "mozilla/dom/MozMobileMessageManagerBinding.h"
#include "mozilla/dom/MozSmsEvent.h"
@ -35,6 +37,7 @@
#define DELIVERY_ERROR_EVENT_NAME NS_LITERAL_STRING("deliveryerror")
#define READ_SUCCESS_EVENT_NAME NS_LITERAL_STRING("readsuccess")
#define READ_ERROR_EVENT_NAME NS_LITERAL_STRING("readerror")
#define DELETED_EVENT_NAME NS_LITERAL_STRING("deleted")
using namespace mozilla::dom::mobilemessage;
@ -71,6 +74,7 @@ MobileMessageManager::Init()
obs->AddObserver(this, kSmsDeliveryErrorObserverTopic, false);
obs->AddObserver(this, kSmsReadSuccessObserverTopic, false);
obs->AddObserver(this, kSmsReadErrorObserverTopic, false);
obs->AddObserver(this, kSmsDeletedObserverTopic, false);
}
void
@ -91,6 +95,7 @@ MobileMessageManager::Shutdown()
obs->RemoveObserver(this, kSmsDeliveryErrorObserverTopic);
obs->RemoveObserver(this, kSmsReadSuccessObserverTopic);
obs->RemoveObserver(this, kSmsReadErrorObserverTopic);
obs->RemoveObserver(this, kSmsDeletedObserverTopic);
}
JSObject*
@ -518,6 +523,39 @@ MobileMessageManager::DispatchTrustedSmsEventToSelf(const char* aTopic,
return NS_OK;
}
nsresult
MobileMessageManager::DispatchTrustedDeletedEventToSelf(nsISupports* aDeletedInfo)
{
nsCOMPtr<nsIDeletedMessageInfo> deletedInfo = do_QueryInterface(aDeletedInfo);
if (deletedInfo) {
MozMessageDeletedEventInit init;
init.mBubbles = false;
init.mCancelable = false;
DeletedMessageInfo* info =
static_cast<DeletedMessageInfo*>(deletedInfo.get());
uint32_t msgIdLength = info->GetData().deletedMessageIds().Length();
if (msgIdLength) {
Sequence<int32_t>& deletedMsgIds = init.mDeletedMessageIds.SetValue();
deletedMsgIds.AppendElements(info->GetData().deletedMessageIds());
}
uint32_t threadIdLength = info->GetData().deletedThreadIds().Length();
if (threadIdLength) {
Sequence<uint64_t>& deletedThreadIds = init.mDeletedThreadIds.SetValue();
deletedThreadIds.AppendElements(info->GetData().deletedThreadIds());
}
nsRefPtr<MozMessageDeletedEvent> event =
MozMessageDeletedEvent::Constructor(this, DELETED_EVENT_NAME, init);
return DispatchTrustedEvent(event);
}
NS_ERROR("Got a 'deleted' topic without a valid message!");
return NS_OK;
}
NS_IMETHODIMP
MobileMessageManager::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
@ -558,6 +596,10 @@ MobileMessageManager::Observe(nsISupports* aSubject, const char* aTopic,
return DispatchTrustedSmsEventToSelf(aTopic, READ_ERROR_EVENT_NAME, aSubject);
}
if (!strcmp(aTopic, kSmsDeletedObserverTopic)) {
return DispatchTrustedDeletedEventToSelf(aSubject);
}
return NS_OK;
}

View File

@ -124,6 +124,7 @@ public:
IMPL_EVENT_HANDLER(deliveryerror)
IMPL_EVENT_HANDLER(readsuccess)
IMPL_EVENT_HANDLER(readerror)
IMPL_EVENT_HANDLER(deleted)
private:
~MobileMessageManager() {}
@ -148,6 +149,9 @@ private:
const nsAString& aEventName,
nsISupports* aMsg);
nsresult
DispatchTrustedDeletedEventToSelf(nsISupports* aDeletedInfo);
/**
* Helper to get message ID from SMS/MMS Message object
*/

View File

@ -7,6 +7,7 @@
#include "MobileMessageThread.h"
#include "MobileMessageService.h"
#include "SmsSegmentInfo.h"
#include "DeletedMessageInfo.h"
namespace mozilla {
namespace dom {
@ -134,6 +135,20 @@ MobileMessageService::CreateThread(uint64_t aId,
aThread);
}
NS_IMETHODIMP
MobileMessageService::CreateDeletedMessageInfo(int32_t* aMessageIds,
uint32_t aMsgCount,
uint64_t* aThreadIds,
uint32_t aThreadCount,
nsIDeletedMessageInfo** aDeletedInfo)
{
return DeletedMessageInfo::Create(aMessageIds,
aMsgCount,
aThreadIds,
aThreadCount,
aDeletedInfo);
}
} // namespace mobilemessage
} // namespace dom
} // namespace mozilla

View File

@ -37,10 +37,10 @@ const kSmsDeliverySuccessObserverTopic = "sms-delivery-success";
const kSmsDeliveryErrorObserverTopic = "sms-delivery-error";
const kSmsReadSuccessObserverTopic = "sms-read-success";
const kSmsReadErrorObserverTopic = "sms-read-error";
const kSmsDeletedObserverTopic = "sms-deleted";
const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
const kNetworkConnStateChangedTopic = "network-connection-state-changed";
const kMobileMessageDeletedObserverTopic = "mobile-message-deleted";
const kPrefRilRadioDisabled = "ril.radio.disabled";
@ -910,7 +910,7 @@ CancellableTransaction.prototype = {
registerRunCallback: function(callback) {
if (!this.isObserversAdded) {
Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
Services.obs.addObserver(this, kMobileMessageDeletedObserverTopic, false);
Services.obs.addObserver(this, kSmsDeletedObserverTopic, false);
Services.prefs.addObserver(kPrefRilRadioDisabled, this, false);
Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
this.isObserversAdded = true;
@ -923,7 +923,7 @@ CancellableTransaction.prototype = {
removeObservers: function() {
if (this.isObserversAdded) {
Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
Services.obs.removeObserver(this, kMobileMessageDeletedObserverTopic);
Services.obs.removeObserver(this, kSmsDeletedObserverTopic);
Services.prefs.removeObserver(kPrefRilRadioDisabled, this);
Services.prefs.removeObserver(kPrefDefaultServiceId, this);
this.isObserversAdded = false;
@ -972,13 +972,11 @@ CancellableTransaction.prototype = {
this.cancelRunning(_MMS_ERROR_SHUTDOWN);
break;
}
case kMobileMessageDeletedObserverTopic: {
data = JSON.parse(data);
if (data.id != this.cancellableId) {
return;
case kSmsDeletedObserverTopic: {
if (subject && subject.deletedMessageIds &&
subject.deletedMessageIds.indexOf(this.cancellableId) >= 0) {
this.cancelRunning(_MMS_ERROR_MESSAGE_DELETED);
}
this.cancelRunning(_MMS_ERROR_MESSAGE_DELETED);
break;
}
case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: {

View File

@ -2095,11 +2095,14 @@ MobileMessageDB.prototype = {
return;
}
let deletedInfo = { messageIds: [], threadIds: [] };
txn.oncomplete = function oncomplete(event) {
if (aMessageRecord.id > self.lastMessageId) {
self.lastMessageId = aMessageRecord.id;
}
notifyResult(Cr.NS_OK, aMessageRecord);
self.notifyDeletedInfo(deletedInfo);
};
txn.onabort = function onabort(event) {
// TODO bug 832140 check event.target.errorCode
@ -2111,13 +2114,14 @@ MobileMessageDB.prototype = {
let threadStore = stores[2];
self.replaceShortMessageOnSave(txn, messageStore, participantStore,
threadStore, aMessageRecord,
aThreadParticipants);
aThreadParticipants, deletedInfo);
}, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
},
replaceShortMessageOnSave: function(aTransaction, aMessageStore,
aParticipantStore, aThreadStore,
aMessageRecord, aThreadParticipants) {
aMessageRecord, aThreadParticipants,
aDeletedInfo) {
let isReplaceTypePid = (aMessageRecord.pid) &&
((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 &&
aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) ||
@ -2127,7 +2131,8 @@ MobileMessageDB.prototype = {
aMessageRecord.delivery != DELIVERY_RECEIVED ||
!isReplaceTypePid) {
this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
aThreadStore, aMessageRecord, aThreadParticipants);
aThreadStore, aMessageRecord, aThreadParticipants,
aDeletedInfo);
return;
}
@ -2149,7 +2154,8 @@ MobileMessageDB.prototype = {
function(participantRecord) {
if (!participantRecord) {
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
aThreadStore, aMessageRecord, aThreadParticipants);
aThreadStore, aMessageRecord, aThreadParticipants,
aDeletedInfo);
return;
}
@ -2160,7 +2166,8 @@ MobileMessageDB.prototype = {
let cursor = event.target.result;
if (!cursor) {
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
aThreadStore, aMessageRecord, aThreadParticipants);
aThreadStore, aMessageRecord, aThreadParticipants,
aDeletedInfo);
return;
}
@ -2177,13 +2184,15 @@ MobileMessageDB.prototype = {
// Match! Now replace that found message record with current one.
aMessageRecord.id = foundMessageRecord.id;
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
aThreadStore, aMessageRecord, aThreadParticipants);
aThreadStore, aMessageRecord, aThreadParticipants,
aDeletedInfo);
};
});
},
realSaveRecord: function(aTransaction, aMessageStore, aParticipantStore,
aThreadStore, aMessageRecord, aThreadParticipants) {
aThreadStore, aMessageRecord, aThreadParticipants,
aDeletedInfo) {
let self = this;
this.findThreadRecordByTypedAddresses(aThreadStore, aParticipantStore,
aThreadParticipants, true,
@ -2229,7 +2238,8 @@ MobileMessageDB.prototype = {
aThreadStore,
oldMessageRecord.threadId,
aMessageRecord.id,
oldMessageRecord.read);
oldMessageRecord.read,
aDeletedInfo);
}
};
};
@ -2479,7 +2489,8 @@ MobileMessageDB.prototype = {
},
updateThreadByMessageChange: function(messageStore, threadStore, threadId,
messageId, messageRead) {
messageId, messageRead, deletedInfo) {
let self = this;
threadStore.get(threadId).onsuccess = function(event) {
// This must exist.
let threadRecord = event.target.result;
@ -2501,6 +2512,9 @@ MobileMessageDB.prototype = {
debug("Deleting mru entry for thread id " + threadId);
}
threadStore.delete(threadId);
if (deletedInfo) {
deletedInfo.threadIds.push(threadId);
}
return;
}
@ -2532,6 +2546,20 @@ MobileMessageDB.prototype = {
};
},
notifyDeletedInfo: function(info) {
if (!info) {
return;
}
let deletedInfo =
gMobileMessageService
.createDeletedMessageInfo(info.messageIds,
info.messageIds.length,
info.threadIds,
info.threadIds.length);
Services.obs.notifyObservers(deletedInfo, "sms-deleted", null);
},
/**
* nsIRilMobileMessageDatabaseService API
*/
@ -2993,6 +3021,9 @@ MobileMessageDB.prototype = {
self.translateCrErrorToMessageCallbackError(error));
return;
}
let deletedInfo = { messageIds: [], threadIds: [] };
txn.onerror = function onerror(event) {
if (DEBUG) debug("Caught error on transaction", event.target.errorCode);
//TODO look at event.target.errorCode, pick appropriate error constant
@ -3005,6 +3036,7 @@ MobileMessageDB.prototype = {
txn.oncomplete = function oncomplete(event) {
if (DEBUG) debug("Transaction " + txn + " completed.");
aRequest.notifyMessageDeleted(deleted, length);
self.notifyDeletedInfo(deletedInfo);
};
for (let i = 0; i < length; i++) {
@ -3019,6 +3051,10 @@ MobileMessageDB.prototype = {
// First actually delete the message.
messageStore.delete(messageId).onsuccess = function(event) {
if (DEBUG) debug("Message id " + messageId + " deleted");
if (deletedInfo) {
deletedInfo.messageIds.push(messageId);
}
deleted[messageIndex] = true;
// Then update unread count and most recent message.
@ -3026,11 +3062,8 @@ MobileMessageDB.prototype = {
threadStore,
messageRecord.threadId,
messageId,
messageRecord.read);
Services.obs.notifyObservers(null,
"mobile-message-deleted",
JSON.stringify({ id: messageId }));
messageRecord.read,
deletedInfo);
};
} else if (DEBUG) {
debug("Message id " + messageId + " does not exist");

View File

@ -122,6 +122,8 @@ child:
NotifyReadErrorMessage(MobileMessageData aMessageData);
NotifyDeletedMessageInfo(DeletedMessageInfoData aDeletedInfo);
parent:
/**
* Sent when the child no longer needs to use sms.

View File

@ -6,6 +6,7 @@
#include "SmsMessage.h"
#include "MmsMessage.h"
#include "SmsSegmentInfo.h"
#include "DeletedMessageInfo.h"
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#include "mozilla/dom/ContentChild.h"
@ -132,6 +133,21 @@ SmsChild::RecvNotifyReadErrorMessage(const MobileMessageData& aData)
return true;
}
bool
SmsChild::RecvNotifyDeletedMessageInfo(const DeletedMessageInfoData& aDeletedInfo)
{
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
NS_ERROR("Failed to get nsIObserverService!");
return false;
}
nsCOMPtr<nsISupports> info = new DeletedMessageInfo(aDeletedInfo);
obs->NotifyObservers(info, kSmsDeletedObserverTopic, nullptr);
return true;
}
PSmsRequestChild*
SmsChild::AllocPSmsRequestChild(const IPCSmsRequest& aRequest)
{

View File

@ -64,6 +64,9 @@ protected:
virtual bool
RecvNotifyReadErrorMessage(const MobileMessageData& aMessage) MOZ_OVERRIDE;
virtual bool
RecvNotifyDeletedMessageInfo(const DeletedMessageInfoData& aDeletedInfo) MOZ_OVERRIDE;
virtual PSmsRequestChild*
AllocPSmsRequestChild(const IPCSmsRequest& aRequest) MOZ_OVERRIDE;

View File

@ -26,6 +26,7 @@
#include "nsCxPusher.h"
#include "xpcpublic.h"
#include "nsServiceManagerUtils.h"
#include "DeletedMessageInfo.h"
namespace mozilla {
namespace dom {
@ -179,6 +180,7 @@ SmsParent::SmsParent()
obs->AddObserver(this, kSilentSmsReceivedObserverTopic, false);
obs->AddObserver(this, kSmsReadSuccessObserverTopic, false);
obs->AddObserver(this, kSmsReadErrorObserverTopic, false);
obs->AddObserver(this, kSmsDeletedObserverTopic, false);
}
void
@ -199,6 +201,7 @@ SmsParent::ActorDestroy(ActorDestroyReason why)
obs->RemoveObserver(this, kSilentSmsReceivedObserverTopic);
obs->RemoveObserver(this, kSmsReadSuccessObserverTopic);
obs->RemoveObserver(this, kSmsReadErrorObserverTopic);
obs->RemoveObserver(this, kSmsDeletedObserverTopic);
}
NS_IMETHODIMP
@ -324,6 +327,17 @@ SmsParent::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
if (!strcmp(aTopic, kSmsDeletedObserverTopic)) {
nsCOMPtr<nsIDeletedMessageInfo> deletedInfo = do_QueryInterface(aSubject);
if (!deletedInfo) {
NS_ERROR("Got a 'sms-deleted' topic without a valid message!");
return NS_OK;
}
unused << SendNotifyDeletedMessageInfo(
static_cast<DeletedMessageInfo*>(deletedInfo.get())->GetData());
return NS_OK;
}
return NS_OK;
}

View File

@ -110,6 +110,12 @@ union MobileMessageCursorData
ThreadData;
};
struct DeletedMessageInfoData
{
int32_t[] deletedMessageIds;
uint64_t[] deletedThreadIds;
};
}
}
}

View File

@ -47,6 +47,7 @@ EXPORTS.mozilla.dom += [
UNIFIED_SOURCES += [
'Constants.cpp',
'DeletedMessageInfo.cpp',
'DOMMobileMessageError.cpp',
'ipc/SmsChild.cpp',
'ipc/SmsIPCService.cpp',

View File

@ -361,9 +361,17 @@ function deleteMessagesById(aMessageIds) {
return [];
}
let promises = [];
promises.push(waitForManagerEvent("deleted"));
let request = manager.delete(aMessageIds);
return wrapDomRequestAsPromise(request)
.then((aEvent) => { return aEvent.target.result; });
promises.push(wrapDomRequestAsPromise(request));
return Promise.all(promises)
.then((aResults) => {
return { deletedInfo: aResults[0],
deletedFlags: aResults[1].target.result };
});
}
/**

View File

@ -48,3 +48,4 @@ qemu = true
[test_error_of_mms_manual_retrieval.js]
[test_error_of_mms_send.js]
[test_error_of_sms_send.js]
[test_ondeleted_event.js]

View File

@ -0,0 +1,58 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const DESTINATING_ADDRESS_1 = "0987654321";
const DESTINATING_ADDRESS_2 = "1234567890";
const OUT_TEXT = "Outgoing SMS message!";
function verifyDeletedInfo(aDeletedInfo, aMsgIds, aThreadIds) {
is(JSON.stringify(aDeletedInfo.deletedMessageIds),
JSON.stringify(aMsgIds), 'Check Msg Id.');
is(JSON.stringify(aDeletedInfo.deletedThreadIds),
JSON.stringify(aThreadIds), 'Check Thread Id.');
}
function testDeletingMessagesInOneThreadOneByOne() {
let sentMessages = [];
return Promise.resolve()
.then(() => sendSmsWithSuccess(DESTINATING_ADDRESS_1, OUT_TEXT))
.then((aMessage) => { sentMessages.push(aMessage); })
.then(() => sendSmsWithSuccess(DESTINATING_ADDRESS_1, OUT_TEXT))
.then((aMessage) => { sentMessages.push(aMessage); })
.then(() => deleteMessagesById([sentMessages[0].id]))
.then((aResult) => {
verifyDeletedInfo(aResult.deletedInfo,
[sentMessages[0].id],
null);
})
.then(() => deleteMessagesById([sentMessages[1].id]))
.then((aResult) => {
verifyDeletedInfo(aResult.deletedInfo,
[sentMessages[1].id],
[sentMessages[1].threadId]);
});
}
function testDeletingMessagesInTwoThreadsAtOnce() {
let sentMessages = [];
return Promise.resolve()
.then(() => sendSmsWithSuccess(DESTINATING_ADDRESS_1, OUT_TEXT))
.then((aMessage) => { sentMessages.push(aMessage); })
.then(() => sendSmsWithSuccess(DESTINATING_ADDRESS_2, OUT_TEXT))
.then((aMessage) => { sentMessages.push(aMessage); })
.then(() =>
deleteMessagesById(sentMessages.map((aMsg) => { return aMsg.id; })))
.then((aResult) =>
verifyDeletedInfo(aResult.deletedInfo,
sentMessages.map((aMsg) => { return aMsg.id; }),
sentMessages.map((aMsg) => { return aMsg.threadId; })));
}
startTestCommon(function testCaseMain() {
return testDeletingMessagesInOneThreadOneByOne()
.then(() => testDeletingMessagesInTwoThreadsAtOnce());
});

View File

@ -293,7 +293,9 @@ WifiGeoPositionProvider.prototype = {
let self = this;
let settingsCallback = {
handle: function(name, result) {
if (name == SETTINGS_DEBUG_ENABLED) {
// Stop the B2G UI setting from overriding the js prefs setting, and turning off logging
// If gLoggingEnabled is already on during startup, that means it was set in js prefs.
if (name == SETTINGS_DEBUG_ENABLED && !gLoggingEnabled) {
gLoggingEnabled = result;
} else if (name == SETTINGS_WIFI_ENABLED) {
gWifiScanningEnabled = result;

View File

@ -1367,7 +1367,7 @@ DataConnectionHandler.prototype = {
return;
}
if (defaultDataCallConnected && wifi_active) {
if (networkInterface.enabled && wifi_active) {
if (DEBUG) {
this.debug("Disconnect data call when Wifi is connected.");
}
@ -1375,7 +1375,7 @@ DataConnectionHandler.prototype = {
return;
}
if (!this.dataCallSettings.enabled || networkInterface.enabled) {
if (!this.dataCallSettings.enabled || defaultDataCallConnected) {
if (DEBUG) {
this.debug("Data call settings: nothing to do.");
}

View File

@ -1861,6 +1861,15 @@ RilObject.prototype = {
options.featureStr = "";
this.sendCdmaFlashCommand(options);
} else {
// Only accept one conference request at a time..
if (this._hasConferenceRequest) {
options.success = false;
options.errorName = "addError";
options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
this.sendChromeMessage(options);
return;
}
this._hasConferenceRequest = true;
this.telephonyRequestQueue.push(REQUEST_CONFERENCE,
this.sendRilRequestConference, options);

View File

@ -712,9 +712,11 @@ let emulator = (function() {
* @param connectedCallback [optional]
* A callback function which is called when conference state becomes
* connected.
* @param twice [optional]
* To send conference request twice. It is only used for special test.
* @return A deferred promise.
*/
function addCallsToConference(callsToAdd, connectedCallback) {
function addCallsToConference(callsToAdd, connectedCallback, twice) {
log("Add " + callsToAdd.length + " calls into conference.");
let deferred = Promise.defer();
@ -755,10 +757,13 @@ let emulator = (function() {
});
// Cannot use apply() through webidl, so just separate the cases to handle.
if (callsToAdd.length == 2) {
conference.add(callsToAdd[0], callsToAdd[1]);
} else {
conference.add(callsToAdd[0]);
let requestCount = twice ? 2 : 1;
for (let i = 0; i < requestCount; ++i) {
if (callsToAdd.length == 2) {
conference.add(callsToAdd[0], callsToAdd[1]);
} else {
conference.add(callsToAdd[0]);
}
}
return deferred.promise;

View File

@ -56,6 +56,7 @@ disabled = Bug 821958
[test_conference_two_remove_one.js]
[test_conference_three_hangup_one.js]
[test_conference_three_remove_one.js]
[test_conference_add_twice_error.js]
[test_outgoing_when_two_calls_on_line.js]
[test_call_presentation.js]
[test_incomingcall_phonestate_speaker.js]

View File

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
function testConferenceTwoCallsTwice() {
log('= testConferenceTwoCallsTwice =');
let outCall;
let inCall;
let outNumber = "5555550101";
let inNumber = "5555550201";
let outInfo = gOutCallStrPool(outNumber);
let inInfo = gInCallStrPool(inNumber);
let sendConferenceTwice = true;
return Promise.resolve()
.then(gCheckInitialState)
.then(() => gDial(outNumber))
.then(call => { outCall = call; })
.then(() => gCheckAll(outCall, [outCall], '', [], [outInfo.ringing]))
.then(() => gRemoteAnswer(outCall))
.then(() => gCheckAll(outCall, [outCall], '', [], [outInfo.active]))
.then(() => gRemoteDial(inNumber))
.then(call => { inCall = call; })
.then(() => gCheckAll(outCall, [outCall, inCall], '', [],
[outInfo.active, inInfo.incoming]))
.then(() => gAnswer(inCall))
.then(() => gCheckAll(inCall, [outCall, inCall], '', [],
[outInfo.held, inInfo.active]))
.then(() => gAddCallsToConference([outCall, inCall], function() {
gCheckState(conference, [], 'connected', [outCall, inCall]);
}, sendConferenceTwice))
.then(() => gCheckAll(conference, [], 'connected', [outCall, inCall],
[outInfo.active, inInfo.active]))
.then(() => gRemoteHangUpCalls([outCall, inCall]));
}
// Start the test
startTest(function() {
testConferenceTwoCallsTwice()
.then(null, error => {
ok(false, 'promise rejects during test.');
})
.then(finish);
});

View File

@ -657,6 +657,8 @@ var interfaceNamesInGlobalScope =
{name: "MozInputContext", b2g: true},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "MozInputMethodManager", b2g: true},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "MozMessageDeletedEvent", b2g: true, pref: "dom.sms.enabled"},
// IMPORTANT: Do not change this list without review from a DOM peer!
"MozMmsEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!

View File

@ -0,0 +1,21 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
[Pref="dom.sms.enabled",
Constructor(DOMString type, optional MozMessageDeletedEventInit eventInitDict)]
interface MozMessageDeletedEvent : Event
{
// Array of deleted message ids.
[Cached, Constant] readonly attribute sequence<long>? deletedMessageIds;
// Array of deleted thread ids.
[Cached, Constant] readonly attribute sequence<unsigned long long>? deletedThreadIds;
};
dictionary MozMessageDeletedEventInit : EventInit
{
sequence<long>? deletedMessageIds = null;
sequence<unsigned long long>? deletedThreadIds = null;
};

View File

@ -120,4 +120,5 @@ interface MozMobileMessageManager : EventTarget
attribute EventHandler ondeliveryerror;
attribute EventHandler onreadsuccess;
attribute EventHandler onreaderror;
attribute EventHandler ondeleted;
};

View File

@ -652,6 +652,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'MozContactChangeEvent.webidl',
'MozEmergencyCbModeEvent.webidl',
'MozInterAppMessageEvent.webidl',
'MozMessageDeletedEvent.webidl',
'MozMmsEvent.webidl',
'MozOtaStatusEvent.webidl',
'MozSettingsEvent.webidl',

View File

@ -4,11 +4,16 @@
let Promise = SpecialPowers.Cu.import('resource://gre/modules/Promise.jsm').Promise;
const STOCK_HOSTAPD_NAME = 'goldfish-hostapd';
const HOSTAPD_CONFIG_PATH = '/data/misc/wifi/hostapd/';
const HOSTAPD_CONFIG_PATH = '/data/misc/wifi/remote-hostapd/';
const SETTINGS_RIL_DATA_ENABLED = 'ril.data.enabled';
const SETTINGS_TETHERING_WIFI_ENABLED = 'tethering.wifi.enabled';
const SETTINGS_TETHERING_WIFI_IP = 'tethering.wifi.ip';
const SETTINGS_TETHERING_WIFI_SECURITY = 'tethering.wifi.security.type';
const HOSTAPD_COMMON_CONFIG = {
driver: 'test',
ctrl_interface: '/data/misc/wifi/hostapd',
ctrl_interface: '/data/misc/wifi/remote-hostapd',
test_socket: 'DIR:/data/misc/wifi/sockets',
hw_mode: 'b',
channel: '2',
@ -38,6 +43,29 @@ let gTestSuite = (function() {
let wifiOrigEnabled;
let pendingEmulatorShellCount = 0;
/**
* A wrapper function of "is".
*
* Calls the marionette function "is" as well as throws an exception
* if the givens values are not equal.
*
* @param value1
* Any type of value to compare.
*
* @param value2
* Any type of value to compare.
*
* @param message
* Debug message for this check.
*
*/
function isOrThrow(value1, value2, message) {
is(value1, value2, message);
if (value1 !== value2) {
throw message;
}
}
/**
* Send emulator shell command with safe guard.
*
@ -98,6 +126,34 @@ let gTestSuite = (function() {
return deferred.promise;
}
/**
* Wait for one named MozMobileConnection event.
*
* Resolve if that named event occurs. Never reject.
*
* Fulfill params: the DOMEvent passed.
*
* @param aEventName
* A string event name.
*
* @return A deferred promise.
*/
function waitForMobileConnectionEventOnce(aEventName, aServiceId) {
aServiceId = aServiceId || 0;
let deferred = Promise.defer();
let mobileconnection = navigator.mozMobileConnections[aServiceId];
mobileconnection.addEventListener(aEventName, function onevent(aEvent) {
mobileconnection.removeEventListener(aEventName, onevent);
ok(true, "Mobile connection event '" + aEventName + "' got.");
deferred.resolve(aEvent);
});
return deferred.promise;
}
/**
* Get the detail of currently running processes containing the given name.
*
@ -151,7 +207,9 @@ let gTestSuite = (function() {
let deferred = Promise.defer();
let permissions = [{ 'type': 'wifi-manage', 'allow': 1, 'context': window.document },
{ 'type': 'settings-write', 'allow': 1, 'context': window.document }];
{ 'type': 'settings-write', 'allow': 1, 'context': window.document },
{ 'type': 'settings-read', 'allow': 1, 'context': window.document },
{ 'type': 'mobileconnection', 'allow': 1, 'context': window.document }];
SpecialPowers.pushPermissions(permissions, function() {
deferred.resolve();
@ -226,6 +284,71 @@ let gTestSuite = (function() {
]);
}
/**
* Wait for RIL data being connected.
*
* This function will check |MozMobileConnection.data.connected| on
* every 'datachange' event. Resolve when |MozMobileConnection.data.connected|
* becomes the expected state. Never reject.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aConnected
* Boolean that indicates the desired data state.
*
* @param aServiceId [optional]
* A numeric DSDS service id. Default: 0.
*
* @return A deferred promise.
*/
function waitForRilDataConnected(aConnected, aServiceId) {
aServiceId = aServiceId || 0;
return waitForMobileConnectionEventOnce('datachange', aServiceId)
.then(function () {
let mobileconnection = navigator.mozMobileConnections[aServiceId];
if (mobileconnection.data.connected !== aConnected) {
return waitForRilDataConnected(aConnected, aServiceId);
}
});
}
/**
* Request to enable/disable wifi tethering.
*
* Enable/disable wifi tethering by changing the settings value 'tethering.wifi.enabled'.
* Resolve when the routing is verified to set up successfully in 20 seconds. The polling
* period is 1 second.
*
* Fulfill params: (none)
* Reject params: The error message.
*
* @param aEnabled
* Boolean that indicates to enable or disable wifi tethering.
*
* @return A deferred promise.
*/
function requestTetheringEnabled(aEnabled) {
let RETRY_INTERVAL_MS = 1000;
let retryCnt = 20;
return setSettings1(SETTINGS_TETHERING_WIFI_ENABLED, aEnabled)
.then(function waitForRoutingVerified() {
return verifyTetheringRouting(aEnabled)
.then(null, function onreject(aReason) {
log('verifyTetheringRouting rejected due to ' + aReason +
' (' + retryCnt + ')');
if (!retryCnt--) {
throw aReason;
}
return waitForTimeout(RETRY_INTERVAL_MS).then(waitForRoutingVerified);
});
});
}
/**
* Forget the given network.
*
@ -459,6 +582,61 @@ let gTestSuite = (function() {
});
}
/**
* Set mozSettings value with only one key.
*
* Resolve if that mozSettings value is set successfully, reject otherwise.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aKey
* A string key.
* @param aValue
* An object value.
* @param aAllowError [optional]
* A boolean value. If set to true, an error response won't be treated
* as test failure. Default: false.
*
* @return A deferred promise.
*/
function setSettings1(aKey, aValue, aAllowError) {
let settings = {};
settings[aKey] = aValue;
return setSettings(settings, aAllowError);
}
/**
* Get mozSettings value specified by @aKey.
*
* Resolve if that mozSettings value is retrieved successfully, reject
* otherwise.
*
* Fulfill params:
* The corresponding mozSettings value of the key.
* Reject params: (none)
*
* @param aKey
* A string.
* @param aAllowError [optional]
* A boolean value. If set to true, an error response won't be treated
* as test failure. Default: false.
*
* @return A deferred promise.
*/
function getSettings(aKey, aAllowError) {
let request =
navigator.mozSettings.createLock().get(aKey);
return wrapDomRequestAsPromise(request)
.then(function resolve(aEvent) {
ok(true, "getSettings(" + aKey + ") - success");
return aEvent.target.result[aKey];
}, function reject(aEvent) {
ok(aAllowError, "getSettings(" + aKey + ") - error");
});
}
/**
* Start hostapd processes with given configuration list.
*
@ -759,6 +937,159 @@ let gTestSuite = (function() {
});
}
/**
* Verify everything about routing when the wifi tethering is either on or off.
*
* We use two unix commands to verify the routing: 'netcfg' and 'ip route'.
* For now the following two things will be checked:
* 1) The default route interface should be 'rmnet0'.
* 2) wlan0 is up and its ip is set to a private subnet.
*
* We also verify iptables output as netd's NatController will execute
* $ iptables -t nat -A POSTROUTING -o rmnet0 -j MASQUERADE
*
* Resolve when the verification is successful and reject otherwise.
*
* Fulfill params: (none)
* Reject params: String that indicates the reason of rejection.
*
* @return A deferred promise.
*/
function verifyTetheringRouting(aEnabled) {
let netcfgResult = {};
let ipRouteResult = {};
// Execute 'netcfg' and parse to |netcfgResult|, each key of which is the
// interface name and value is { ip(string) }.
function exeAndParseNetcfg() {
return runEmulatorShellSafe(['netcfg'])
.then(function (aLines) {
// Sample output:
//
// lo UP 127.0.0.1/8 0x00000049 00:00:00:00:00:00
// eth0 UP 10.0.2.15/24 0x00001043 52:54:00:12:34:56
// rmnet1 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:58
// rmnet2 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:59
// rmnet3 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:5a
// wlan0 UP 192.168.1.1/24 0x00001043 52:54:00:12:34:5b
// sit0 DOWN 0.0.0.0/0 0x00000080 00:00:00:00:00:00
// rmnet0 UP 10.0.2.100/24 0x00001043 52:54:00:12:34:57
//
aLines.forEach(function (aLine) {
let tokens = aLine.split(/\s+/);
if (tokens.length < 5) {
return;
}
let ifname = tokens[0];
let ip = (tokens[2].split('/'))[0];
netcfgResult[ifname] = { ip: ip };
});
});
}
// Execute 'ip route' and parse to |ipRouteResult|, each key of which is the
// interface name and value is { src(string), default(boolean) }.
function exeAndParseIpRoute() {
return runEmulatorShellSafe(['ip', 'route'])
.then(function (aLines) {
// Sample output:
//
// 10.0.2.4 via 10.0.2.2 dev rmnet0
// 10.0.2.3 via 10.0.2.2 dev rmnet0
// 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.1
// 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15
// 10.0.2.0/24 dev rmnet0 proto kernel scope link src 10.0.2.100
// default via 10.0.2.2 dev rmnet0
// default via 10.0.2.2 dev eth0 metric 2
//
// Parse source ip for each interface.
aLines.forEach(function (aLine) {
let tokens = aLine.trim().split(/\s+/);
let srcIndex = tokens.indexOf('src');
if (srcIndex < 0 || srcIndex + 1 >= tokens.length) {
return;
}
let ifname = tokens[2];
let src = tokens[srcIndex + 1];
ipRouteResult[ifname] = { src: src, default: false };
});
// Parse default interfaces.
aLines.forEach(function (aLine) {
let tokens = aLine.split(/\s+/);
if (tokens.length < 2) {
return;
}
if ('default' === tokens[0]) {
let ifnameIndex = tokens.indexOf('dev');
if (ifnameIndex < 0 || ifnameIndex + 1 >= tokens.length) {
return;
}
let ifname = tokens[ifnameIndex + 1];
if (ipRouteResult[ifname]) {
ipRouteResult[ifname].default = true;
}
return;
}
});
});
}
// Find MASQUERADE in POSTROUTING section. 'MASQUERADE' should be found
// when tethering is enabled. 'MASQUERADE' shouldn't be found when tethering
// is disabled.
function verifyIptables() {
return runEmulatorShellSafe(['iptables', '-t', 'nat', '-L', 'POSTROUTING'])
.then(function(aLines) {
// $ iptables -t nat -L POSTROUTING
//
// Sample output (tethering on):
//
// Chain POSTROUTING (policy ACCEPT)
// target prot opt source destination
// MASQUERADE all -- anywhere anywhere
//
let found = (function find_MASQUERADE() {
// Skip first two lines.
for (let i = 2; i < aLines.length; i++) {
if (-1 !== aLines[i].indexOf('MASQUERADE')) {
return true;
}
}
return false;
})();
if ((aEnabled && !found) || (!aEnabled && found)) {
throw 'MASQUERADE' + (found ? '' : ' not') + ' found while tethering is ' +
(aEnabled ? 'enabled' : 'disabled');
}
});
}
function verifyDefaultRouteAndIp(aExpectedWifiTetheringIp) {
log(JSON.stringify(ipRouteResult));
log(JSON.stringify(netcfgResult));
if (aEnabled) {
isOrThrow(ipRouteResult['rmnet0'].src, netcfgResult['rmnet0'].ip, 'rmnet0.ip');
isOrThrow(ipRouteResult['rmnet0'].default, true, 'rmnet0.default');
isOrThrow(ipRouteResult['wlan0'].src, netcfgResult['wlan0'].ip, 'wlan0.ip');
isOrThrow(ipRouteResult['wlan0'].src, aExpectedWifiTetheringIp, 'expected ip');
isOrThrow(ipRouteResult['wlan0'].default, false, 'wlan0.default');
}
}
return verifyIptables()
.then(exeAndParseNetcfg)
.then(exeAndParseIpRoute)
.then(() => getSettings(SETTINGS_TETHERING_WIFI_IP))
.then(ip => verifyDefaultRouteAndIp(ip));
}
/**
* Clean up all the allocated resources and running services for the test.
*
@ -829,6 +1160,8 @@ let gTestSuite = (function() {
suite.waitForConnected = waitForConnected;
suite.forgetNetwork = forgetNetwork;
suite.waitForTimeout = waitForTimeout;
suite.waitForRilDataConnected = waitForRilDataConnected;
suite.requestTetheringEnabled = requestTetheringEnabled;
/**
* Common test routine.
@ -878,5 +1211,56 @@ let gTestSuite = (function() {
});
};
/**
* The common test routine for wifi tethering.
*
* Similar as doTest except that it will set 'ril.data.enabled' to true
* before testing and restore it afterward. It will also verify 'ril.data.enabled'
* and 'tethering.wifi.enabled' to be false in the beginning. Note that this routine
* will NOT change the state of 'tethering.wifi.enabled' so the user should enable
* than disable on his/her own. This routine will only check if tethering is turned
* off after testing.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aTestCaseChain
* The test case entry point, which can be a function or a promise.
*
* @return A deferred promise.
*/
suite.doTestTethering = function(aTestCaseChain) {
function verifyInitialState() {
return getSettings(SETTINGS_RIL_DATA_ENABLED)
.then(enabled => isOrThrow(enabled, false, SETTINGS_RIL_DATA_ENABLED))
.then(() => getSettings(SETTINGS_TETHERING_WIFI_ENABLED))
.then(enabled => isOrThrow(enabled, false, SETTINGS_TETHERING_WIFI_ENABLED));
}
function initTetheringTestEnvironment() {
// Enable ril data.
return Promise.all([waitForRilDataConnected(true),
setSettings1(SETTINGS_RIL_DATA_ENABLED, true)])
.then(setSettings1(SETTINGS_TETHERING_WIFI_SECURITY, 'open'));
}
function restoreToInitialState() {
return setSettings1(SETTINGS_RIL_DATA_ENABLED, false)
.then(() => getSettings(SETTINGS_TETHERING_WIFI_ENABLED))
.then(enabled => is(enabled, false, 'Tethering should be turned off.'));
}
return suite.doTest(function() {
return verifyInitialState()
.then(initTetheringTestEnvironment)
.then(aTestCaseChain)
.then(restoreToInitialState, function onreject(aReason) {
return restoreToInitialState()
.then(() => { throw aReason; }); // Re-throw the orignal reject reason.
});
});
};
return suite;
})();

View File

@ -8,3 +8,6 @@ qemu = true
[test_wifi_associate.js]
[test_wifi_associate_wo_connect.js]
[test_wifi_auto_connect.js]
[test_wifi_tethering_wifi_disabled.js]
[test_wifi_tethering_wifi_inactive.js]
[test_wifi_tethering_wifi_active.js]

View File

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
function connectToFirstNetwork() {
let firstNetwork;
return gTestSuite.requestWifiScan()
.then(function (networks) {
firstNetwork = networks[0];
return gTestSuite.testAssociate(firstNetwork);
})
.then(() => firstNetwork);
}
gTestSuite.doTestTethering(function() {
let firstNetwork;
return gTestSuite.ensureWifiEnabled(true)
.then(function () {
return Promise.all([
// 1) Set up the event listener first:
// RIL data should become disconnected after connecting to wifi.
gTestSuite.waitForRilDataConnected(false),
// 2) Connect to the first scanned network.
connectToFirstNetwork()
.then(aFirstNetwork => firstNetwork = aFirstNetwork)
]);
})
.then(function() {
return Promise.all([
// 1) Set up the event listeners first:
// Wifi should be turned off and RIL data should be connected.
gTestSuite.waitForWifiManagerEventOnce('disabled'),
gTestSuite.waitForRilDataConnected(true),
// 2) Start wifi tethering.
gTestSuite.requestTetheringEnabled(true)
]);
})
.then(function() {
return Promise.all([
// 1) Set up the event listeners first:
// Wifi should be enabled, RIL data should become disconnected and
// we should connect to an wifi AP.
gTestSuite.waitForWifiManagerEventOnce('enabled'),
gTestSuite.waitForRilDataConnected(false),
gTestSuite.waitForConnected(firstNetwork),
// 2) Stop wifi tethering.
gTestSuite.requestTetheringEnabled(false)
]);
})
// Remove wlan0 from default route by disabling wifi. Otherwise,
// it will cause the subsequent test cases loading page error.
.then(() => gTestSuite.requestWifiEnabled(false));
});

View File

@ -0,0 +1,11 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
gTestSuite.doTestTethering(function() {
return gTestSuite.ensureWifiEnabled(false)
.then(() => gTestSuite.requestTetheringEnabled(true))
.then(() => gTestSuite.requestTetheringEnabled(false))
});

View File

@ -0,0 +1,21 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
gTestSuite.doTestTethering(function() {
return gTestSuite.ensureWifiEnabled(true)
.then(function() {
return Promise.all([
gTestSuite.waitForWifiManagerEventOnce('disabled'),
gTestSuite.requestTetheringEnabled(true)
]);
})
.then(function() {
return Promise.all([
gTestSuite.waitForWifiManagerEventOnce('enabled'),
gTestSuite.requestTetheringEnabled(false)
]);
});
});

View File

@ -12,5 +12,4 @@ b2g = true
skip = false
[test_touchcaret.py]
disabled = "Bug 1035172"
[test_selectioncarets.py]