mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 00:37:47 +00:00
Refactor MMS send/download to be synchronous.
1) Make the radio change a synchronous action with a timeout. 2) Move the send logic into an MmsTransport, in preparation for UniversalTransport composition. 3) Move the download logic into a synchronous receiver.
This commit is contained in:
parent
53803630d4
commit
fd045f2354
@ -240,6 +240,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
notifyConversationListeners(getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void markAsSending(long messageId) {
|
||||
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
|
||||
notifyConversationListeners(getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void markAsSent(long messageId, byte[] mmsId, long status) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
|
@ -349,7 +349,7 @@ public class ThreadDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public Recipients getRecipientsForThreadId(Context context, long threadId) {
|
||||
public Recipients getRecipientsForThreadId(long threadId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
|
@ -153,7 +153,7 @@ public class MmsCommunication {
|
||||
int ipAddress = Conversions.byteArrayToIntLittleEndian(ipAddressBytes, 0);
|
||||
ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
if (!manager.requestRouteToHost(MmsDownloader.TYPE_MOBILE_MMS, ipAddress))
|
||||
if (!manager.requestRouteToHost(MmsRadio.TYPE_MOBILE_MMS, ipAddress))
|
||||
throw new IOException("Connection manager could not obtain route to host.");
|
||||
}
|
||||
}
|
||||
|
143
src/org/thoughtcrime/securesms/mms/MmsRadio.java
Normal file
143
src/org/thoughtcrime/securesms/mms/MmsRadio.java
Normal file
@ -0,0 +1,143 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class MmsRadio {
|
||||
|
||||
private static MmsRadio instance;
|
||||
|
||||
public static synchronized MmsRadio getInstance(Context context) {
|
||||
if (instance == null)
|
||||
instance = new MmsRadio(context);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
private static final String FEATURE_ENABLE_MMS = "enableMMS";
|
||||
private static final int APN_ALREADY_ACTIVE = 0;
|
||||
public static final int TYPE_MOBILE_MMS = 2;
|
||||
|
||||
private final Context context;
|
||||
|
||||
private ConnectivityManager connectivityManager;
|
||||
private ConnectivityListener connectivityListener;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private int connectedCounter = 0;
|
||||
|
||||
private MmsRadio(Context context) {
|
||||
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
this.context = context;
|
||||
this.connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
this.wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connection");
|
||||
this.wakeLock.setReferenceCounted(true);
|
||||
}
|
||||
|
||||
public String getApnInformation() {
|
||||
return connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS).getExtraInfo();
|
||||
}
|
||||
|
||||
public synchronized void disconnect() {
|
||||
Log.w("MmsRadio", "MMS Radio Disconnect Called...");
|
||||
wakeLock.release();
|
||||
connectedCounter--;
|
||||
|
||||
Log.w("MmsRadio", "Reference count: " + connectedCounter);
|
||||
|
||||
if (connectedCounter == 0) {
|
||||
Log.w("MmsRadio", "Turning off MMS radio...");
|
||||
connectivityManager.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
|
||||
|
||||
if (connectivityListener != null) {
|
||||
Log.w("MmsRadio", "Unregistering receiver...");
|
||||
context.unregisterReceiver(connectivityListener);
|
||||
connectivityListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void connect() throws MmsRadioException {
|
||||
int status = connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
|
||||
FEATURE_ENABLE_MMS);
|
||||
|
||||
Log.w("MmsRadio", "startUsingNetworkFeature status: " + status);
|
||||
|
||||
if (status == APN_ALREADY_ACTIVE) {
|
||||
wakeLock.acquire();
|
||||
connectedCounter++;
|
||||
return;
|
||||
} else {
|
||||
wakeLock.acquire();
|
||||
connectedCounter++;
|
||||
|
||||
if (connectivityListener == null) {
|
||||
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
connectivityListener = new ConnectivityListener();
|
||||
context.registerReceiver(connectivityListener, filter);
|
||||
}
|
||||
|
||||
Util.wait(this, 30000);
|
||||
|
||||
if (!isConnected()) {
|
||||
Log.w("MmsRadio", "Got back from connectivity wait, and not connected...");
|
||||
disconnect();
|
||||
throw new MmsRadioException("Unable to successfully enable MMS radio.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isConnected() {
|
||||
NetworkInfo info = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
|
||||
|
||||
if ((info == null) || (info.getType() != TYPE_MOBILE_MMS) || !info.isConnected())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isConnectivityPossible() {
|
||||
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
|
||||
|
||||
return networkInfo != null && networkInfo.isAvailable();
|
||||
}
|
||||
|
||||
private boolean isConnectivityFailure() {
|
||||
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
|
||||
|
||||
return networkInfo == null || networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED;
|
||||
}
|
||||
|
||||
private synchronized void issueConnectivityChange() {
|
||||
if (isConnected()) {
|
||||
Log.w("MmsRadio", "Notifying connected...");
|
||||
notifyAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isConnected() && (isConnectivityFailure() || !isConnectivityPossible())) {
|
||||
Log.w("MmsRadio", "Notifying not connected...");
|
||||
notifyAll();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectivityListener extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("MmsRadio", "Got connectivity change...");
|
||||
issueConnectivityChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
public class MmsRadioException extends Throwable {
|
||||
public MmsRadioException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -24,10 +24,8 @@ import android.util.Log;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.thoughtcrime.securesms.service.MmscProcessor;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -39,7 +37,9 @@ import ws.com.google.android.mms.pdu.SendConf;
|
||||
|
||||
public class MmsSendHelper extends MmsCommunication {
|
||||
|
||||
private static byte[] makePost(Context context, MmsConnectionParameters parameters, byte[] mms) throws ClientProtocolException, IOException {
|
||||
private static byte[] makePost(Context context, MmsConnectionParameters parameters, byte[] mms)
|
||||
throws IOException
|
||||
{
|
||||
AndroidHttpClient client = null;
|
||||
|
||||
try {
|
||||
@ -114,7 +114,7 @@ public class MmsSendHelper extends MmsCommunication {
|
||||
public static boolean hasNecessaryApnDetails(Context context) {
|
||||
try {
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
String apn = connectivityManager.getNetworkInfo(MmscProcessor.TYPE_MOBILE_MMS).getExtraInfo();
|
||||
String apn = connectivityManager.getNetworkInfo(MmsRadio.TYPE_MOBILE_MMS).getExtraInfo();
|
||||
|
||||
MmsCommunication.getMmsConnectionParameters(context, apn, true);
|
||||
return true;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* 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
|
||||
@ -30,6 +30,8 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
|
||||
import org.thoughtcrime.securesms.mms.MmsDownloadHelper;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadio;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||
import org.thoughtcrime.securesms.mms.MmsSendHelper;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
@ -45,48 +47,26 @@ import ws.com.google.android.mms.pdu.PduComposer;
|
||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||
import ws.com.google.android.mms.pdu.RetrieveConf;
|
||||
|
||||
public class MmsDownloader extends MmscProcessor {
|
||||
public class MmsDownloader {
|
||||
|
||||
private final LinkedList<DownloadItem> pendingMessages = new LinkedList<DownloadItem>();
|
||||
private final Context context;
|
||||
private final SendReceiveService.ToastHandler toastHandler;
|
||||
private final MmsRadio radio;
|
||||
|
||||
public MmsDownloader(Context context, SendReceiveService.ToastHandler toastHandler) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.toastHandler = toastHandler;
|
||||
this.radio = MmsRadio.getInstance(context);
|
||||
}
|
||||
|
||||
public void process(MasterSecret masterSecret, Intent intent) {
|
||||
if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_ACTION)) {
|
||||
boolean isCdma = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
|
||||
DownloadItem item = new DownloadItem(masterSecret, !isCdma, false,
|
||||
intent.getLongExtra("message_id", -1),
|
||||
intent.getLongExtra("thread_id", -1),
|
||||
intent.getBooleanExtra("automatic", false),
|
||||
intent.getStringExtra("content_location"),
|
||||
intent.getByteArrayExtra("transaction_id"));
|
||||
|
||||
handleDownloadMmsAction(item);
|
||||
} else if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION)) {
|
||||
handleConnectivityChange();
|
||||
handleDownloadMms(masterSecret, intent);
|
||||
} else if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION)) {
|
||||
handleMmsPendingApnDownloads(masterSecret);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDownloadMmsAction(DownloadItem item) {
|
||||
if (!isConnectivityPossible()) {
|
||||
Log.w("MmsDownloader", "No MMS connectivity available!");
|
||||
handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY,
|
||||
context.getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later));
|
||||
return;
|
||||
}
|
||||
|
||||
DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Status.DOWNLOAD_CONNECTING);
|
||||
|
||||
if (item.useMmsRadioMode()) downloadMmsWithRadioChange(item);
|
||||
else downloadMms(item);
|
||||
}
|
||||
|
||||
private void handleMmsPendingApnDownloads(MasterSecret masterSecret) {
|
||||
if (!MmsDownloadHelper.isMmsConnectionParametersAvailable(context, null, false))
|
||||
return;
|
||||
@ -109,86 +89,125 @@ public class MmsDownloader extends MmscProcessor {
|
||||
stalledMmsReader.close();
|
||||
}
|
||||
|
||||
private void downloadMmsWithRadioChange(DownloadItem item) {
|
||||
Log.w("MmsDownloader", "Handling MMS download with radio change...");
|
||||
pendingMessages.add(item);
|
||||
issueConnectivityRequest();
|
||||
}
|
||||
private void handleDownloadMms(MasterSecret masterSecret, Intent intent) {
|
||||
long messageId = intent.getLongExtra("message_id", -1);
|
||||
long threadId = intent.getLongExtra("thread_id", -1);
|
||||
byte[] transactionId = intent.getByteArrayExtra("transaction_id");
|
||||
String contentLocation = intent.getStringExtra("content_location");
|
||||
boolean automatic = intent.getBooleanExtra("automatic", false);
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
|
||||
private void downloadMms(DownloadItem item) {
|
||||
Log.w("MmsDownloadService", "Handling actual MMS download...");
|
||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING);
|
||||
|
||||
try {
|
||||
RetrieveConf retrieved = MmsDownloadHelper.retrieveMms(context, item.getContentLocation(),
|
||||
getApnInformation(),
|
||||
item.useMmsRadioMode(),
|
||||
item.proxyRequestIfPossible());
|
||||
|
||||
for (int i=0;i<retrieved.getBody().getPartsNum();i++) {
|
||||
Log.w("MmsDownloader", "Got MMS part of content-type: " +
|
||||
new String(retrieved.getBody().getPart(i).getContentType()));
|
||||
if (isCdmaNetwork()) {
|
||||
Log.w("MmsDownloader", "Connecting directly...");
|
||||
try {
|
||||
retrieveAndStore(masterSecret, messageId, threadId, contentLocation,
|
||||
transactionId, false, false);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Log.w("MmsDownloader", e);
|
||||
}
|
||||
}
|
||||
|
||||
storeRetrievedMms(mmsDatabase, item, retrieved);
|
||||
sendRetrievedAcknowledgement(item);
|
||||
Log.w("MmsDownloader", "Changing radio to MMS mode..");
|
||||
radio.connect();
|
||||
|
||||
Log.w("MmsDownloader", "Downloading in MMS mode without proxy...");
|
||||
|
||||
try {
|
||||
retrieveAndStore(masterSecret, messageId, threadId, contentLocation,
|
||||
transactionId, true, false);
|
||||
radio.disconnect();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Log.w("MmsDownloader", e);
|
||||
}
|
||||
|
||||
Log.w("MmsDownloader", "Downloading in MMS mode with proxy...");
|
||||
|
||||
try {
|
||||
retrieveAndStore(masterSecret, messageId, threadId,
|
||||
contentLocation, transactionId, true, true);
|
||||
radio.disconnect();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Log.w("MmsDownloader", e);
|
||||
radio.disconnect();
|
||||
handleDownloadError(masterSecret, messageId, threadId,
|
||||
MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
|
||||
context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider),
|
||||
automatic);
|
||||
}
|
||||
|
||||
} catch (ApnUnavailableException e) {
|
||||
Log.w("MmsDownloader", e);
|
||||
handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
|
||||
context.getString(R.string.MmsDownloader_error_reading_mms_settings));
|
||||
} catch (IOException e) {
|
||||
Log.w("MmsDownloader", e);
|
||||
if (!item.useMmsRadioMode() && !item.proxyRequestIfPossible()) {
|
||||
Log.w("MmsDownloader", "Falling back to just radio mode...");
|
||||
scheduleDownloadWithRadioMode(item);
|
||||
} else if (!item.proxyRequestIfPossible()) {
|
||||
Log.w("MmsDownloadeR", "Falling back to radio mode and proxy...");
|
||||
scheduleDownloadWithRadioModeAndProxy(item);
|
||||
} else {
|
||||
handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
|
||||
context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider));
|
||||
}
|
||||
handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
|
||||
context.getString(R.string.MmsDownloader_error_reading_mms_settings), automatic);
|
||||
} catch (MmsException e) {
|
||||
Log.w("MmsDownloader", e);
|
||||
handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
|
||||
context.getString(R.string.MmsDownloader_error_storing_mms));
|
||||
handleDownloadError(masterSecret, messageId, threadId,
|
||||
MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
|
||||
context.getString(R.string.MmsDownloader_error_storing_mms),
|
||||
automatic);
|
||||
} catch (MmsRadioException e) {
|
||||
Log.w("MmsDownloader", e);
|
||||
handleDownloadError(masterSecret, messageId, threadId,
|
||||
MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
|
||||
context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider),
|
||||
automatic);
|
||||
}
|
||||
}
|
||||
|
||||
private void storeRetrievedMms(MmsDatabase mmsDatabase, DownloadItem item, RetrieveConf retrieved)
|
||||
private void retrieveAndStore(MasterSecret masterSecret, long messageId, long threadId,
|
||||
String contentLocation, byte[] transactionId,
|
||||
boolean radioEnabled, boolean useProxy)
|
||||
throws IOException, MmsException
|
||||
{
|
||||
RetrieveConf retrieved = MmsDownloadHelper.retrieveMms(context, contentLocation,
|
||||
radio.getApnInformation(),
|
||||
radioEnabled, useProxy);
|
||||
|
||||
storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieved);
|
||||
sendRetrievedAcknowledgement(transactionId, radioEnabled, useProxy);
|
||||
}
|
||||
|
||||
private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation,
|
||||
long messageId, long threadId, RetrieveConf retrieved)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Pair<Long, Long> messageAndThreadId;
|
||||
|
||||
if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) {
|
||||
messageAndThreadId = mmsDatabase.insertSecureMessageInbox(item.getMasterSecret(), retrieved,
|
||||
item.getContentLocation(),
|
||||
item.getThreadId());
|
||||
messageAndThreadId = database.insertSecureMessageInbox(masterSecret, retrieved,
|
||||
contentLocation, threadId);
|
||||
|
||||
if (item.getMasterSecret() != null)
|
||||
DecryptingQueue.scheduleDecryption(context, item.getMasterSecret(), messageAndThreadId.first,
|
||||
if (masterSecret != null)
|
||||
DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first,
|
||||
messageAndThreadId.second, retrieved);
|
||||
|
||||
} else {
|
||||
messageAndThreadId = mmsDatabase.insertMessageInbox(item.getMasterSecret(), retrieved,
|
||||
item.getContentLocation(),
|
||||
item.getThreadId());
|
||||
messageAndThreadId = database.insertMessageInbox(masterSecret, retrieved,
|
||||
contentLocation, threadId);
|
||||
}
|
||||
|
||||
mmsDatabase.delete(item.getMessageId());
|
||||
MessageNotifier.updateNotification(context, item.getMasterSecret(), messageAndThreadId.second);
|
||||
database.delete(messageId);
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
}
|
||||
|
||||
private void sendRetrievedAcknowledgement(DownloadItem item) {
|
||||
private void sendRetrievedAcknowledgement(byte[] transactionId,
|
||||
boolean usingRadio,
|
||||
boolean useProxy)
|
||||
{
|
||||
try {
|
||||
NotifyRespInd notifyResponse = new NotifyRespInd(PduHeaders.CURRENT_MMS_VERSION,
|
||||
item.getTransactionId(),
|
||||
transactionId,
|
||||
PduHeaders.STATUS_RETRIEVED);
|
||||
|
||||
MmsSendHelper.sendNotificationReceived(context, new PduComposer(context, notifyResponse).make(),
|
||||
getApnInformation(), item.useMmsRadioMode(),
|
||||
item.proxyRequestIfPossible());
|
||||
radio.getApnInformation(), usingRadio, useProxy);
|
||||
} catch (InvalidHeaderValueException e) {
|
||||
Log.w("MmsDownloader", e);
|
||||
} catch (IOException e) {
|
||||
@ -196,116 +215,25 @@ public class MmsDownloader extends MmscProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleConnectivityChange() {
|
||||
LinkedList<DownloadItem> downloadItems = (LinkedList<DownloadItem>)pendingMessages.clone();
|
||||
|
||||
if (isConnected()) {
|
||||
pendingMessages.clear();
|
||||
|
||||
for (DownloadItem item : downloadItems) {
|
||||
downloadMms(item);
|
||||
}
|
||||
|
||||
if (pendingMessages.isEmpty())
|
||||
finishConnectivity();
|
||||
|
||||
} else if (!isConnected() && (!isConnectivityPossible() || isConnectivityFailure())) {
|
||||
pendingMessages.clear();
|
||||
handleDownloadError(downloadItems, MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY,
|
||||
context.getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later));
|
||||
finishConnectivity();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDownloadError(List<DownloadItem> items, int downloadStatus, String error) {
|
||||
for (DownloadItem item : items) {
|
||||
handleDownloadError(item, downloadStatus, error);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDownloadError(DownloadItem item, int downloadStatus, String error) {
|
||||
private void handleDownloadError(MasterSecret masterSecret, long messageId, long threadId,
|
||||
int downloadStatus, String error, boolean automatic)
|
||||
{
|
||||
MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
|
||||
db.markDownloadState(item.getMessageId(), downloadStatus);
|
||||
|
||||
if (item.isAutomatic()) {
|
||||
db.markIncomingNotificationReceived(item.getThreadId());
|
||||
MessageNotifier.updateNotification(context, item.getMasterSecret(), item.getThreadId());
|
||||
db.markDownloadState(messageId, downloadStatus);
|
||||
|
||||
if (automatic) {
|
||||
db.markIncomingNotificationReceived(threadId);
|
||||
MessageNotifier.updateNotification(context, masterSecret, threadId);
|
||||
}
|
||||
|
||||
toastHandler.makeToast(error);
|
||||
}
|
||||
|
||||
private void scheduleDownloadWithRadioMode(DownloadItem item) {
|
||||
item.mmsRadioMode = true;
|
||||
handleDownloadMmsAction(item);
|
||||
}
|
||||
|
||||
private void scheduleDownloadWithRadioModeAndProxy(DownloadItem item) {
|
||||
item.mmsRadioMode = true;
|
||||
item.proxyIfPossible = true;
|
||||
handleDownloadMmsAction(item);
|
||||
}
|
||||
|
||||
private static class DownloadItem {
|
||||
private final MasterSecret masterSecret;
|
||||
private boolean mmsRadioMode;
|
||||
private boolean proxyIfPossible;
|
||||
|
||||
private long threadId;
|
||||
private long messageId;
|
||||
private byte[] transactionId;
|
||||
private String contentLocation;
|
||||
private boolean automatic;
|
||||
|
||||
public DownloadItem(MasterSecret masterSecret, boolean mmsRadioMode, boolean proxyIfPossible,
|
||||
long messageId, long threadId, boolean automatic, String contentLocation,
|
||||
byte[] transactionId)
|
||||
{
|
||||
this.masterSecret = masterSecret;
|
||||
this.mmsRadioMode = mmsRadioMode;
|
||||
this.proxyIfPossible = proxyIfPossible;
|
||||
this.threadId = threadId;
|
||||
this.messageId = messageId;
|
||||
this.contentLocation = contentLocation;
|
||||
this.transactionId = transactionId;
|
||||
this.automatic = automatic;
|
||||
}
|
||||
|
||||
public long getThreadId() {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
public long getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public String getContentLocation() {
|
||||
return contentLocation;
|
||||
}
|
||||
|
||||
public byte[] getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
public MasterSecret getMasterSecret() {
|
||||
return masterSecret;
|
||||
}
|
||||
|
||||
public boolean proxyRequestIfPossible() {
|
||||
return proxyIfPossible;
|
||||
}
|
||||
|
||||
public boolean useMmsRadioMode() {
|
||||
return mmsRadioMode;
|
||||
}
|
||||
|
||||
public boolean isAutomatic() {
|
||||
return automatic;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConnectivityAction() {
|
||||
return SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION;
|
||||
private boolean isCdmaNetwork() {
|
||||
return ((TelephonyManager)context
|
||||
.getSystemService(Context.TELEPHONY_SERVICE))
|
||||
.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* 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
|
||||
@ -18,250 +18,66 @@ package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.SessionCipher;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.mms.MmsSendHelper;
|
||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
import org.thoughtcrime.securesms.transport.MmsTransport;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
|
||||
import ws.com.google.android.mms.ContentType;
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.PduComposer;
|
||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
import ws.com.google.android.mms.pdu.SendConf;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
public class MmsSender {
|
||||
|
||||
public class MmsSender extends MmscProcessor {
|
||||
|
||||
private final LinkedList<SendItem> pendingMessages = new LinkedList<SendItem>();
|
||||
private final Context context;
|
||||
private final ToastHandler toastHandler;
|
||||
|
||||
public MmsSender(Context context, ToastHandler toastHandler) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.toastHandler = toastHandler;
|
||||
}
|
||||
|
||||
public void process(MasterSecret masterSecret, Intent intent) {
|
||||
Log.w("MmsSender", "Got intent action: " + intent.getAction());
|
||||
if (intent.getAction().equals(SendReceiveService.SEND_MMS_ACTION)) {
|
||||
long messageId = intent.getLongExtra("message_id", -1);
|
||||
boolean isCdma = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
|
||||
try {
|
||||
List<SendReq> sendRequests = getOutgoingMessages(masterSecret, messageId);
|
||||
|
||||
for (SendReq sendRequest : sendRequests) {
|
||||
handleSendMmsAction(new SendItem(masterSecret, sendRequest, messageId != -1, !isCdma, false));
|
||||
}
|
||||
|
||||
} catch (MmsException me) {
|
||||
Log.w("MmsSender", me);
|
||||
if (messageId != -1)
|
||||
database.markAsSentFailed(messageId);
|
||||
}
|
||||
} else if (intent.getAction().equals(SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION)) {
|
||||
handleConnectivityChange();
|
||||
handleSendMms(masterSecret, intent);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendMmsAction(SendItem item) {
|
||||
if (!isConnectivityPossible()) {
|
||||
if (item.targeted) {
|
||||
toastHandler
|
||||
.obtainMessage(0, context.getString(R.string.MmsSender_currently_unable_to_send_your_mms_message))
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.useMmsRadio) sendMmsMessageWithRadioChange(item);
|
||||
else sendMmsMessage(item);
|
||||
}
|
||||
|
||||
private void sendMmsMessageWithRadioChange(SendItem item) {
|
||||
Log.w("MmsSender", "Sending MMS with radio change..");
|
||||
pendingMessages.add(item);
|
||||
issueConnectivityRequest();
|
||||
}
|
||||
|
||||
private void sendMmsMessage(SendItem item) {
|
||||
Log.w("MmsSender", "Sending MMS SendItem...");
|
||||
MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
|
||||
String number = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
|
||||
long messageId = item.request.getDatabaseMessageId();
|
||||
long messageBox = item.request.getDatabaseMessageBox();
|
||||
SendReq request = item.request;
|
||||
|
||||
|
||||
if (MmsDatabase.Types.isSecureType(messageBox)) {
|
||||
request = getEncryptedMms(item.masterSecret, request, messageId);
|
||||
}
|
||||
|
||||
if (number != null && number.trim().length() != 0) {
|
||||
request.setFrom(new EncodedStringValue(number));
|
||||
}
|
||||
private void handleSendMms(MasterSecret masterSecret, Intent intent) {
|
||||
long messageId = intent.getLongExtra("message_id", -1);
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
|
||||
MmsTransport transport = new MmsTransport(context, masterSecret);
|
||||
|
||||
try {
|
||||
SendConf conf = MmsSendHelper.sendMms(context, new PduComposer(context, request).make(),
|
||||
getApnInformation(), item.useMmsRadio, item.useProxyIfAvailable);
|
||||
SendReq[] messages = database.getOutgoingMessages(masterSecret, messageId);
|
||||
|
||||
for (int i=0;i<request.getBody().getPartsNum();i++) {
|
||||
Log.w("MmsSender", "Sent MMS part of content-type: " + new String(request.getBody().getPart(i).getContentType()));
|
||||
for (SendReq message : messages) {
|
||||
try {
|
||||
Log.w("MmsSender", "Passing to MMS transport: " + message.getDatabaseMessageId());
|
||||
database.markAsSending(message.getDatabaseMessageId());
|
||||
Pair<byte[], Integer> result = transport.deliver(message);
|
||||
database.markAsSent(message.getDatabaseMessageId(), result.first, result.second);
|
||||
} catch (UndeliverableMessageException e) {
|
||||
Log.w("MmsSender", e);
|
||||
database.markAsSentFailed(message.getDatabaseMessageId());
|
||||
long threadId = database.getThreadIdForMessage(messageId);
|
||||
Recipients recipients = threads.getRecipientsForThreadId(threadId);
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
long threadId = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId);
|
||||
Recipients recipients = DatabaseFactory.getThreadDatabase(context).getRecipientsForThreadId(context, threadId);
|
||||
|
||||
if (conf == null) {
|
||||
db.markAsSentFailed(messageId);
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||
Log.w("MmsSender", "No M-Send.conf received in response to send.");
|
||||
return;
|
||||
} else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
|
||||
Log.w("MmsSender", "Got bad response: " + conf.getResponseStatus());
|
||||
db.updateResponseStatus(messageId, conf.getResponseStatus());
|
||||
db.markAsSentFailed(messageId);
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||
return;
|
||||
} else if (isInconsistentResponse(request, conf)) {
|
||||
db.markAsSentFailed(messageId);
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||
Log.w("MmsSender", "Got a response for the wrong transaction?");
|
||||
return;
|
||||
} else {
|
||||
Log.w("MmsSender", "Successful send! " + messageId);
|
||||
db.markAsSent(messageId, conf.getMessageId(), conf.getResponseStatus());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.w("MmsSender", ioe);
|
||||
if (!item.useMmsRadio) scheduleSendWithMmsRadio(item);
|
||||
else if (!item.useProxyIfAvailable) scheduleSendWithMmsRadioAndProxy(item);
|
||||
else db.markAsSentFailed(messageId);
|
||||
} catch (MmsException e) {
|
||||
Log.w("MmsSender", e);
|
||||
if (messageId != -1)
|
||||
database.markAsSentFailed(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
private List<SendReq> getOutgoingMessages(MasterSecret masterSecret, long messageId)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
return Arrays.asList(database.getOutgoingMessages(masterSecret, messageId));
|
||||
}
|
||||
|
||||
protected void handleConnectivityChange() {
|
||||
if (!isConnected()) {
|
||||
if ((!isConnectivityPossible() || isConnectivityFailure()) && !pendingMessages.isEmpty()) {
|
||||
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(pendingMessages.remove().request.getDatabaseMessageId());
|
||||
toastHandler.makeToast(context.getString(R.string.MmsSender_currently_unable_to_send_your_mms_message));
|
||||
Log.w("MmsSender", "Unable to send MMS.");
|
||||
finishConnectivity();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
List<SendItem> outgoing = (List<SendItem>)pendingMessages.clone();
|
||||
pendingMessages.clear();
|
||||
|
||||
for (SendItem item : outgoing) {
|
||||
sendMmsMessage(item);
|
||||
}
|
||||
|
||||
if (pendingMessages.isEmpty())
|
||||
finishConnectivity();
|
||||
}
|
||||
|
||||
private boolean isInconsistentResponse(SendReq send, SendConf response) {
|
||||
Log.w("MmsSenderService", "Comparing: " + Hex.toString(send.getTransactionId()));
|
||||
Log.w("MmsSenderService", "With: " + Hex.toString(response.getTransactionId()));
|
||||
return !Arrays.equals(send.getTransactionId(), response.getTransactionId());
|
||||
}
|
||||
|
||||
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) {
|
||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
SessionCipher cipher = new SessionCipher(context, masterSecret, new Recipient(null, recipient, null, null), new TextTransport());
|
||||
return cipher.encryptMessage(pduBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private SendReq getEncryptedMms(MasterSecret masterSecret, SendReq pdu, long messageId) {
|
||||
Log.w("MmsSender", "Sending Secure MMS.");
|
||||
EncodedStringValue[] encodedRecipient = pdu.getTo();
|
||||
String recipient = encodedRecipient[0].getString();
|
||||
byte[] pduBytes = new PduComposer(context, pdu).make();
|
||||
byte[] encryptedPduBytes = getEncryptedPdu(masterSecret, recipient, pduBytes);
|
||||
|
||||
PduBody body = new PduBody();
|
||||
PduPart part = new PduPart();
|
||||
SendReq encryptedPdu = new SendReq(pdu.getPduHeaders(), body);
|
||||
|
||||
part.setContentId((System.currentTimeMillis()+"").getBytes());
|
||||
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
|
||||
part.setName((System.currentTimeMillis()+"").getBytes());
|
||||
part.setData(encryptedPduBytes);
|
||||
body.addPart(part);
|
||||
encryptedPdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject()));
|
||||
encryptedPdu.setBody(body);
|
||||
|
||||
return encryptedPdu;
|
||||
}
|
||||
|
||||
private void scheduleSendWithMmsRadioAndProxy(SendItem item) {
|
||||
Log.w("MmsSender", "Falling back to sending MMS with radio and proxy...");
|
||||
item.useMmsRadio = true;
|
||||
item.useProxyIfAvailable = true;
|
||||
handleSendMmsAction(item);
|
||||
}
|
||||
|
||||
private void scheduleSendWithMmsRadio(SendItem item) {
|
||||
Log.w("MmsSender", "Falling back to sending MMS with radio only...");
|
||||
item.useMmsRadio = true;
|
||||
handleSendMmsAction(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConnectivityAction() {
|
||||
return SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION;
|
||||
}
|
||||
|
||||
private static class SendItem {
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
private boolean useMmsRadio;
|
||||
private boolean useProxyIfAvailable;
|
||||
private SendReq request;
|
||||
private boolean targeted;
|
||||
|
||||
public SendItem(MasterSecret masterSecret, SendReq request,
|
||||
boolean targeted, boolean useMmsRadio,
|
||||
boolean useProxyIfAvailable)
|
||||
{
|
||||
this.masterSecret = masterSecret;
|
||||
this.request = request;
|
||||
this.targeted = targeted;
|
||||
this.useMmsRadio = useMmsRadio;
|
||||
this.useProxyIfAvailable = useProxyIfAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,123 +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.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
public abstract class MmscProcessor {
|
||||
|
||||
private static final String FEATURE_ENABLE_MMS = "enableMMS";
|
||||
private static final int APN_ALREADY_ACTIVE = 0;
|
||||
public static final int TYPE_MOBILE_MMS = 2;
|
||||
|
||||
private ConnectivityManager connectivityManager;
|
||||
private ConnectivityListener connectivityListener;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
protected final Context context;
|
||||
|
||||
public MmscProcessor(Context context) {
|
||||
this.context = context;
|
||||
PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
|
||||
this.connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
this.wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connection");
|
||||
this.wakeLock.setReferenceCounted(false);
|
||||
}
|
||||
|
||||
protected String getApnInformation() {
|
||||
return connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS).getExtraInfo();
|
||||
}
|
||||
|
||||
protected boolean isConnected() {
|
||||
NetworkInfo info = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
|
||||
|
||||
Log.w("MmsService", "NetworkInfo: " + info);
|
||||
|
||||
if ((info == null) || (info.getType() != TYPE_MOBILE_MMS) || !info.isConnected())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract String getConnectivityAction();
|
||||
|
||||
protected void issueConnectivityRequest() {
|
||||
int status = connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
|
||||
|
||||
Log.w("MmscProcessor", "startUsingNetworkFeature status: " + status);
|
||||
|
||||
if (status == APN_ALREADY_ACTIVE) {
|
||||
issueConnectivityChange();
|
||||
} else if (connectivityListener == null) {
|
||||
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
connectivityListener = new ConnectivityListener();
|
||||
context.registerReceiver(connectivityListener, filter);
|
||||
|
||||
wakeLock.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isConnectivityFailure() {
|
||||
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
|
||||
|
||||
return networkInfo == null || networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED;
|
||||
}
|
||||
|
||||
|
||||
protected boolean isConnectivityPossible() {
|
||||
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS);
|
||||
Log.w("MmsService", "Got network info: " + networkInfo);
|
||||
|
||||
return networkInfo != null && networkInfo.isAvailable();
|
||||
}
|
||||
|
||||
protected void finishConnectivity() {
|
||||
Log.w("MmsService", "Calling stop using network feature!");
|
||||
connectivityManager.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
|
||||
|
||||
if (connectivityListener != null) {
|
||||
context.unregisterReceiver(connectivityListener);
|
||||
connectivityListener = null;
|
||||
}
|
||||
|
||||
if (this.wakeLock.isHeld())
|
||||
this.wakeLock.release();
|
||||
}
|
||||
|
||||
private void issueConnectivityChange() {
|
||||
Intent intent = new Intent(context, SendReceiveService.class);
|
||||
intent.setAction(getConnectivityAction());
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
private class ConnectivityListener extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w("MmsService", "Dispatching connectivity change...");
|
||||
issueConnectivityChange();
|
||||
Log.w("MmsService", "Dispatched...");
|
||||
}
|
||||
}
|
||||
}
|
@ -50,7 +50,6 @@ public class SendReceiveService extends Service {
|
||||
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 SEND_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_CONNECTIVITY_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";
|
||||
@ -101,11 +100,11 @@ public class SendReceiveService extends Service {
|
||||
scheduleIntent(SEND_SMS, intent);
|
||||
else if (intent.getAction().equals(DELIVERED_SMS_ACTION))
|
||||
scheduleIntent(SEND_SMS, intent);
|
||||
else if (intent.getAction().equals(SEND_MMS_ACTION) || intent.getAction().equals(SEND_MMS_CONNECTIVITY_ACTION))
|
||||
else if (intent.getAction().equals(SEND_MMS_ACTION))
|
||||
scheduleSecretRequiredIntent(SEND_MMS, intent);
|
||||
else if (intent.getAction().equals(RECEIVE_MMS_ACTION))
|
||||
scheduleIntent(RECEIVE_MMS, intent);
|
||||
else if (intent.getAction().equals(DOWNLOAD_MMS_ACTION) || intent.getAction().equals(DOWNLOAD_MMS_CONNECTIVITY_ACTION))
|
||||
else if (intent.getAction().equals(DOWNLOAD_MMS_ACTION))
|
||||
scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent);
|
||||
else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION))
|
||||
scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent);
|
||||
|
@ -100,7 +100,7 @@ public class SmsSender {
|
||||
registerForRadioChanges();
|
||||
} else {
|
||||
long threadId = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageId);
|
||||
Recipients recipients = DatabaseFactory.getThreadDatabase(context).getRecipientsForThreadId(context, threadId);
|
||||
Recipients recipients = DatabaseFactory.getThreadDatabase(context).getRecipientsForThreadId(threadId);
|
||||
|
||||
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||
|
@ -7,6 +7,8 @@ import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.telephony.ServiceState;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.MmsRadio;
|
||||
|
||||
public class SystemStateListener extends BroadcastReceiver {
|
||||
|
||||
public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE";
|
||||
@ -42,7 +44,7 @@ public class SystemStateListener extends BroadcastReceiver {
|
||||
ConnectivityManager connectivityManager
|
||||
= (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(MmscProcessor.TYPE_MOBILE_MMS);
|
||||
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(MmsRadio.TYPE_MOBILE_MMS);
|
||||
|
||||
if (networkInfo != null && networkInfo.isAvailable()) {
|
||||
sendMmsOutbox(context);
|
||||
|
157
src/org/thoughtcrime/securesms/transport/MmsTransport.java
Normal file
157
src/org/thoughtcrime/securesms/transport/MmsTransport.java
Normal file
@ -0,0 +1,157 @@
|
||||
package org.thoughtcrime.securesms.transport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.SessionCipher;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadio;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||
import org.thoughtcrime.securesms.mms.MmsSendHelper;
|
||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import ws.com.google.android.mms.ContentType;
|
||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.PduComposer;
|
||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
import ws.com.google.android.mms.pdu.SendConf;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
public class MmsTransport {
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final MmsRadio radio;
|
||||
|
||||
public MmsTransport(Context context, MasterSecret masterSecret) {
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.radio = MmsRadio.getInstance(context);
|
||||
}
|
||||
|
||||
public Pair<byte[], Integer> deliver(SendReq message) throws UndeliverableMessageException {
|
||||
try {
|
||||
if (isCdmaDevice()) {
|
||||
Log.w("MmsTransport", "Sending MMS directly without radio change...");
|
||||
try {
|
||||
return sendMms(message, false, false);
|
||||
} catch (IOException e) {
|
||||
Log.w("MmsTransport", e);
|
||||
}
|
||||
}
|
||||
|
||||
Log.w("MmsTransport", "Sending MMS with radio change...");
|
||||
radio.connect();
|
||||
|
||||
try {
|
||||
Pair<byte[], Integer> result = sendMms(message, true, false);
|
||||
radio.disconnect();
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
Log.w("MmsTransport", e);
|
||||
}
|
||||
|
||||
Log.w("MmsTransport", "Sending MMS with radio change and proxy...");
|
||||
|
||||
try {
|
||||
Pair<byte[], Integer> result = sendMms(message, true, true);
|
||||
radio.disconnect();
|
||||
return result;
|
||||
} catch (IOException ioe) {
|
||||
Log.w("MmsTransport", ioe);
|
||||
radio.disconnect();
|
||||
throw new UndeliverableMessageException(ioe);
|
||||
}
|
||||
|
||||
} catch (MmsRadioException mre) {
|
||||
Log.w("MmsTransport", mre);
|
||||
throw new UndeliverableMessageException(mre);
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<byte[], Integer> sendMms(SendReq message, boolean usingMmsRadio, boolean useProxy)
|
||||
throws IOException, UndeliverableMessageException
|
||||
{
|
||||
String number = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
|
||||
|
||||
if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) {
|
||||
message = getEncryptedMessage(message);
|
||||
}
|
||||
|
||||
if (number != null && number.trim().length() != 0) {
|
||||
message.setFrom(new EncodedStringValue(number));
|
||||
}
|
||||
|
||||
SendConf conf = MmsSendHelper.sendMms(context, new PduComposer(context, message).make(),
|
||||
radio.getApnInformation(), usingMmsRadio, useProxy);
|
||||
|
||||
for (int i=0;i<message.getBody().getPartsNum();i++) {
|
||||
Log.w("MmsSender", "Sent MMS part of content-type: " + new String(message.getBody().getPart(i).getContentType()));
|
||||
}
|
||||
|
||||
if (conf == null) {
|
||||
throw new UndeliverableMessageException("No M-Send.conf received in response to send.");
|
||||
} else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
|
||||
throw new UndeliverableMessageException("Got bad response: " + conf.getResponseStatus());
|
||||
} else if (isInconsistentResponse(message, conf)) {
|
||||
throw new UndeliverableMessageException("Mismatched response!");
|
||||
} else {
|
||||
return new Pair<byte[], Integer>(conf.getMessageId(), conf.getResponseStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private SendReq getEncryptedMessage(SendReq pdu) {
|
||||
EncodedStringValue[] encodedRecipient = pdu.getTo();
|
||||
String recipient = encodedRecipient[0].getString();
|
||||
byte[] pduBytes = new PduComposer(context, pdu).make();
|
||||
byte[] encryptedPduBytes = getEncryptedPdu(masterSecret, recipient, pduBytes);
|
||||
|
||||
PduBody body = new PduBody();
|
||||
PduPart part = new PduPart();
|
||||
SendReq encryptedPdu = new SendReq(pdu.getPduHeaders(), body);
|
||||
|
||||
part.setContentId((System.currentTimeMillis()+"").getBytes());
|
||||
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
|
||||
part.setName((System.currentTimeMillis()+"").getBytes());
|
||||
part.setData(encryptedPduBytes);
|
||||
body.addPart(part);
|
||||
encryptedPdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject()));
|
||||
encryptedPdu.setBody(body);
|
||||
|
||||
return encryptedPdu;
|
||||
}
|
||||
|
||||
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) {
|
||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
SessionCipher cipher = new SessionCipher(context, masterSecret,
|
||||
new Recipient(null, recipient, null, null),
|
||||
new TextTransport());
|
||||
|
||||
return cipher.encryptMessage(pduBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInconsistentResponse(SendReq message, SendConf response) {
|
||||
Log.w("MmsTransport", "Comparing: " + Hex.toString(message.getTransactionId()));
|
||||
Log.w("MmsTransport", "With: " + Hex.toString(response.getTransactionId()));
|
||||
return !Arrays.equals(message.getTransactionId(), response.getTransactionId());
|
||||
}
|
||||
|
||||
private boolean isCdmaDevice() {
|
||||
return ((TelephonyManager)context
|
||||
.getSystemService(Context.TELEPHONY_SERVICE))
|
||||
.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
|
||||
}
|
||||
|
||||
}
|
@ -25,6 +25,8 @@ import android.widget.EditText;
|
||||
import android.os.Build;
|
||||
import android.provider.Telephony;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.MmsRadio;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
@ -134,6 +136,14 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
public static void wait(Object lock, int timeout) {
|
||||
try {
|
||||
lock.wait(timeout);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new AssertionError(ie);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isDefaultSmsProvider(Context context){
|
||||
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) ||
|
||||
|
Loading…
x
Reference in New Issue
Block a user