mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-26 00:58:51 +00:00
Transition the outbound pipeline to JobManager jobs.
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user