[atchat] Do not add an \r to the wakeup command to avoid many issues
[qtopia.git] / src / libraries / qtopiaphonemodem / qmodemsmsreader.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2000-2008 TROLLTECH ASA. All rights reserved.
4 **
5 ** This file is part of the Opensource Edition of the Qtopia Toolkit.
6 **
7 ** This software is licensed under the terms of the GNU General Public
8 ** License (GPL) version 2.
9 **
10 ** See http://www.trolltech.com/gpl/ for GPL licensing information.
11 **
12 ** Contact info@trolltech.com if any conditions of this licensing are
13 ** not clear to you.
14 **
15 **
16 **
17 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19 **
20 ****************************************************************************/
21
22 #include <qmodemsmsreader.h>
23 #include <qmodemservice.h>
24 #include <qmodemindicators.h>
25 #include <qatresult.h>
26 #include <qatresultparser.h>
27 #include <qatutils.h>
28 #include <qretryatchat.h>
29 #include <qsimenvelope.h>
30 #include <qtimer.h>
31
32 #include <qtopialog.h>
33
34 /*!
35     \class QModemSMSReader
36     \mainclass
37     \brief The QModemSMSReader class provides SMS reading facilities for AT-based modems.
38     \ingroup telephony::modem
39
40     This class uses the \c{AT+CPMS}, \c{AT+CMGL}, \c{AT+CMGR},
41     \c{AT+CMGD}, \c{AT+CNMI}, and \c{AT+CMGF} commands from
42     3GPP TS 27.005.
43
44     QModemSMSReader implements the QSMSReader telephony interface.  Client
45     applications should use QSMSReader instead of this class to
46     access the modem's incoming SMS message store.
47
48     \sa QSMSReader
49 */
50
51 class QSMSTaggedMessage
52 {
53 public:
54     QString     identifier;
55     QSMSMessage message;
56     bool        isUnread;
57 };
58
59 class QModemSMSReaderPrivate
60 {
61 public:
62     QModemSMSReaderPrivate()
63     {
64         totalStore = 0;
65         pendingPosn = 0;
66         unreadCount = 0;
67         fragmentsOnly = false;
68         haveCached = false;
69         fetching = false;
70         pendingCheck = false;
71         pendingFirstMessage = false;
72         needRefetch = false;
73         pseudoIndex = 1;
74         initializing = false;
75     }
76
77     QModemService *service;
78     int totalStore;
79     QList<QSMSTaggedMessage *> pending;
80     QList<QSMSTaggedMessage *> pseudoInbox;
81     uint pseudoIndex;
82     int pendingPosn;
83     uint unreadCount;
84     bool fragmentsOnly;
85     bool haveCached;
86     bool fetching;
87     bool pendingCheck;
88     bool pendingFirstMessage;
89     bool needRefetch;
90     bool initializing;
91     QStringList unreadList;
92 };
93
94 /*!
95     Create a new modem-based SMS request object for \a service.
96 */
97 QModemSMSReader::QModemSMSReader( QModemService *service )
98     : QSMSReader( service->service(), service, QCommInterface::Server )
99 {
100     d = new QModemSMSReaderPrivate();
101     d->service = service;
102     connect( service, SIGNAL(resetModem()), this, SLOT(resetModem()) );
103     service->connectToPost( "smsready", this, SLOT(smsReady()) );
104
105     service->primaryAtChat()->registerNotificationType
106         ( "+CMTI:", this, SLOT(newMessageArrived()) );
107     service->primaryAtChat()->registerNotificationType
108         ( "+CDSI:", this, SLOT(newMessageArrived()) );
109
110     connect( service->primaryAtChat(),
111              SIGNAL(pduNotification(QString,QByteArray)),
112              this,
113              SLOT(pduNotification(QString,QByteArray)) );
114 }
115
116 /*!
117     Destroy this modem-based SMS request object.
118 */
119 QModemSMSReader::~QModemSMSReader()
120 {
121     delete d;
122 }
123
124 /*!
125     \reimp
126 */
127 void QModemSMSReader::check()
128 {
129     check( false );
130 }
131
132 /*!
133     \reimp
134 */
135 void QModemSMSReader::firstMessage()
136 {
137     if ( d->haveCached ) {
138         d->pendingPosn = 0;
139         nextMessage();
140     } else {
141         d->pendingFirstMessage = true;
142         if ( !d->fetching )
143             fetchMessages();
144     }
145 }
146
147 /*!
148     \reimp
149 */
150 void QModemSMSReader::nextMessage()
151 {
152     if ( !d->unreadList.isEmpty() ) {
153         // Remove messages in the pending queue from the unread list.
154         for ( int posn = 0; posn < d->pending.count(); ++posn )
155             d->unreadList.removeAll( d->pending.at(posn)->identifier );
156         updateUnreadList();
157     }
158     if ( d->pendingPosn < d->pending.count() ) {
159
160         // Pass the next entry to the "fetched" callback.
161         QSMSTaggedMessage *tmsg = d->pending.at( d->pendingPosn++ );
162         fetched( tmsg->identifier, tmsg->message );
163
164     } else {
165
166         // We are at the end of the pending list.
167         d->pendingPosn = 0;
168         QSMSMessage dummyMsg;
169         emit fetched( "", dummyMsg );
170
171     }
172 }
173
174 /*!
175     \reimp
176 */
177 void QModemSMSReader::deleteMessage( const QString& id )
178 {
179     if ( d->unreadList.contains( id ) ) {
180         d->unreadList.removeAll( id );
181         updateUnreadList();
182     }
183     QList<QSMSTaggedMessage *>::Iterator it;
184     for ( it = d->pending.begin(); it != d->pending.end(); ++it ) {
185         if ( (*it)->identifier == id ) {
186             d->pending.erase( it );
187             break;
188         }
189     }
190     if ( id.indexOf( QChar(':') ) == 2 ) {
191
192         // Extract the name of the message store.
193         QString store = id.left(2);
194         QString trimId = id;
195         
196         // Remove trailing timestamp
197         int timeIndex = trimId.indexOf( QChar(':'), 3 );
198         if (timeIndex != -1)
199             trimId = trimId.left( timeIndex );
200
201         // Process the comma-separated indices in the list.  Normally
202         // there will be only one, but there could be multiple if
203         // the message was reconstructed from multiple parts.
204         int posn = 3;
205         int temp, numDeleted;
206         QString index;
207         if ( store != "@@" && !messageStore().isEmpty() )
208             d->service->primaryAtChat()->chat( "AT+CPMS=\"" + store + "\"" );
209         numDeleted = 0;
210         while ( posn < (int)trimId.length() ) {
211             temp = trimId.indexOf( QChar(','), posn );
212             if ( temp == -1 ) {
213                 index = trimId.mid( posn );
214                 posn = trimId.length();
215             } else {
216                 index = trimId.mid( posn, temp - posn );
217                 posn = temp + 1;
218             }
219             if ( store == "@@" ) {
220                 // Delete the message from the pseudo inbox.
221                 QList<QSMSTaggedMessage *>::Iterator it;
222                 QSMSTaggedMessage *tmsg;
223                 QString actualId = "@@:" + index;
224                 it = d->pseudoInbox.begin();
225                 while ( it != d->pseudoInbox.end() ) {
226                     tmsg = *it;
227                     if ( tmsg->identifier == actualId ) {
228                         d->pseudoInbox.erase( it );
229                         delete tmsg;
230                         break;
231                     }
232                     ++it;
233                 }
234             } else {
235                 d->service->primaryAtChat()->chat( "AT+CMGD=" + index );
236                 ++numDeleted;
237             }
238         }
239
240         // Update the "used messages" count to reflect the change.
241         if ( numDeleted > 0 ) {
242             int used = usedMessages();
243             used -= numDeleted;
244             if ( used < 0 )
245                 used = 0;
246             setValue( "usedMessages", used );
247
248             // After we have deleted message(s), the SIM is no longer full
249             d->service->indicators()->setSmsMemoryFull( QModemIndicators::SmsMemoryOK );
250         }
251     }
252 }
253
254 /*!
255     \reimp
256 */
257 void QModemSMSReader::setUnreadCount( int value )
258 {
259     d->unreadCount = value;
260     updateUnreadCount();
261 }
262
263 void QModemSMSReader::updateUnreadCount()
264 {
265     QVariant prev = value( "unreadCount" );
266     if ( prev.isNull() || prev.toInt() != (int)(d->unreadCount) ) {
267         setValue( "unreadCount", (int)(d->unreadCount) );
268         emit unreadCountChanged();
269     }
270 }
271
272 void QModemSMSReader::updateUnreadList()
273 {
274     setValue( "unreadList", d->unreadList );
275 }
276
277 /*!
278     Returns the name of the SMS message store to scan for incoming messages.
279     The default implementation returns \c SM.  If this function returns
280     an empty string, then the message store will not be changed from
281     the modem's power-on default with an \c{AT+CPMS} command.
282
283     \sa messageListCommand()
284 */
285 QString QModemSMSReader::messageStore() const
286 {
287     return "SM";        // No tr
288 }
289
290 /*!
291     Returns the command that lists the contents of an SMS message store.
292     Normally this is \c{AT+CMGL=4}, but some modems do not support the \c{=4}.
293
294     \sa messageStore()
295 */
296 QString QModemSMSReader::messageListCommand() const
297 {
298     return "AT+CMGL=4";     // No tr
299 }
300
301 /*!
302     Process a SIM download \a message from the network.  Modem
303     vendor plug-ins should override this and send the message back
304     to the SIM.
305
306     Normally SIM download messages are processed internally by the modem.
307     This function is only called if the message ends up in the incoming
308     SMS message store.
309
310     The default implementation composes a \c{SMS-PP DOWNLOAD}
311     envelope, according to 3GPP TS 11.14, and sends it to the
312     modem using the \c{AT+CSIM command}.
313 */
314 void QModemSMSReader::simDownload( const QSMSMessage& message )
315 {
316     // Construct the SMS-PP DOWNLOAD envelope to be sent to the SIM.
317     QSimEnvelope env;
318     env.setType( QSimEnvelope::SMSPPDownload );
319     QByteArray pdu = message.toPdu();
320     int sclen = (pdu[0] & 0xFF);
321     env.addExtensionField( 0x06, pdu.mid( 1, sclen ) );     // Service center address.
322     env.addExtensionField( 0x8B, pdu.mid( sclen + 1 ) );    // SMS-TPDU for the SMS-DELIVER.
323     pdu = env.toPdu();
324
325     // Build the payload for the AT+CSIM command.
326     QByteArray cmd;
327     cmd += (char)0xA0;
328     cmd += (char)0xC2;
329     cmd += (char)0x00;
330     cmd += (char)0x00;
331     cmd += (char)pdu.size();
332     cmd += pdu;
333
334     // Send the AT+CSIM command to the modem.
335     d->service->primaryAtChat()->chat
336         ( "AT+CSIM=" + QString::number( cmd.size() * 2 ) + "," + QAtUtils::toHex( cmd ) );
337 }
338
339 void QModemSMSReader::check(bool force)
340 {
341     if ( d->haveCached || ( d->initializing && !force ) ) {
342         emit messageCount( d->pending.count() );
343         updateUnreadList();
344         updateUnreadCount();
345     } else {
346         d->pendingCheck = true;
347         if ( !d->fetching )
348             fetchMessages();
349     }
350 }
351
352 void QModemSMSReader::resetModem()
353 {
354     if ( !d->initializing ) {
355         d->initializing = true;
356         d->service->post( "needsms" );
357     }
358 }
359
360 void QModemSMSReader::smsReady()
361 {
362     if ( d->initializing ) {
363         d->initializing = false;
364
365         // Query the available SMS message notification options.
366         d->service->primaryAtChat()->chat
367             ( "AT+CNMI=?", this, SLOT(nmiStatusReceived(bool,QAtResult)) );
368
369         // Perform the initial "how many messages are there" check.
370         check( true );
371     }
372 }
373
374 static QChar cnmiOpt(int flag, const char* pref)
375 {
376     for ( ; *pref; pref++ ) {
377         int bit = *pref-'0';
378         if ( flag & (1 << bit) )
379             return *pref;
380     }
381     return '0'; // not good...
382 }
383
384 void QModemSMSReader::nmiStatusReceived( bool, const QAtResult& result )
385 {
386     int flags[5] = {0, 0, 0, 0, 0};
387     int index = 0;
388     int lposn;
389     uint ch, ch2;
390     int insideb;
391     QString line;
392     QAtResultParser cmd( result );
393
394     // Collect up flags that represent the supported indication types.
395     if ( cmd.next( "+CNMI:" ) ) {
396         line = cmd.line();
397         index = 0;
398         lposn = 0;
399         insideb = 0;
400         while ( index < 5 && lposn < line.length() ) {
401             ch = line[lposn++].unicode();
402             if ( ch == '(' ) {
403                 insideb = true;
404             } else if ( ch == ',' ) {
405                 if ( !insideb )
406                     ++index;
407             } else if ( ch == ')' ) {
408                 insideb = false;
409             } else if ( insideb && ch >= '0' && ch <= '9' ) {
410                 if ( lposn < ( line.length() - 1 ) &&
411                      line[lposn] == '-' &&
412                      line[lposn + 1] >= '0' && line[lposn + 1] <= '9' )
413                 {
414                     ch2 = line[lposn + 1].unicode();
415                     lposn += 2;
416                     while ( ch <= ch2 ) {
417                         flags[index] |= (1 << (ch - '0'));
418                         ++ch;
419                     }
420                 }
421                 else
422                 {
423                     flags[index] |= (1 << (ch - '0'));
424                 }
425             }
426         }
427     }
428
429     // Construct the command to send to enable indications.
430     // We send ordinary SMS messages into the device's queue, and
431     // cell broadcast messages direct to us.
432     line = "AT+CNMI=";
433     line += cnmiOpt(flags[0],"2310");
434     line += ",";
435     line += cnmiOpt(flags[1],"10");
436     line += ",";
437     line += cnmiOpt(flags[2],"2301");
438     line += ",";
439     line += cnmiOpt(flags[3],"01");
440     line += ",";
441     line += cnmiOpt(flags[4],"01");
442
443     // Send the command.  If it doesn't work, then we assume that
444     // the phone acts in "won't notify" mode, so no harm done.
445     //
446     // Some modems fail to process this command for a small period
447     // of time after the AT+CPIN request at startup, so we need
448     // to repeat the command if it fails.
449     new QRetryAtChat( d->service->primaryAtChat(), line, 25 );
450
451     // We are now initialized.
452     d->initializing = false;
453 }
454
455 void QModemSMSReader::newMessageArrived()
456 {
457     d->fragmentsOnly = false;
458     if ( d->fetching ) {
459         d->needRefetch = true;
460     } else {
461         d->haveCached = false;
462         check();
463     }
464 }
465
466 void QModemSMSReader::pduNotification
467         ( const QString& type, const QByteArray& pdu )
468 {
469     if ( type.startsWith( "+CMT:" ) ) {
470
471         // This is an SMS message that was delivered directly to Qtopia
472         // without first going through the incoming SMS queue.  Technically,
473         // this is a violation of 3GPP TS 07.05 because we explicitly sent a
474         // "AT+CNMI" command to stop this.  But some modems do it for SMS
475         // datagrams and WAP Push messages, while still putting text messages
476         // into the normal queue.  We add it to the pseudo inbox.
477         QString id = "@@:" + QString::number( d->pseudoIndex++ );
478         QSMSTaggedMessage *tmsg = new QSMSTaggedMessage;
479         tmsg->identifier = id;
480         tmsg->isUnread = true;
481         tmsg->message = QSMSMessage::fromPdu( pdu );
482         d->pseudoInbox.append( tmsg );
483
484         // Fake out a new message notification to force a check.
485         newMessageArrived();
486
487     }
488 }
489
490 void QModemSMSReader::extractMessages( const QString& store, const QAtResult& result )
491 {
492     QAtResultParser cmd( result );
493     while ( cmd.next( "+CMGL:" ) ) {
494
495         // Get the message index and the PDU information.
496         uint index = cmd.readNumeric();
497         uint status = cmd.readNumeric();
498         QByteArray pdu = QAtUtils::fromHex( cmd.readNextLine() );
499
500         // Unpack the PDU.
501         QSMSTaggedMessage *tmsg = new QSMSTaggedMessage;
502         tmsg->isUnread = ( status == 0 );
503         tmsg->message = QSMSMessage::fromPdu( pdu );
504
505         // Build the message identifier from the store, index and date
506         QString id = store + ":" + QString::number( index );
507         id += ":" + QString::number( tmsg->message.timestamp().toTime_t(), 36 );
508         tmsg->identifier = id;
509
510         // Record the message information for later.
511         d->pending.append( tmsg );
512
513     }
514 }
515
516 void QModemSMSReader::cpmsDone( bool ok, const QAtResult& result )
517 {
518     // Look at the used and total values to determine if the
519     // incoming SMS memory store is full or not, so we can update
520     // the "SMSMemoryFull" indicator on modems that don't have
521     // a proprietry way of detecting the full state.
522     QAtResultParser parser( result );
523     QModemIndicators *indicators = d->service->indicators();
524     if ( parser.next( "+CPMS:" ) ) {
525         if ( parser.line().startsWith( QChar('"') ) )
526             parser.readString();    // Skip store name, if present.
527         uint used = parser.readNumeric();
528         uint total = parser.readNumeric();
529         if ( used < total ) {
530             indicators->setSmsMemoryFull( QModemIndicators::SmsMemoryOK );
531         } else {
532             indicators->setSmsMemoryFull( QModemIndicators::SmsMemoryFull );
533         }
534
535         // Update the local value space with the actual counts.
536         setValue( "usedMessages", (int)used, Delayed );
537         setValue( "totalMessages", (int)total );
538     }
539
540     if (ok) {
541         listMessages();
542     } else {
543         qLog(Modem) << __PRETTY_FUNCTION__ << "Giving up on CPMS.... it keeps failing. how to escalate?";
544     }
545 }
546
547 void QModemSMSReader::listMessages()
548 {
549     d->service->primaryAtChat()->chat
550         ( messageListCommand(), this, SLOT(storeListDone(bool,QAtResult)) );
551 }
552
553 void QModemSMSReader::storeListDone( bool ok, const QAtResult& result )
554 {
555     if (!ok)  {
556         // ask again... CPMS succeeded so CMGL will work sooner or later too
557         QTimer::singleShot(500, this, SLOT(listMessages()));
558         return;
559     }
560
561     // Read the messages from the response.
562     QString store = messageStore();
563     if ( store.isEmpty() )
564         store = "SM";
565     extractMessages( store, result );
566
567     // The SIM is now ready to perform SMS read operations
568     // if the AT+CMGL command was successful.
569     if ( ok )
570         setReady( true );
571
572     // Join together messages that are sent as a multi-part.
573     joinMessages();
574
575     // We now have cached messages.
576     d->haveCached = ok;
577     d->fetching = false;
578
579     // If another message arrived while we were fetching,
580     // then we need to re-fetch the entire list from scratch.
581     if ( d->needRefetch ) {
582         d->needRefetch = false;
583         fetchMessages();
584         return;
585     }
586
587     // Determine what to do with the messages we just fetched.
588     if ( d->pendingCheck ) {
589         d->pendingCheck = false;
590         emit messageCount( d->pending.count() );
591         updateUnreadList();
592         updateUnreadCount();
593     }
594     if ( d->pendingFirstMessage ) {
595         d->pendingFirstMessage = false;
596         d->pendingPosn = 0;
597         nextMessage();
598     }
599 }
600
601 void QModemSMSReader::fetchMessages()
602 {
603     // Enter fetching mode.
604     d->fetching = true;
605
606     // Clear the pending message list.
607     d->pending.clear();
608
609     // Copy the pseudo inbox into the pending list because some of
610     // the messages we are looking for may have arrived via "+CMT:".
611     for ( int index = 0; index < d->pseudoInbox.size(); ++index ) {
612         QSMSTaggedMessage *tmsg = new QSMSTaggedMessage;
613         tmsg->identifier = d->pseudoInbox[index]->identifier;
614         tmsg->isUnread = d->pseudoInbox[index]->isUnread;
615         d->pseudoInbox[index]->isUnread = false;   // Now it has been read.
616         tmsg->message = d->pseudoInbox[index]->message;
617         d->pending.append( tmsg );
618     }
619
620     // Make sure that PDU mode is enabled.  If the device was reset,
621     // then it may have forgotten about PDU mode.
622     d->service->primaryAtChat()->chat( "AT+CMGF=0" );
623
624     // Select the message store and request the message list.
625     QString store = messageStore();
626     QString cmd;
627     if ( !store.isEmpty() ) {
628         cmd = "AT+CPMS=\"" + store + "\"";
629     } else {
630         cmd = "AT+CPMS?";
631     }
632
633     QRetryAtChat* chat = new QRetryAtChat(d->service->primaryAtChat(), cmd, 25);
634     connect(chat, SIGNAL(done(bool,QAtResult)), SLOT(cpmsDone(bool,QAtResult)));
635 }
636
637 void QModemSMSReader::joinMessages()
638 {
639     int posn;
640
641     // Join the messages together.
642     QStringList toBeDeleted;
643     if ( joinMessages( d->pending, toBeDeleted ) ) {
644         // We only have fragments, so temporarily disable the new
645         // message check until we get something else in the queue.
646         d->fragmentsOnly = true;
647     }
648
649     // Delete any datagrams that were dispatched by "joinMessages".
650     for ( posn = 0; posn < (int)(toBeDeleted.count()); ++posn ) {
651         deleteMessage( toBeDeleted[posn] );
652     }
653
654     // Count the number of unread messages in the combined list.
655     // Add any new ones to the list of unread message identifiers.
656     for ( posn = 0; posn < d->pending.count(); ++posn ) {
657         if ( d->pending.at(posn)->isUnread ) {
658             ++d->unreadCount;
659             d->unreadList += d->pending.at(posn)->identifier;
660         }
661     }
662 }
663
664 // Get the multipart identifier information for a message.
665 static bool getMultipartInfo( const QSMSMessage& msg, QString& multiId,
666                               uint& part, uint& numParts )
667 {
668     QByteArray headers = msg.headers();
669     int posn = 0;
670     uint tag;
671     int len;
672     while ( ( posn + 2 ) <= headers.size() ) {
673         tag = (unsigned char)(headers[posn]);
674         len = (unsigned char)(headers[posn + 1]);
675         if ( ( posn + len + 2 ) > headers.size() )
676             break;
677         if ( tag == 0 && len >= 3 ) {
678             // Concatenated message with 8-bit identifiers.
679             multiId = msg.sender() + "&" +
680                       QString::number
681                         ( (uint)(unsigned char)(headers[posn + 2]) );
682             numParts = (unsigned char)(headers[posn + 3]);
683             part     = (unsigned char)(headers[posn + 4]);
684             if ( numParts && part && part <= numParts )
685                 return true;
686         } else if ( tag == 8 && len >= 4 ) {
687             // Concatenated message with 16-bit identifiers.
688             multiId = msg.sender() + "&" +
689                       QString::number
690                         ( ( (uint)(unsigned char)(headers[posn + 2]) << 8 ) +
691                             (uint)(unsigned char)(headers[posn + 3]) );
692             numParts = (unsigned char)(headers[posn + 4]);
693             part     = (unsigned char)(headers[posn + 5]);
694             if ( numParts && part && part <= numParts )
695                 return true;
696         }
697         posn += 2 + len;
698     }
699     return false;
700 }
701
702 // Find multipart messages within a list and join them together.
703 // The "messages" list will be modified in-place with the new list.
704 // Returns true if there were left-over fragments that could not
705 // be combined into full messages.  This function will also dispatch
706 // datagram messages to their destinations.  The "toBeDeleted" list will
707 // contain the identifiers of messages to be deleted because their
708 // corresponding datagrams have been dispatched.
709 bool QModemSMSReader::joinMessages
710     ( QList<QSMSTaggedMessage *>& messages, QStringList& toBeDeleted )
711 {
712     QString multiId;
713     uint part, numParts;
714     QString multiId2;
715     uint part2, numParts2;
716     int posn, posn2;
717     QList<QSMSTaggedMessage *> newList;
718     QSMSTaggedMessage *tmsg;
719     QList<QSMSTaggedMessage> partList;
720     bool leftOvers;
721
722     // Construct a new message list.  This could probably be made
723     // more efficient, but we normally won't be processing more than
724     // a handful of messages at a time.
725     leftOvers = false;
726     for ( posn = 0; posn < messages.count(); ++posn ) {
727         if ( getMultipartInfo
728                 ( messages.at(posn)->message, multiId, part, numParts ) ) {
729
730             // Collect up the rest of the parts for this message.
731             partList.clear();
732             partList.append( *messages.at(posn) );
733             for ( posn2 = posn + 1; posn2 < messages.count(); ++posn2 ) {
734
735                 // Skip this message if it isn't part of the same multipart.
736                 if ( !getMultipartInfo
737                         ( messages.at(posn2)->message,
738                           multiId2, part2, numParts2 ) ) {
739                     continue;
740                 } else if ( multiId != multiId2 ) {
741                     continue;
742                 }
743
744                 // Add the part to the temporary list.
745                 partList.append( *messages.at(posn2) );
746             }
747
748             // Do we have all of the parts that we are interested in?
749             if ( partList.count() == (int)numParts ) {
750                 tmsg = new QSMSTaggedMessage();
751                 tmsg->isUnread = false;
752                 tmsg->identifier = messages.at(posn)->identifier.left(3);
753                 tmsg->message = messages.at(posn)->message;
754                 tmsg->message.setText( "" );
755                 tmsg->message.setHeaders( QByteArray() );
756                 for ( part = 1; part <= numParts; ++part ) {
757                     for ( posn2 = 0; posn2 < partList.count(); ++posn2 ) {
758                         getMultipartInfo
759                             ( partList[posn2].message,
760                               multiId2, part2, numParts2 );
761                         if ( part == part2 ) {
762                             if ( partList[posn2].isUnread )
763                                 tmsg->isUnread = true;
764                             tmsg->message.addParts
765                                 ( partList[posn2].message.parts() );
766                             
767                             // Remove timestamp from part identifier
768                             QString id = partList[posn2].identifier.mid(3);
769                             int timeIndex = id.indexOf( QChar(':') );
770                             if (timeIndex != -1)
771                                 id = id.left( timeIndex );
772                             
773                             if ( tmsg->identifier.length() == 3 ) {
774                                 tmsg->identifier += id;
775                             } else {
776                                 tmsg->identifier += "," + id;
777                             }
778                         }
779                     }
780                 }
781                 // Append timestamp to message identifier
782                 uint time_t = tmsg->message.timestamp().toTime_t();
783                 tmsg->identifier += ":" + QString::number( time_t, 36 );
784                 
785                 if ( dispatchDatagram( tmsg ) ) {
786                     toBeDeleted.append( tmsg->identifier );
787                     delete tmsg;
788                 } else {
789                     newList.append( tmsg );
790                 }
791             } else {
792                 leftOvers = true;
793             }
794
795         } else {
796
797             // Ordinary message: copy directly to the new list.
798             tmsg = new QSMSTaggedMessage();
799             tmsg->identifier = messages.at(posn)->identifier;
800             tmsg->isUnread = messages.at(posn)->isUnread;
801             tmsg->message = messages.at(posn)->message;
802             if ( dispatchDatagram( tmsg ) ) {
803                 toBeDeleted.append( tmsg->identifier );
804                 delete tmsg;
805             } else {
806                 newList.append( tmsg );
807             }
808
809         }
810     }
811     messages = newList;
812     return leftOvers;
813 }
814
815 // Dispatch an SMS message that has an application port number
816 // to the local application that handles that kind of service.
817 // If the port number is not understood, then we pass the
818 // message to the normal message handling program (e.g. qtmail).
819 bool QModemSMSReader::dispatchDatagram( QSMSTaggedMessage *m )
820 {
821     // Check for SIM download packets that were not extracted by the modem.
822     if ( m->message.protocol() == 0x3F ||        // SIM data download
823          m->message.protocol() == 0x3C ) {       // ANSI-136 R-DATA
824         if ( ( m->message.dataCodingScheme() & 0x03 ) == 2 ) {
825             // Class 2 message intended for the SIM.
826             simDownload( m->message );
827             return true;
828         }
829     }
830
831     // Dispatch using the core dispatch code in QTelephonyService.
832     return d->service->dispatchDatagram( m->message );
833 }