Transition the outbound pipeline to JobManager jobs.

This commit is contained in:
Moxie Marlinspike
2014-11-08 11:35:58 -08:00
parent 99f42e2ee1
commit cafe03a70a
38 changed files with 1590 additions and 1817 deletions

View File

@@ -1,136 +0,0 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.transport.UniversalTransport;
import org.whispersystems.textsecure.crypto.UntrustedIdentityException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.SendReq;
public class MmsSender {
private final Context context;
private final SystemStateListener systemStateListener;
private final ToastHandler toastHandler;
public MmsSender(Context context, SystemStateListener systemStateListener, ToastHandler toastHandler) {
this.context = context;
this.systemStateListener = systemStateListener;
this.toastHandler = toastHandler;
}
public void process(MasterSecret masterSecret, Intent intent) {
Log.w("MmsSender", "Got intent action: " + intent.getAction());
if (SendReceiveService.SEND_MMS_ACTION.equals(intent.getAction())) {
handleSendMms(masterSecret, intent);
}
}
private void handleSendMms(MasterSecret masterSecret, Intent intent) {
long messageId = intent.getLongExtra("message_id", -1);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
UniversalTransport transport = new UniversalTransport(context, masterSecret);
try {
SendReq[] messages = database.getOutgoingMessages(masterSecret, messageId);
for (SendReq message : messages) {
long threadId = database.getThreadIdForMessage(message.getDatabaseMessageId());
try {
Log.w("MmsSender", "Passing to MMS transport: " + message.getDatabaseMessageId());
database.markAsSending(message.getDatabaseMessageId());
MmsSendResult result = transport.deliver(message);
if (result.isUpgradedSecure()) database.markAsSecure(message.getDatabaseMessageId());
if (result.isPush()) database.markAsPush(message.getDatabaseMessageId());
database.markAsSent(message.getDatabaseMessageId(), result.getMessageId(),
result.getResponseStatus());
systemStateListener.unregisterForConnectivityChange();
} catch (InsecureFallbackApprovalException ifae) {
Log.w("MmsSender", ifae);
database.markAsPendingInsecureSmsFallback(message.getDatabaseMessageId());
notifyMessageDeliveryFailed(context, threads, threadId);
} catch (SecureFallbackApprovalException sfae) {
Log.w("MmsSender", sfae);
database.markAsPendingSecureSmsFallback(message.getDatabaseMessageId());
notifyMessageDeliveryFailed(context, threads, threadId);
} catch (UndeliverableMessageException e) {
Log.w("MmsSender", e);
database.markAsSentFailed(message.getDatabaseMessageId());
notifyMessageDeliveryFailed(context, threads, threadId);
} catch (UntrustedIdentityException uie) {
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey());
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage);
database.markAsSentFailed(messageId);
} catch (RetryLaterException e) {
Log.w("MmsSender", e);
database.markAsOutbox(message.getDatabaseMessageId());
if (systemStateListener.isConnected()) scheduleQuickRetryAlarm();
else systemStateListener.registerForConnectivityChange();
toastHandler
.obtainMessage(0, context.getString(R.string.SmsReceiver_currently_unable_to_send_your_sms_message))
.sendToTarget();
}
}
} catch (MmsException e) {
Log.w("MmsSender", e);
if (messageId != -1)
database.markAsSentFailed(messageId);
}
}
private static void notifyMessageDeliveryFailed(Context context, ThreadDatabase threads, long threadId) {
Recipients recipients = threads.getRecipientsForThreadId(threadId);
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
}
private void scheduleQuickRetryAlarm() {
((AlarmManager)context.getSystemService(Context.ALARM_SERVICE))
.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (30 * 1000),
PendingIntent.getService(context, 0,
new Intent(SendReceiveService.SEND_MMS_ACTION,
null, context, SendReceiveService.class),
PendingIntent.FLAG_UPDATE_CURRENT));
}
}

View File

