/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.thoughtcrime.securesms.service;
import java.util.ArrayList;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionCipher;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.protocol.KeyExchangeWirePrefix;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.protocol.SecureMessageWirePrefix;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MultipartMessageHandler;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.InvalidMessageException;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.telephony.SmsManager;
import android.util.Log;
public class SmsSender {
private final MultipartMessageHandler multipartMessageHandler = new MultipartMessageHandler();
private final Context context;
public SmsSender(Context context) {
this.context = context;
}
public void process(MasterSecret masterSecret, Intent intent) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
long messageId = intent.getLongExtra("message_id", -1);
Cursor c = null;
Log.w("SMSSenderService", "Processing outgoing message: " + messageId);
try {
if (messageId == -1) c = DatabaseFactory.getSmsDatabase(context).getOutgoingMessages();
else c = DatabaseFactory.getSmsDatabase(context).getMessage(messageId);
if (c != null && c.moveToFirst()) {
do {
messageId = c.getLong(c.getColumnIndexOrThrow(SmsDatabase.ID));
String body = c.getString(c.getColumnIndexOrThrow(SmsDatabase.BODY));
String address = c.getString(c.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
String messageText = getClearTextBody(masterCipher, body);
long type = c.getLong(c.getColumnIndexOrThrow(SmsDatabase.TYPE));
if (!SmsDatabase.Types.isPendingMessageType(type))
continue;
if (isSecureMessage(type))
messageText = getAsymmetricEncrypt(masterSecret, messageText, address);
Log.w("SMSSenderService", "Actually delivering: " + messageId);
deliverTextMessage(address, messageText, messageId, type);
} while (c.moveToNext());
}
} finally {
if (c != null)
c.close();
}
}
private String getClearTextBody(MasterCipher masterCipher, String body) {
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT)) {
try {
return masterCipher.decryptBody(body.substring(Prefix.SYMMETRIC_ENCRYPT.length()));
} catch (InvalidMessageException e) {
return "Error decrypting message.";
}
} else {
return body;
}
}
private ArrayList constructSentIntents(long messageId, long type, ArrayList messages) {
ArrayList sentIntents = new ArrayList(messages.size());
for (int i=0;i messages = SmsManager.getDefault().divideMessage(text);
ArrayList sentIntents = constructSentIntents(messageId, type, messages);
// XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients
// and messages, this will throw an NPE. I have no idea why, so I'm just catching it and marking
// the message as a failure. That way at least it doesn't repeatedly crash every time you start
// the app.
try {
SmsManager.getDefault().sendMultipartTextMessage(recipient, null, messages, sentIntents, null);
} catch (NullPointerException npe) {
Log.w("SmsSender", npe);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
}
}
private void deliverSecureTransportTextMessage(String recipient, String text, long messageId, long type) {
WirePrefix prefix;
if (isSecureMessage(type)) {
prefix = new SecureMessageWirePrefix();
text = text.substring(Prefix.ASYMMETRIC_ENCRYPT.length());
} else {
prefix = new KeyExchangeWirePrefix();
text = text.substring(Prefix.KEY_EXCHANGE.length());
}
if (!multipartMessageHandler.isManualTransport(text)) {
deliverGSMTransportTextMessage(recipient, prefix.calculatePrefix(text) + text, messageId, type);
return;
}
ArrayList messages = multipartMessageHandler.divideMessage(recipient, text, prefix);
ArrayList sentIntents = constructSentIntents(messageId, type, messages);
for (int i=0;i