session-android/src/org/thoughtcrime/securesms/service/SendReceiveService.java
Moxie Marlinspike a3f1d9cdfd Beginning of libtextsecure refactor.
1) Break out appropriate components.

2) Switch the incoming pipeline from SendReceiveService to
   the JobManager.
2014-11-12 15:21:32 -08:00

364 lines
14 KiB
Java

/**
* 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();
}
}
};
}