@@ -1,363 +0,0 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.database.CanonicalSessionMigrator;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.WorkerThread;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Services that handles sending/receiving of SMS/MMS.
*
* @author Moxie Marlinspike
*/
public class SendReceiveService extends Service {
public static final String SEND_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_SMS_ACTION";
public static final String SENT_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SENT_SMS_ACTION";
public static final String DELIVERED_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DELIVERED_SMS_ACTION";
// public static final String RECEIVE_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_SMS_ACTION";
public static final String SEND_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_ACTION";
// public static final String RECEIVE_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_MMS_ACTION";
// public static final String DOWNLOAD_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_ACTION";
// public static final String DOWNLOAD_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION";
// public static final String DOWNLOAD_MMS_PENDING_APN_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION";
// public static final String RECEIVE_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_PUSH_ACTION";
// public static final String DECRYPTED_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DECRYPTED_PUSH_ACTION";
// public static final String DOWNLOAD_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_PUSH_ACTION";
// public static final String DOWNLOAD_AVATAR_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_AVATAR_ACTION";
public static final String MASTER_SECRET_EXTRA = "master_secret";
private static final int SEND_SMS = 0;
// private static final int RECEIVE_SMS = 1;
private static final int SEND_MMS = 2;
// private static final int RECEIVE_MMS = 3;
// private static final int DOWNLOAD_MMS = 4;
// private static final int DOWNLOAD_MMS_PENDING = 5;
// private static final int RECEIVE_PUSH = 6;
// private static final int DOWNLOAD_PUSH = 7;
// private static final int DOWNLOAD_AVATAR = 8;
private ToastHandler toastHandler;
private SystemStateListener systemStateListener;
// private MmsReceiver mmsReceiver;
// private SmsReceiver smsReceiver;
private SmsSender smsSender;
private MmsSender mmsSender;
// private MmsDownloader mmsDownloader;
// private PushReceiver pushReceiver;
// private PushDownloader pushDownloader;
// private AvatarDownloader avatarDownloader;
private MasterSecret masterSecret;
private boolean hasSecret;
private NewKeyReceiver newKeyReceiver;
private ClearKeyReceiver clearKeyReceiver;
private List<Runnable> workQueue;
private List<Runnable> pendingSecretList;
@Override
public void onCreate() {
initializeHandlers();
initializeProcessors();
initializeAddressCanonicalization();
initializeWorkQueue();
initializeMasterSecret();
}
@Override
public void onStart(Intent intent, int startId) {
if (intent == null) return;
String action = intent.getAction();
if (action.equals(SEND_SMS_ACTION))
scheduleSecretRequiredIntent(SEND_SMS, intent);
// else if (action.equals(RECEIVE_SMS_ACTION))
// scheduleIntent(RECEIVE_SMS, intent);
else if (action.equals(SENT_SMS_ACTION))
scheduleIntent(SEND_SMS, intent);
else if (action.equals(DELIVERED_SMS_ACTION))
scheduleIntent(SEND_SMS, intent);
else if (action.equals(SEND_MMS_ACTION))
scheduleSecretRequiredIntent(SEND_MMS, intent);
// else if (action.equals(RECEIVE_MMS_ACTION))
// scheduleIntent(RECEIVE_MMS, intent);
// else if (action.equals(DOWNLOAD_MMS_ACTION))
// scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent);
// else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION))
// scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent);
// else if (action.equals(RECEIVE_PUSH_ACTION))
// scheduleIntent(RECEIVE_PUSH, intent);
// else if (action.equals(DECRYPTED_PUSH_ACTION))
// scheduleSecretRequiredIntent(RECEIVE_PUSH, intent);
// else if (action.equals(DOWNLOAD_PUSH_ACTION))
// scheduleSecretRequiredIntent(DOWNLOAD_PUSH, intent);
// else if (action.equals(DOWNLOAD_AVATAR_ACTION))
// scheduleIntent(DOWNLOAD_AVATAR, intent);
else
Log.w("SendReceiveService", "Received intent with unknown action: " + intent.getAction());
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Log.w("SendReceiveService", "onDestroy()...");
super.onDestroy();
if (newKeyReceiver != null)
unregisterReceiver(newKeyReceiver);
if (clearKeyReceiver != null)
unregisterReceiver(clearKeyReceiver);
}
private void initializeHandlers() {
systemStateListener = new SystemStateListener(this);
toastHandler = new ToastHandler();
}
private void initializeProcessors() {
// smsReceiver = new SmsReceiver(this);
smsSender = new SmsSender(this, systemStateListener, toastHandler);
// mmsReceiver = new MmsReceiver(this);
mmsSender = new MmsSender(this, systemStateListener, toastHandler);
// mmsDownloader = new MmsDownloader(this, toastHandler);
// pushReceiver = new PushReceiver(this);
// pushDownloader = new PushDownloader(this);
// avatarDownloader = new AvatarDownloader(this);
}
private void initializeWorkQueue() {
pendingSecretList = new LinkedList<Runnable>();
workQueue = new LinkedList<Runnable>();
Thread workerThread = new WorkerThread(workQueue, "SendReceveService-WorkerThread");
workerThread.start();
}
private void initializeMasterSecret() {
hasSecret = false;
newKeyReceiver = new NewKeyReceiver();
clearKeyReceiver = new ClearKeyReceiver();
IntentFilter newKeyFilter = new IntentFilter(KeyCachingService.NEW_KEY_EVENT);
registerReceiver(newKeyReceiver, newKeyFilter, KeyCachingService.KEY_PERMISSION, null);
IntentFilter clearKeyFilter = new IntentFilter(KeyCachingService.CLEAR_KEY_EVENT);
registerReceiver(clearKeyReceiver, clearKeyFilter, KeyCachingService.KEY_PERMISSION, null);
initializeWithMasterSecret(KeyCachingService.getMasterSecret(this));
// Intent bindIntent = new Intent(this, KeyCachingService.class);
// startService(bindIntent);
// bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private void initializeWithMasterSecret(MasterSecret masterSecret) {
Log.w("SendReceiveService", "SendReceive service got master secret.");
if (masterSecret != null) {
synchronized (workQueue) {
this.masterSecret = masterSecret;
this.hasSecret = true;
Iterator<Runnable> iterator = pendingSecretList.iterator();
while (iterator.hasNext())
workQueue.add(iterator.next());
workQueue.notifyAll();
}
}
}
private void initializeAddressCanonicalization() {
CanonicalSessionMigrator.migrateSessions(this);
}
private MasterSecret getPlaceholderSecret() {
try {
return MasterSecretUtil.getMasterSecret(SendReceiveService.this,
MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
} catch (InvalidPassphraseException e) {
Log.w("SendReceiveService", e);
return null;
}
}
private void scheduleIntent(int what, Intent intent) {
Runnable work = new SendReceiveWorkItem(intent, what);
synchronized (workQueue) {
workQueue.add(work);
workQueue.notifyAll();
}
}
private void scheduleSecretRequiredIntent(int what, Intent intent) {
Runnable work = new SendReceiveWorkItem(intent, what);
synchronized (workQueue) {
if (!hasSecret && TextSecurePreferences.isPasswordDisabled(SendReceiveService.this)) {
initializeWithMasterSecret(getPlaceholderSecret());
}
if (hasSecret) {
workQueue.add(work);
workQueue.notifyAll();
} else {
pendingSecretList.add(work);
}
}
}
private class SendReceiveWorkItem implements Runnable {
private final Intent intent;
private final int what;
public SendReceiveWorkItem(Intent intent, int what) {
this.intent = intent;
this.what = what;
}
@Override
public void run() {
MasterSecret masterSecret = SendReceiveService.this.masterSecret;
if (masterSecret == null && TextSecurePreferences.isPasswordDisabled(SendReceiveService.this)) {
masterSecret = getPlaceholderSecret();
}
switch (what) {
// case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return;
case SEND_SMS: smsSender.process(masterSecret, intent); return;
// case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return;
case SEND_MMS: mmsSender.process(masterSecret, intent); return;
// case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return;
// case DOWNLOAD_MMS_PENDING: mmsDownloader.process(masterSecret, intent); return;
// case RECEIVE_PUSH: pushReceiver.process(masterSecret, intent); return;
// case DOWNLOAD_PUSH: pushDownloader.process(masterSecret, intent); return;
// case DOWNLOAD_AVATAR: avatarDownloader.process(masterSecret, intent); return;
}
}
}
public class ToastHandler extends Handler {
public void makeToast(String toast) {
Message message = this.obtainMessage();
message.obj = toast;
this.sendMessage(message);
}
@Override
public void handleMessage(Message message) {
Toast.makeText(SendReceiveService.this, (String)message.obj, Toast.LENGTH_LONG).show();
}
}
// private ServiceConnection serviceConnection = new ServiceConnection() {
// @Override
// public void onServiceConnected(ComponentName className, IBinder service) {
// KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
// MasterSecret masterSecret = keyCachingService.getMasterSecret();
//
// initializeWithMasterSecret(masterSecret);
//
// SendReceiveService.this.unbindService(this);
// }
//
// @Override
// public void onServiceDisconnected(ComponentName name) {}
// };
private class NewKeyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.w("SendReceiveService", "Got a MasterSecret broadcast...");
initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra(MASTER_SECRET_EXTRA));
}
}
/**
* This class receives broadcast notifications to clear the MasterSecret.
*
* We don't want to clear it immediately, since there are potentially jobs
* in the work queue which require the master secret. Instead, we reset a
* flag so that new incoming jobs will be evaluated as if no mastersecret is
* present.
*
* Then, we add a job to the end of the queue which actually clears the masterSecret
* value. That way all jobs before this moment will be processed correctly, and all
* jobs after this moment will be evaluated as if no mastersecret is present (and potentially
* held).
*
* When we go to actually clear the mastersecret, we ensure that the flag is still false.
* This allows a new mastersecret broadcast to come in correctly without us clobbering it.
*
*/
private class ClearKeyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.w("SendReceiveService", "Got a clear mastersecret broadcast...");
synchronized (workQueue) {
SendReceiveService.this.hasSecret = false;
workQueue.add(new Runnable() {
@Override
public void run() {
Log.w("SendReceiveService", "Running clear key work item...");
synchronized (workQueue) {
if (!SendReceiveService.this.hasSecret) {
Log.w("SendReceiveService", "Actually clearing key...");
SendReceiveService.this.masterSecret = null;
}
}
}
});
workQueue.notifyAll();
}
}
};
}

