If you ever wanted to receive an SMS without user notification, it might have occurred to you that using standard CSmsClientMtm APIs is not the best option. Sometimes it's possible to delete an incoming message before your phone beeps, but we can't rely on this. Luckily, we have a different approach which will help us to take care about user's rest. I'm talking about receiving SMS through sockets.
We will need the TSmsAddr class which is not a part of the public SDK since Series60 3rd MR. Now you can find it inside the SMSUtilities folder of the API plug-in package.
The basic idea is to bind a RSocket to the SMS channel and wait till something would appear there. We have several different ways of opening the SMS socket defined in the TSmsAddrFamily enum:
enum TSmsAddrFamily
{
ESmsAddrUnbound = 0, // Not bound yet
ESmsAddrSendOnly = 1, // Only for sending, no reception
ESmsAddrMessageIndication = 2, // Matches on IEI 0x01 and DCS 0x110(1)0(1)xxxx
ESmsAddrMatchIEI = 3, // For matching Information Element Identifiers
ESmsAddrMatchText = 4, // For matching any text patterns
ESmsAddrRecvAny = 5, // Receive all messages. Only one client allowed
ESmsAddrStatusReport = 6, // For receiving Status Reports
ESmsAddrLocalOperation = 7, // For local SIM operations
ESmsAddrApplication8BitPort = 8, // For sock port identification
ESmsAddrApplication16BitPort = 9, // For sock port identification
ESmsAddrEmail = 10 // For matching of email messages
};
ESmsAddrRecvAny looks tempting, but the built-in SMS application has already bound it. Our next choice is ESmsAddrMatchText. We can catch any message that starts with the required symbol sequence defined through SetTextMatch(). I'm using '#:' as an example, but you are free to setup any necessary pattern, even an empty one, that will allow you to intercept all messages.
Alternatively, you can select messages not by a prefix, but by a port number using ESmsAddrApplication8BitPort or ESmsAddrApplication16BitPort. It's useful for the inter-application communication. For example, client/server applications can exchange messages via SMS on the defined port.
One more note before digging into the code. Even after you read the message from the SMS socket, the actual data is still persisted in the receiving queue. You have to sent the KIoctlReadMessageSucceeded command using the Ioctl() function to indicate, that the message was successfully received. Otherwise, all the messages you intercepted will appear in the native SMS inbox on the next reboot.
And now less talk, more code.
SmsSocketEngine.h
#ifndef SMSSOCKETENGINE_H
#define SMSSOCKETENGINE_H
// INCLUDES
#include
#include // RSocketServ
#include // RFs
// LIBS
// esock.lib (RSocketServ), smsu.lib (TSmsAddr), gsmu.lib (CSmsBuffer),
// efsrv.lib (RFs), estor.lib (RSmsSocketReadStream)
// CAPS
// NetworkServices (RSocket::Bind), ReadUserData (RSocket::Bind),
// WriteUserData (RSocket::Bind)
// FORWARD DECLARATIONS
class MSmsEngineObserver;
// CLASS DECLARATION
/**
* CSmsSocketEngine class.
* CSmsSocketEngine declaration.
*
*/
class CSmsSocketEngine : public CActive
{
public: // Constructors and destructor
/**
* NewL()
* Creates new CSmsSocketEngine object.
* @param aObserver Reference to the MSmsEngineObserver object.
* @return Pointer to the created instance of CSmsSocketEngine.
*/
static CSmsSocketEngine* NewL(MSmsEngineObserver& aObserver);
/**
* NewLC()
* Creates new CSmsSocketEngine object.
* @param aObserver Reference to the MSmsEngineObserver object.
* @return Pointer to the created instance of CSmsSocketEngine.
*/
static CSmsSocketEngine* NewLC(MSmsEngineObserver& aObserver);
/**
* ~CSmsSocketEngine()
* Destructor.
*/
~CSmsSocketEngine();
protected: // From CActive
/**
* DoCancel()
* Implements cancellation of an outstanding request.
*/
void DoCancel();
/**
* RunL()
* Handles an active objects request completion event.
*/
void RunL();
/**
* RunError()
* Handles a leave occurring in the request completion event handler RunL().
* @param aError The leave code.
* @return KErrNone if error was handled, otherwise system-wide error.
*/
TInt RunError(TInt aError);
private: // New functions
/**
* Start()
* Starts waiting for the actual socket data.
*/
void Start();
public: // New functions
/**
* StartListeningL()
* Starts listening for an incoming SMS.
*/
void StartListeningL();
/**
* StopListening()
* Stops listening.
*/
void StopListening();
private: // Constructors
/**
* CSmsSocketEngine()
* Default C++ constructor.
* @param aObserver Reference to the MSmsEngineObserver object.
*/
CSmsSocketEngine(MSmsEngineObserver& aObserver);
/**
* ConstructL()
* Default EPOC constructor.
*/
void ConstructL();
private: // enum
enum TSmsSocketEngineState
{
ESmsIdle,
ESmsListening,
ESmsSystemNotyfing
};
private: // data
MSmsEngineObserver& iObserver;
RSocketServ iSocketServ;
RSocket iReadSocket;
RFs iFs;
TBool iWait;
TPckgBuf iBuf;
TSmsSocketEngineState iState;
};
#endif // SMSSOCKETENGINE_H
SmsSocketEngine.cpp
// INCLUDE FILES
#include "SmsSocketEngine.h" // CSmsSocketEngine
#include "SmsEngineObserver.h" // MSmsEngineObserver
#include // TSmsAddr
#include // CSmsBuffer
#include // RSmsSocketReadStream
#include // CSmsMessage
// ================= MEMBER FUNCTIONS ========================================
//
// ---------------------------------------------------------------------------
// CSmsSocketEngine::CSmsSocketEngine(MSmsEngineObserver& aObserver)
// Default C++ constructor.
// ---------------------------------------------------------------------------
//
CSmsSocketEngine::CSmsSocketEngine(MSmsEngineObserver& aObserver) :
CActive(EPriorityStandard),
iObserver(aObserver)
{
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::~CSmsSocketEngine()
// Destructor.
// ---------------------------------------------------------------------------
//
CSmsSocketEngine::~CSmsSocketEngine()
{
// cancel any request, if outstanding
Cancel();
iReadSocket.Close();
iFs.Close();
iSocketServ.Close();
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::NewL(MSmsEngineObserver& aObserver)
// Two-phased constructor.
// ---------------------------------------------------------------------------
//
CSmsSocketEngine* CSmsSocketEngine::NewL(MSmsEngineObserver& aObserver)
{
CSmsSocketEngine* self = CSmsSocketEngine::NewLC(aObserver);
CleanupStack::Pop(self);
return self;
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::NewLC(MSmsEngineObserver& aObserver)
// Two-phased constructor.
// ---------------------------------------------------------------------------
//
CSmsSocketEngine* CSmsSocketEngine::NewLC(MSmsEngineObserver& aObserver)
{
CSmsSocketEngine* self = new (ELeave) CSmsSocketEngine(aObserver);
CleanupStack::PushL(self);
self->ConstructL();
return self;
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::ConstructL()
// Default EPOC constructor.
// ---------------------------------------------------------------------------
//
void CSmsSocketEngine::ConstructL()
{
CActiveScheduler::Add(this);
User::LeaveIfError(iSocketServ.Connect());
User::LeaveIfError(iFs.Connect());
StartListeningL();
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::DoCancel()
// Implements cancellation of an outstanding request.
// ---------------------------------------------------------------------------
//
void CSmsSocketEngine::DoCancel()
{
iReadSocket.CancelIoctl();
iState = ESmsIdle;
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::RunL()
// Handles an active objects request completion event.
// ---------------------------------------------------------------------------
//
void CSmsSocketEngine::RunL()
{
if (iStatus == KErrNone)
{
if (iState == ESmsListening)
{
// allocate SMS buffer
CSmsBuffer* buffer = CSmsBuffer::NewL();
CleanupStack::PushL(buffer);
// create new incoming message, pass ownership of the buffer!
CSmsMessage* message = CSmsMessage::NewL(iFs,
CSmsPDU::ESmsDeliver,
buffer);
CleanupStack::Pop(buffer);
CleanupStack::PushL(message);
// open socket read stream
RSmsSocketReadStream readStream(iReadSocket);
CleanupClosePushL(readStream);
// read message
message->InternalizeL(readStream);
CleanupStack::PopAndDestroy(&readStream);
TPtrC number = message->ToFromAddress();
// extract the message body
HBufC* body = HBufC::NewLC(message->Buffer().Length());
TPtr bodyPtr(body->Des());
message->Buffer().Extract(bodyPtr, 0, message->Buffer().Length());
iObserver.MessageReceived(number, *body);
CleanupStack::PopAndDestroy(2, message); // body, message
// notify system about successful receiving
iReadSocket.Ioctl(KIoctlReadMessageSucceeded, iStatus,
NULL, KSolSmsProv);
iState = ESmsSystemNotyfing;
SetActive();
}
else
{
Start();
}
}
else
{
iObserver.HandleError(iStatus.Int());
}
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::RunError(TInt aError)
// Handles a leave occurring in the request completion event handler RunL().
// ---------------------------------------------------------------------------
//
TInt CSmsSocketEngine::RunError(TInt aError)
{
iObserver.HandleError(aError);
return KErrNone;
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::Start()
// Starts waiting for the actual socket data.
// ---------------------------------------------------------------------------
//
void CSmsSocketEngine::Start()
{
// wait for an incoming data
iReadSocket.Ioctl(KIOctlSelect, iStatus, &iBuf, KSOLSocket);
iState = ESmsListening;
SetActive();
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::StartListeningL()
// Starts listening for an incoming SMS.
// ---------------------------------------------------------------------------
//
void CSmsSocketEngine::StartListeningL()
{
// we can't handle several requests simultaneously
if (IsActive())
{
User::Leave(KErrNotReady);
}
// just in case
iReadSocket.Close();
// open read socket
User::LeaveIfError(iReadSocket.Open(iSocketServ,
KSMSAddrFamily,
KSockDatagram,
KSMSDatagramProtocol));
_LIT8(KMathTag, "#:");
// set match pattern
TSmsAddr smsAddr;
smsAddr.SetSmsAddrFamily(ESmsAddrMatchText);
smsAddr.SetTextMatch(KMathTag); // put KNullDesC8 to catch all messages
// use this to read the message from a certain port
//smsAddr.SetSmsAddrFamily(ESmsAddrApplication8BitPort);
//smsAddr.SetPort(16500); // GSM Application port from 16000 to 16999
// bind the socket
User::LeaveIfError(iReadSocket.Bind(smsAddr));
iBuf() = KSockSelectRead;
Start();
}
// ---------------------------------------------------------------------------
// CSmsSocketEngine::StopListening()
// Stops listening.
// ---------------------------------------------------------------------------
//
void CSmsSocketEngine::StopListening()
{
Cancel();
iReadSocket.Close();
}
P.S.: There is such an example on Wiki Nokia, unfortunately it's very buggy.