View File

@@ -3,19 +3,50 @@ package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsMessage;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.SmsSentJob;
import org.whispersystems.jobqueue.JobManager;
public class SmsDeliveryListener extends BroadcastReceiver {
private static final String TAG = SmsDeliveryListener.class.getSimpleName();
public static final String SENT_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SENT_SMS_ACTION";
public static final String DELIVERED_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DELIVERED_SMS_ACTION";
@Override
public void onReceive(Context context, Intent intent) {
if (SendReceiveService.SENT_SMS_ACTION.equals(intent.getAction())) {
intent.putExtra("ResultCode", this.getResultCode());
intent.setClass(context, SendReceiveService.class);
context.startService(intent);
} else if (SendReceiveService.DELIVERED_SMS_ACTION.equals(intent.getAction())) {
intent.putExtra("ResultCode", this.getResultCode());
intent.setClass(context, SendReceiveService.class);
context.startService(intent);
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
long messageId = intent.getLongExtra("message_id", -1);
switch (intent.getAction()) {
case SENT_SMS_ACTION:
int result = getResultCode();
jobManager.add(new SmsSentJob(context, messageId, SENT_SMS_ACTION, result));
break;
case DELIVERED_SMS_ACTION:
byte[] pdu = intent.getByteArrayExtra("pdu");
if (pdu == null) {
Log.w(TAG, "No PDU in delivery receipt!");
break;
}
SmsMessage message = SmsMessage.createFromPdu(pdu);
if (message == null) {
Log.w(TAG, "Delivery receipt failed to parse!");
break;
}
jobManager.add(new SmsSentJob(context, messageId, DELIVERED_SMS_ACTION, message.getStatus()));
break;
default:
Log.w(TAG, "Unknown action: " + intent.getAction());
}
}
}

View File

@@ -1,194 +0,0 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.transport.UniversalTransport;
import org.whispersystems.textsecure.crypto.UntrustedIdentityException;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
public class SmsSender {
private final Context context;
private final SystemStateListener systemStateListener;
private final ToastHandler toastHandler;
public SmsSender(Context context, SystemStateListener systemStateListener, ToastHandler toastHandler) {
this.context = context;
this.systemStateListener = systemStateListener;
this.toastHandler = toastHandler;
}
public void process(MasterSecret masterSecret, Intent intent) {
if (SendReceiveService.SEND_SMS_ACTION.equals(intent.getAction())) {
handleSendMessage(masterSecret, intent);
} else if (SendReceiveService.SENT_SMS_ACTION.equals(intent.getAction())) {
handleSentMessage(masterSecret, intent);
} else if (SendReceiveService.DELIVERED_SMS_ACTION.equals(intent.getAction())) {
handleDeliveredMessage(intent);
}
}
private void handleSendMessage(MasterSecret masterSecret, Intent intent) {
long messageId = intent.getLongExtra("message_id", -1);
UniversalTransport transport = new UniversalTransport(context, masterSecret);
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
EncryptingSmsDatabase.Reader reader = null;
SmsMessageRecord record;
Log.w("SmsSender", "Sending message: " + messageId);
try {
if (messageId != -1) reader = database.getMessage(masterSecret, messageId);
else reader = database.getOutgoingMessages(masterSecret);
while (reader != null && (record = reader.getNext()) != null) {
try {
database.markAsSending(record.getId());
transport.deliver(record);
} catch (InsecureFallbackApprovalException ifae) {
Log.w("SmsSender", ifae);
DatabaseFactory.getSmsDatabase(context).markAsPendingInsecureSmsFallback(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (SecureFallbackApprovalException sfae) {
Log.w("SmsSender", sfae);
DatabaseFactory.getSmsDatabase(context).markAsPendingSecureSmsFallback(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (UntrustedIdentityException e) {
Log.w("SmsSender", e);
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(e.getE164Number(), e.getIdentityKey());
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId());
} catch (UndeliverableMessageException ude) {
Log.w("SmsSender", ude);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (RetryLaterException rle) {
Log.w("SmsSender", rle);
DatabaseFactory.getSmsDatabase(context).markAsOutbox(record.getId());
if (systemStateListener.isConnected()) scheduleQuickRetryAlarm();
else systemStateListener.registerForConnectivityChange();
}
}
} finally {
if (reader != null)
reader.close();
}
}
private void handleSentMessage(MasterSecret masterSecret, Intent intent) {
long messageId = intent.getLongExtra("message_id", -1);
int result = intent.getIntExtra("ResultCode", -31337);
boolean upgraded = intent.getBooleanExtra("upgraded", false);
boolean push = intent.getBooleanExtra("push", false);
Log.w("SMSReceiverService", "Intent resultcode: " + result);
Log.w("SMSReceiverService", "Running sent callback: " + messageId);
if (result == Activity.RESULT_OK) {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
Cursor cursor = database.getMessage(messageId);
SmsDatabase.Reader reader = database.readerFor(cursor);
if (push) database.markAsPush(messageId);
if (upgraded) database.markAsSecure(messageId);
database.markAsSent(messageId);
SmsMessageRecord record = reader.getNext();
if (record != null && record.isEndSession()) {
Log.w("SmsSender", "Ending session...");
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
sessionStore.deleteAllSessions(record.getIndividualRecipient().getRecipientId());
SecurityEvent.broadcastSecurityUpdateEvent(context, record.getThreadId());
}
unregisterForRadioChanges();
} else if (result == SmsManager.RESULT_ERROR_NO_SERVICE || result == SmsManager.RESULT_ERROR_RADIO_OFF) {
DatabaseFactory.getSmsDatabase(context).markAsOutbox(messageId);
toastHandler
.obtainMessage(0, context.getString(R.string.SmsReceiver_currently_unable_to_send_your_sms_message))
.sendToTarget();
registerForRadioChanges();
} else {
long threadId = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageId);
Recipients recipients = DatabaseFactory.getThreadDatabase(context).getRecipientsForThreadId(threadId);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
unregisterForRadioChanges();
}
}
private void handleDeliveredMessage(Intent intent) {
long messageId = intent.getLongExtra("message_id", -1);
byte[] pdu = intent.getByteArrayExtra("pdu");
SmsMessage message = SmsMessage.createFromPdu(pdu);
if (message == null) {
return;
}
DatabaseFactory.getSmsDatabase(context).markStatus(messageId, message.getStatus());
}
private void registerForRadioChanges() {
if (systemStateListener.isConnected()) systemStateListener.registerForRadioChange();
else systemStateListener.registerForConnectivityChange();
}
private void unregisterForRadioChanges() {
systemStateListener.unregisterForConnectivityChange();
}
private void scheduleQuickRetryAlarm() {
((AlarmManager)context.getSystemService(Context.ALARM_SERVICE))
.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (30 * 1000),
PendingIntent.getService(context, 0,
new Intent(SendReceiveService.SEND_SMS_ACTION,
null, context, SendReceiveService.class),
PendingIntent.FLAG_UPDATE_CURRENT));
}
}

View File

@@ -1,95 +0,0 @@
package org.thoughtcrime.securesms.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.util.Log;
public class SystemStateListener {
private final TelephonyListener telephonyListener = new TelephonyListener();
private final ConnectivityListener connectivityListener = new ConnectivityListener();
private final Context context;
private final TelephonyManager telephonyManager;
private final ConnectivityManager connectivityManager;
public SystemStateListener(Context context) {
this.context = context.getApplicationContext();
this.telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
this.connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
public void registerForRadioChange() {
Log.w("SystemStateListener", "Registering for radio changes...");
unregisterForConnectivityChange();
telephonyManager.listen(telephonyListener, PhoneStateListener.LISTEN_SERVICE_STATE);
}
public void registerForConnectivityChange() {
Log.w("SystemStateListener", "Registering for any connectivity changes...");
unregisterForConnectivityChange();
telephonyManager.listen(telephonyListener, PhoneStateListener.LISTEN_SERVICE_STATE);
context.registerReceiver(connectivityListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
public void unregisterForConnectivityChange() {
telephonyManager.listen(telephonyListener, 0);
try {
context.unregisterReceiver(connectivityListener);
} catch (IllegalArgumentException iae) {
Log.w("SystemStateListener", iae);
}
}
public boolean isConnected() {
return
connectivityManager.getActiveNetworkInfo() != null &&
connectivityManager.getActiveNetworkInfo().isConnected();
}
private void sendSmsOutbox(Context context) {
Intent smsSenderIntent = new Intent(SendReceiveService.SEND_SMS_ACTION, null, context,
SendReceiveService.class);
context.startService(smsSenderIntent);
}
private void sendMmsOutbox(Context context) {
Intent mmsSenderIntent = new Intent(SendReceiveService.SEND_MMS_ACTION, null, context,
SendReceiveService.class);
context.startService(mmsSenderIntent);
}
private class TelephonyListener extends PhoneStateListener {
@Override
public void onServiceStateChanged(ServiceState state) {
if (state.getState() == ServiceState.STATE_IN_SERVICE) {
Log.w("SystemStateListener", "In service, sending sms/mms outboxes...");
sendSmsOutbox(context);
sendMmsOutbox(context);
}
}
}
private class ConnectivityListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
if (connectivityManager.getActiveNetworkInfo() != null &&
connectivityManager.getActiveNetworkInfo().isConnected())
{
Log.w("SystemStateListener", "Got connectivity action: " + intent.toString());
sendSmsOutbox(context);
sendMmsOutbox(context);
}
}
}
}
}