mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-25 09:37:25 +00:00

committed by
Moxie Marlinspike

parent
dfda2f733c
commit
427c9a6b21
@@ -305,6 +305,10 @@
|
|||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:authorities="org.thoughtcrime.provider.securesms" />
|
android:authorities="org.thoughtcrime.provider.securesms" />
|
||||||
|
|
||||||
|
<provider android:name=".providers.MmsBodyProvider"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:authorities="org.thoughtcrime.provider.securesms.mms" />
|
||||||
|
|
||||||
<receiver android:name=".service.RegistrationNotifier"
|
<receiver android:name=".service.RegistrationNotifier"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -330,5 +334,6 @@
|
|||||||
<action android:name="org.thoughtcrime.securesms.MessageNotifier.DELETE_REMINDER_ACTION"/>
|
<action android:name="org.thoughtcrime.securesms.MessageNotifier.DELETE_REMINDER_ACTION"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@@ -123,8 +123,8 @@ dependencyVerification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 21
|
compileSdkVersion 22
|
||||||
buildToolsVersion '21.1.2'
|
buildToolsVersion '22.0.1'
|
||||||
|
|
||||||
dexOptions {
|
dexOptions {
|
||||||
javaMaxHeapSize "4g"
|
javaMaxHeapSize "4g"
|
||||||
|
@@ -113,6 +113,8 @@
|
|||||||
<string name="ConversationActivity_get_with_it">Get with it: %s</string>
|
<string name="ConversationActivity_get_with_it">Get with it: %s</string>
|
||||||
<string name="ConversationActivity_lets_use_this_to_chat">Let\'s use this to chat: %s</string>
|
<string name="ConversationActivity_lets_use_this_to_chat">Let\'s use this to chat: %s</string>
|
||||||
<string name="ConversationActivity_error_leaving_group">Error leaving group...</string>
|
<string name="ConversationActivity_error_leaving_group">Error leaving group...</string>
|
||||||
|
<string name="ConversationActivity_mms_not_supported_title">MMS not supported</string>
|
||||||
|
<string name="ConversationActivity_mms_not_supported_message">This message cannot be sent since your carrier doesn\'t support MMS.</string>
|
||||||
|
|
||||||
<!-- ConversationFragment -->
|
<!-- ConversationFragment -->
|
||||||
<string name="ConversationFragment_message_details">Message details</string>
|
<string name="ConversationFragment_message_details">Message details</string>
|
||||||
|
@@ -73,7 +73,6 @@ import org.thoughtcrime.securesms.mms.MediaTooLargeException;
|
|||||||
import org.thoughtcrime.securesms.mms.MmsMediaConstraints;
|
import org.thoughtcrime.securesms.mms.MmsMediaConstraints;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
|
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
@@ -414,7 +413,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
Context self = ConversationActivity.this;
|
Context self = ConversationActivity.this;
|
||||||
try {
|
try {
|
||||||
byte[] groupId = GroupUtil.getDecodedId(getRecipients().getPrimaryRecipient().getNumber());
|
byte[] groupId = GroupUtil.getDecodedId(getRecipients().getPrimaryRecipient().getNumber());
|
||||||
DatabaseFactory.getGroupDatabase(self).setActive(groupId, false);
|
DatabaseFactory.getGroupDatabase(self).setActive(groupId, false);
|
||||||
|
|
||||||
GroupContext context = GroupContext.newBuilder()
|
GroupContext context = GroupContext.newBuilder()
|
||||||
@@ -664,7 +663,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
new AsyncTask<Void, Void, Boolean>() {
|
new AsyncTask<Void, Void, Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
return OutgoingMmsConnection.isConnectionPossible(ConversationActivity.this);
|
return Util.isMmsCapable(ConversationActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -23,8 +23,7 @@ import android.database.sqlite.SQLiteDatabase;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
|
import org.thoughtcrime.securesms.mms.LegacyMmsConnection.Apn;
|
||||||
import org.thoughtcrime.securesms.mms.MmsConnection.Apn;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
@@ -2,22 +2,21 @@ package org.thoughtcrime.securesms.jobs;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.telephony.TelephonyManager;
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||||
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
|
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
|
||||||
|
import org.thoughtcrime.securesms.mms.IncomingLollipopMmsConnection;
|
||||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||||
|
import org.thoughtcrime.securesms.mms.IncomingLegacyMmsConnection;
|
||||||
import org.thoughtcrime.securesms.mms.IncomingMmsConnection;
|
import org.thoughtcrime.securesms.mms.IncomingMmsConnection;
|
||||||
import org.thoughtcrime.securesms.mms.MmsConnection;
|
|
||||||
import org.thoughtcrime.securesms.mms.MmsRadio;
|
|
||||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
|
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
@@ -32,16 +31,10 @@ import org.whispersystems.libaxolotl.util.guava.Optional;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import ws.com.google.android.mms.InvalidHeaderValueException;
|
|
||||||
import ws.com.google.android.mms.MmsException;
|
import ws.com.google.android.mms.MmsException;
|
||||||
import ws.com.google.android.mms.pdu.NotificationInd;
|
import ws.com.google.android.mms.pdu.NotificationInd;
|
||||||
import ws.com.google.android.mms.pdu.NotifyRespInd;
|
|
||||||
import ws.com.google.android.mms.pdu.PduComposer;
|
|
||||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
|
||||||
import ws.com.google.android.mms.pdu.RetrieveConf;
|
import ws.com.google.android.mms.pdu.RetrieveConf;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.mms.MmsConnection.Apn;
|
|
||||||
|
|
||||||
public class MmsDownloadJob extends MasterSecretJob {
|
public class MmsDownloadJob extends MasterSecretJob {
|
||||||
|
|
||||||
private static final String TAG = MmsDownloadJob.class.getSimpleName();
|
private static final String TAG = MmsDownloadJob.class.getSimpleName();
|
||||||
@@ -73,8 +66,8 @@ public class MmsDownloadJob extends MasterSecretJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRun(MasterSecret masterSecret) {
|
public void onRun(MasterSecret masterSecret) {
|
||||||
Log.w(TAG, "MmsDownloadJob:onRun()");
|
Log.w(TAG, "onRun()");
|
||||||
|
|
||||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||||
Optional<NotificationInd> notification = database.getNotification(messageId);
|
Optional<NotificationInd> notification = database.getNotification(messageId);
|
||||||
@@ -86,71 +79,27 @@ public class MmsDownloadJob extends MasterSecretJob {
|
|||||||
|
|
||||||
database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING);
|
database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING);
|
||||||
|
|
||||||
String contentLocation = new String(notification.get().getContentLocation());
|
String contentLocation = new String(notification.get().getContentLocation());
|
||||||
byte[] transactionId = notification.get().getTransactionId();
|
byte[] transactionId = notification.get().getTransactionId();
|
||||||
MmsRadio radio = MmsRadio.getInstance(context);
|
|
||||||
|
|
||||||
Log.w(TAG, "About to parse URL...");
|
Log.w(TAG, "Downloading mms at " + Uri.parse(contentLocation).getHost());
|
||||||
|
|
||||||
Log.w(TAG, "Downloading mms at " + Uri.parse(contentLocation).getHost());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isCdmaNetwork()) {
|
RetrieveConf retrieveConf = getMmsConnection(context).retrieve(contentLocation, transactionId);
|
||||||
Log.w(TAG, "Connecting directly...");
|
storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieveConf);
|
||||||
try {
|
|
||||||
retrieveAndStore(masterSecret, radio, messageId, threadId, contentLocation,
|
|
||||||
transactionId, false, false);
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.w(TAG, "Changing radio to MMS mode..");
|
|
||||||
radio.connect();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Log.w(TAG, "Downloading in MMS mode with proxy...");
|
|
||||||
|
|
||||||
try {
|
|
||||||
retrieveAndStore(masterSecret, radio, messageId, threadId, contentLocation,
|
|
||||||
transactionId, true, true);
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.w(TAG, "Downloading in MMS mode without proxy...");
|
|
||||||
|
|
||||||
try {
|
|
||||||
retrieveAndStore(masterSecret, radio, messageId, threadId,
|
|
||||||
contentLocation, transactionId, true, false);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
handleDownloadError(masterSecret, messageId, threadId,
|
|
||||||
MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
|
|
||||||
context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider),
|
|
||||||
automatic);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
radio.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (ApnUnavailableException e) {
|
} catch (ApnUnavailableException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
|
handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
|
||||||
context.getString(R.string.MmsDownloader_error_reading_mms_settings), automatic);
|
automatic);
|
||||||
} catch (MmsException e) {
|
} catch (MmsException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleDownloadError(masterSecret, messageId, threadId,
|
handleDownloadError(masterSecret, messageId, threadId,
|
||||||
MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
|
MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
|
||||||
context.getString(R.string.MmsDownloader_error_storing_mms),
|
|
||||||
automatic);
|
automatic);
|
||||||
} catch (MmsRadioException e) {
|
} catch (MmsRadioException | IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
handleDownloadError(masterSecret, messageId, threadId,
|
handleDownloadError(masterSecret, messageId, threadId,
|
||||||
MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
|
MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
|
||||||
context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider),
|
|
||||||
automatic);
|
automatic);
|
||||||
} catch (DuplicateMessageException e) {
|
} catch (DuplicateMessageException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
@@ -167,6 +116,16 @@ public class MmsDownloadJob extends MasterSecretJob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IncomingMmsConnection getMmsConnection(Context context)
|
||||||
|
throws ApnUnavailableException
|
||||||
|
{
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||||
|
return new IncomingLollipopMmsConnection(context);
|
||||||
|
} else {
|
||||||
|
return new IncomingLegacyMmsConnection(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCanceled() {
|
public void onCanceled() {
|
||||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||||
@@ -183,23 +142,6 @@ public class MmsDownloadJob extends MasterSecretJob {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retrieveAndStore(MasterSecret masterSecret, MmsRadio radio,
|
|
||||||
long messageId, long threadId,
|
|
||||||
String contentLocation, byte[] transactionId,
|
|
||||||
boolean radioEnabled, boolean useProxy)
|
|
||||||
throws IOException, MmsException, ApnUnavailableException,
|
|
||||||
DuplicateMessageException, NoSessionException,
|
|
||||||
InvalidMessageException, LegacyMessageException
|
|
||||||
{
|
|
||||||
Apn dbApn = MmsConnection.getApn(context, radio.getApnInformation());
|
|
||||||
Apn contentApn = new Apn(contentLocation, dbApn.getProxy(), Integer.toString(dbApn.getPort()), dbApn.getUsername(), dbApn.getPassword());
|
|
||||||
IncomingMmsConnection connection = new IncomingMmsConnection(context, contentApn);
|
|
||||||
RetrieveConf retrieved = connection.retrieve(radioEnabled, useProxy);
|
|
||||||
|
|
||||||
storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieved);
|
|
||||||
sendRetrievedAcknowledgement(radio, transactionId, radioEnabled, useProxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation,
|
private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation,
|
||||||
long messageId, long threadId, RetrieveConf retrieved)
|
long messageId, long threadId, RetrieveConf retrieved)
|
||||||
throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException,
|
throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException,
|
||||||
@@ -222,26 +164,8 @@ public class MmsDownloadJob extends MasterSecretJob {
|
|||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendRetrievedAcknowledgement(MmsRadio radio,
|
|
||||||
byte[] transactionId,
|
|
||||||
boolean usingRadio,
|
|
||||||
boolean useProxy)
|
|
||||||
throws ApnUnavailableException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
NotifyRespInd notifyResponse = new NotifyRespInd(PduHeaders.CURRENT_MMS_VERSION,
|
|
||||||
transactionId,
|
|
||||||
PduHeaders.STATUS_RETRIEVED);
|
|
||||||
|
|
||||||
OutgoingMmsConnection connection = new OutgoingMmsConnection(context, radio.getApnInformation(), new PduComposer(context, notifyResponse).make());
|
|
||||||
connection.sendNotificationReceived(usingRadio, useProxy);
|
|
||||||
} catch (InvalidHeaderValueException | IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDownloadError(MasterSecret masterSecret, long messageId, long threadId,
|
private void handleDownloadError(MasterSecret masterSecret, long messageId, long threadId,
|
||||||
int downloadStatus, String error, boolean automatic)
|
int downloadStatus, boolean automatic)
|
||||||
{
|
{
|
||||||
MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
|
MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
|
||||||
|
|
||||||
@@ -252,11 +176,4 @@ public class MmsDownloadJob extends MasterSecretJob {
|
|||||||
MessageNotifier.updateNotification(context, masterSecret, threadId);
|
MessageNotifier.updateNotification(context, masterSecret, threadId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCdmaNetwork() {
|
|
||||||
return ((TelephonyManager)context
|
|
||||||
.getSystemService(Context.TELEPHONY_SERVICE))
|
|
||||||
.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
package org.thoughtcrime.securesms.jobs;
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.telephony.TelephonyManager;
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
@@ -11,9 +12,9 @@ import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
|||||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||||
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
|
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
import org.thoughtcrime.securesms.mms.MmsRadio;
|
|
||||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
|
||||||
import org.thoughtcrime.securesms.mms.MmsSendResult;
|
import org.thoughtcrime.securesms.mms.MmsSendResult;
|
||||||
|
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
|
||||||
|
import org.thoughtcrime.securesms.mms.OutgoingLollipopMmsConnection;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
|
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
@@ -21,8 +22,8 @@ import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
|||||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||||
import org.thoughtcrime.securesms.util.Hex;
|
import org.thoughtcrime.securesms.util.Hex;
|
||||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||||
import org.thoughtcrime.securesms.util.TelephonyUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.SmilUtil;
|
import org.thoughtcrime.securesms.util.SmilUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.TelephonyUtil;
|
||||||
import org.whispersystems.jobqueue.JobParameters;
|
import org.whispersystems.jobqueue.JobParameters;
|
||||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||||
|
|
||||||
@@ -37,7 +38,6 @@ import ws.com.google.android.mms.pdu.SendConf;
|
|||||||
import ws.com.google.android.mms.pdu.SendReq;
|
import ws.com.google.android.mms.pdu.SendReq;
|
||||||
|
|
||||||
public class MmsSendJob extends SendJob {
|
public class MmsSendJob extends SendJob {
|
||||||
|
|
||||||
private static final String TAG = MmsSendJob.class.getSimpleName();
|
private static final String TAG = MmsSendJob.class.getSimpleName();
|
||||||
|
|
||||||
private final long messageId;
|
private final long messageId;
|
||||||
@@ -65,10 +65,14 @@ public class MmsSendJob extends SendJob {
|
|||||||
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
|
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MmsSendResult result = deliver(masterSecret, message);
|
validateDestinations(message);
|
||||||
|
|
||||||
|
final byte[] pduBytes = getPduBytes(masterSecret, message);
|
||||||
|
final SendConf sendConf = getMmsConnection(context).send(pduBytes);
|
||||||
|
final MmsSendResult result = getSendResult(sendConf, message);
|
||||||
|
|
||||||
database.markAsSent(messageId, result.getMessageId(), result.getResponseStatus());
|
database.markAsSent(messageId, result.getMessageId(), result.getResponseStatus());
|
||||||
} catch (UndeliverableMessageException e) {
|
} catch (UndeliverableMessageException | IOException | ApnUnavailableException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
database.markAsSentFailed(messageId);
|
database.markAsSentFailed(messageId);
|
||||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||||
@@ -90,59 +94,23 @@ public class MmsSendJob extends SendJob {
|
|||||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MmsSendResult deliver(MasterSecret masterSecret, SendReq message)
|
private OutgoingMmsConnection getMmsConnection(Context context)
|
||||||
throws UndeliverableMessageException, InsecureFallbackApprovalException
|
throws ApnUnavailableException
|
||||||
{
|
{
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||||
validateDestinations(message);
|
return new OutgoingLollipopMmsConnection(context);
|
||||||
|
} else {
|
||||||
MmsRadio radio = MmsRadio.getInstance(context);
|
return new OutgoingLegacyMmsConnection(context);
|
||||||
|
|
||||||
try {
|
|
||||||
prepareMessageMedia(masterSecret, message, MediaConstraints.MMS_CONSTRAINTS, true);
|
|
||||||
if (isCdmaDevice()) {
|
|
||||||
Log.w(TAG, "Sending MMS directly without radio change...");
|
|
||||||
try {
|
|
||||||
return sendMms(masterSecret, radio, message, false, false);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.w(TAG, "Sending MMS with radio change and proxy...");
|
|
||||||
radio.connect();
|
|
||||||
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
return sendMms(masterSecret, radio, message, true, true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.w(TAG, "Sending MMS with radio change and without proxy...");
|
|
||||||
|
|
||||||
try {
|
|
||||||
return sendMms(masterSecret, radio, message, true, false);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
Log.w(TAG, ioe);
|
|
||||||
throw new UndeliverableMessageException(ioe);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
radio.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (MmsRadioException | IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
throw new UndeliverableMessageException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MmsSendResult sendMms(MasterSecret masterSecret, MmsRadio radio, SendReq message,
|
private byte[] getPduBytes(MasterSecret masterSecret, SendReq message)
|
||||||
boolean usingMmsRadio, boolean useProxy)
|
|
||||||
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
|
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
|
||||||
{
|
{
|
||||||
String number = TelephonyUtil.getManager(context).getLine1Number();
|
String number = TelephonyUtil.getManager(context).getLine1Number();
|
||||||
|
|
||||||
|
message = getResolvedMessage(masterSecret, message, MediaConstraints.MMS_CONSTRAINTS, true);
|
||||||
|
message.setBody(SmilUtil.getSmilBody(message.getBody()));
|
||||||
if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) {
|
if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) {
|
||||||
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
|
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
|
||||||
}
|
}
|
||||||
@@ -150,28 +118,25 @@ public class MmsSendJob extends SendJob {
|
|||||||
if (number != null && number.trim().length() != 0) {
|
if (number != null && number.trim().length() != 0) {
|
||||||
message.setFrom(new EncodedStringValue(number));
|
message.setFrom(new EncodedStringValue(number));
|
||||||
}
|
}
|
||||||
|
byte[] pduBytes = new PduComposer(context, message).make();
|
||||||
|
if (pduBytes == null) {
|
||||||
|
throw new UndeliverableMessageException("PDU composition failed, null payload");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
return pduBytes;
|
||||||
byte[] pdu = new PduComposer(context, message).make();
|
}
|
||||||
|
|
||||||
if (pdu == null) {
|
private MmsSendResult getSendResult(SendConf conf, SendReq message)
|
||||||
throw new UndeliverableMessageException("PDU composition failed, null payload");
|
throws UndeliverableMessageException
|
||||||
}
|
{
|
||||||
|
if (conf == null) {
|
||||||
OutgoingMmsConnection connection = new OutgoingMmsConnection(context, radio.getApnInformation(), pdu);
|
throw new UndeliverableMessageException("No M-Send.conf received in response to send.");
|
||||||
SendConf conf = connection.send(usingMmsRadio, useProxy);
|
} else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
|
||||||
|
throw new UndeliverableMessageException("Got bad response: " + conf.getResponseStatus());
|
||||||
if (conf == null) {
|
} else if (isInconsistentResponse(message, conf)) {
|
||||||
throw new UndeliverableMessageException("No M-Send.conf received in response to send.");
|
throw new UndeliverableMessageException("Mismatched response!");
|
||||||
} else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
|
} else {
|
||||||
throw new UndeliverableMessageException("Got bad response: " + conf.getResponseStatus());
|
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus());
|
||||||
} else if (isInconsistentResponse(message, conf)) {
|
|
||||||
throw new UndeliverableMessageException("Mismatched response!");
|
|
||||||
} else {
|
|
||||||
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus());
|
|
||||||
}
|
|
||||||
} catch (ApnUnavailableException aue) {
|
|
||||||
throw new IOException("no APN was retrievable");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,37 +146,21 @@ public class MmsSendJob extends SendJob {
|
|||||||
return !Arrays.equals(message.getTransactionId(), response.getTransactionId());
|
return !Arrays.equals(message.getTransactionId(), response.getTransactionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCdmaDevice() {
|
private void validateDestinations(EncodedStringValue[] destinations) throws UndeliverableMessageException {
|
||||||
return ((TelephonyManager)context
|
if (destinations == null) return;
|
||||||
.getSystemService(Context.TELEPHONY_SERVICE))
|
|
||||||
.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateDestination(EncodedStringValue destination) throws UndeliverableMessageException {
|
for (EncodedStringValue destination : destinations) {
|
||||||
if (destination == null || !NumberUtil.isValidSmsOrEmail(destination.getString())) {
|
if (destination == null || !NumberUtil.isValidSmsOrEmail(destination.getString())) {
|
||||||
throw new UndeliverableMessageException("Invalid destination: " +
|
throw new UndeliverableMessageException("Invalid destination: " +
|
||||||
(destination == null ? null : destination.getString()));
|
(destination == null ? null : destination.getString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateDestinations(SendReq message) throws UndeliverableMessageException {
|
private void validateDestinations(SendReq message) throws UndeliverableMessageException {
|
||||||
if (message.getTo() != null) {
|
validateDestinations(message.getTo());
|
||||||
for (EncodedStringValue to : message.getTo()) {
|
validateDestinations(message.getCc());
|
||||||
validateDestination(to);
|
validateDestinations(message.getBcc());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.getCc() != null) {
|
|
||||||
for (EncodedStringValue cc : message.getCc()) {
|
|
||||||
validateDestination(cc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.getBcc() != null) {
|
|
||||||
for (EncodedStringValue bcc : message.getBcc()) {
|
|
||||||
validateDestination(bcc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.getTo() == null && message.getCc() == null && message.getBcc() == null) {
|
if (message.getTo() == null && message.getCc() == null && message.getBcc() == null) {
|
||||||
throw new UndeliverableMessageException("No to, cc, or bcc specified!");
|
throw new UndeliverableMessageException("No to, cc, or bcc specified!");
|
||||||
@@ -226,13 +175,4 @@ public class MmsSendJob extends SendJob {
|
|||||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void prepareMessageMedia(MasterSecret masterSecret, SendReq message,
|
|
||||||
MediaConstraints constraints, boolean toMemory)
|
|
||||||
throws IOException, UndeliverableMessageException {
|
|
||||||
super.prepareMessageMedia(masterSecret, message, constraints, toMemory);
|
|
||||||
message.setBody(SmilUtil.getSmilBody(message.getBody()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -104,7 +104,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
|||||||
String destination = message.getTo()[0].getString();
|
String destination = message.getTo()[0].getString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
prepareMessageMedia(masterSecret, message, MediaConstraints.PUSH_CONSTRAINTS, false);
|
message = getResolvedMessage(masterSecret, message, MediaConstraints.PUSH_CONSTRAINTS, false);
|
||||||
|
|
||||||
TextSecureAddress address = getPushAddress(destination);
|
TextSecureAddress address = getPushAddress(destination);
|
||||||
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
|
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
|
||||||
|
@@ -17,6 +17,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import ws.com.google.android.mms.MmsException;
|
import ws.com.google.android.mms.MmsException;
|
||||||
|
import ws.com.google.android.mms.pdu.PduBody;
|
||||||
import ws.com.google.android.mms.pdu.PduPart;
|
import ws.com.google.android.mms.pdu.PduPart;
|
||||||
import ws.com.google.android.mms.pdu.SendReq;
|
import ws.com.google.android.mms.pdu.SendReq;
|
||||||
|
|
||||||
@@ -40,22 +41,23 @@ public abstract class SendJob extends MasterSecretJob {
|
|||||||
|
|
||||||
protected abstract void onSend(MasterSecret masterSecret) throws Exception;
|
protected abstract void onSend(MasterSecret masterSecret) throws Exception;
|
||||||
|
|
||||||
// FIXME: This should return a value rather than modifying one.
|
protected SendReq getResolvedMessage(MasterSecret masterSecret, SendReq message,
|
||||||
protected void prepareMessageMedia(MasterSecret masterSecret, SendReq message,
|
MediaConstraints constraints, boolean toMemory)
|
||||||
MediaConstraints constraints, boolean toMemory)
|
|
||||||
throws IOException, UndeliverableMessageException
|
throws IOException, UndeliverableMessageException
|
||||||
{
|
{
|
||||||
|
PduBody body = new PduBody();
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < message.getBody().getPartsNum(); i++) {
|
for (int i = 0; i < message.getBody().getPartsNum(); i++) {
|
||||||
preparePart(masterSecret, constraints, message.getBody().getPart(i), toMemory);
|
body.addPart(getResolvedPart(masterSecret, constraints, message.getBody().getPart(i), toMemory));
|
||||||
}
|
}
|
||||||
} catch (MmsException me) {
|
} catch (MmsException me) {
|
||||||
throw new UndeliverableMessageException(me);
|
throw new UndeliverableMessageException(me);
|
||||||
}
|
}
|
||||||
|
return new SendReq(message.getPduHeaders(), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void preparePart(MasterSecret masterSecret, MediaConstraints constraints,
|
private PduPart getResolvedPart(MasterSecret masterSecret, MediaConstraints constraints,
|
||||||
PduPart part, boolean toMemory)
|
PduPart part, boolean toMemory)
|
||||||
throws IOException, MmsException, UndeliverableMessageException
|
throws IOException, MmsException, UndeliverableMessageException
|
||||||
{
|
{
|
||||||
byte[] resizedData = null;
|
byte[] resizedData = null;
|
||||||
@@ -64,7 +66,7 @@ public abstract class SendJob extends MasterSecretJob {
|
|||||||
if (!constraints.canResize(part)) {
|
if (!constraints.canResize(part)) {
|
||||||
throw new UndeliverableMessageException("Size constraints could not be satisfied.");
|
throw new UndeliverableMessageException("Size constraints could not be satisfied.");
|
||||||
}
|
}
|
||||||
resizedData = resizePart(masterSecret, constraints, part);
|
resizedData = getResizedPartData(masterSecret, constraints, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toMemory && part.getDataUri() != null) {
|
if (toMemory && part.getDataUri() != null) {
|
||||||
@@ -74,10 +76,11 @@ public abstract class SendJob extends MasterSecretJob {
|
|||||||
if (resizedData != null) {
|
if (resizedData != null) {
|
||||||
part.setDataSize(resizedData.length);
|
part.setDataSize(resizedData.length);
|
||||||
}
|
}
|
||||||
|
return part;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] resizePart(MasterSecret masterSecret, MediaConstraints constraints,
|
private byte[] getResizedPartData(MasterSecret masterSecret, MediaConstraints constraints,
|
||||||
PduPart part)
|
PduPart part)
|
||||||
throws IOException, MmsException
|
throws IOException, MmsException
|
||||||
{
|
{
|
||||||
Log.w(TAG, "resizing part " + part.getId());
|
Log.w(TAG, "resizing part " + part.getId());
|
||||||
|
@@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 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.mms;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.client.methods.HttpGetHC4;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import ws.com.google.android.mms.InvalidHeaderValueException;
|
||||||
|
import ws.com.google.android.mms.pdu.NotifyRespInd;
|
||||||
|
import ws.com.google.android.mms.pdu.PduComposer;
|
||||||
|
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||||
|
import ws.com.google.android.mms.pdu.PduParser;
|
||||||
|
import ws.com.google.android.mms.pdu.RetrieveConf;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class IncomingLegacyMmsConnection extends LegacyMmsConnection implements IncomingMmsConnection {
|
||||||
|
private static final String TAG = IncomingLegacyMmsConnection.class.getSimpleName();
|
||||||
|
|
||||||
|
public IncomingLegacyMmsConnection(Context context) throws ApnUnavailableException {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpUriRequest constructRequest(Apn contentApn, boolean useProxy) throws IOException {
|
||||||
|
HttpGetHC4 request = new HttpGetHC4(contentApn.getMmsc());
|
||||||
|
for (Header header : getBaseHeaders()) {
|
||||||
|
request.addHeader(header);
|
||||||
|
}
|
||||||
|
if (useProxy) {
|
||||||
|
HttpHost proxy = new HttpHost(contentApn.getProxy(), contentApn.getPort());
|
||||||
|
request.setConfig(RequestConfig.custom().setProxy(proxy).build());
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RetrieveConf retrieve(String contentLocation, byte[] transactionId) throws MmsRadioException, ApnUnavailableException, IOException {
|
||||||
|
MmsRadio radio = MmsRadio.getInstance(context);
|
||||||
|
Apn contentApn = new Apn(contentLocation, apn.getProxy(), Integer.toString(apn.getPort()), apn.getUsername(), apn.getPassword());
|
||||||
|
if (isCdmaDevice()) {
|
||||||
|
Log.w(TAG, "Connecting directly...");
|
||||||
|
try {
|
||||||
|
return retrieve(contentApn, transactionId, false, false);
|
||||||
|
} catch (IOException | ApnUnavailableException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "Changing radio to MMS mode..");
|
||||||
|
radio.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.w(TAG, "Downloading in MMS mode with proxy...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return retrieve(contentApn, transactionId, true, true);
|
||||||
|
} catch (IOException | ApnUnavailableException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "Downloading in MMS mode without proxy...");
|
||||||
|
|
||||||
|
return retrieve(contentApn, transactionId, true, false);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
radio.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RetrieveConf retrieve(Apn contentApn, byte[] transactionId, boolean usingMmsRadio, boolean useProxyIfAvailable)
|
||||||
|
throws IOException, ApnUnavailableException
|
||||||
|
{
|
||||||
|
byte[] pdu = null;
|
||||||
|
|
||||||
|
final boolean useProxy = useProxyIfAvailable && contentApn.hasProxy();
|
||||||
|
final String targetHost = useProxy
|
||||||
|
? contentApn.getProxy()
|
||||||
|
: Uri.parse(contentApn.getMmsc()).getHost();
|
||||||
|
try {
|
||||||
|
if (checkRouteToHost(context, targetHost, usingMmsRadio)) {
|
||||||
|
Log.w(TAG, "got successful route to host " + targetHost);
|
||||||
|
pdu = execute(constructRequest(contentApn, useProxy));
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log.w(TAG, ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pdu == null) {
|
||||||
|
throw new IOException("Connection manager could not obtain route to host.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RetrieveConf retrieved = (RetrieveConf)new PduParser(pdu).parse();
|
||||||
|
|
||||||
|
if (retrieved == null) {
|
||||||
|
Log.w(TAG, "Couldn't parse PDU, byte response: " + Arrays.toString(pdu));
|
||||||
|
Log.w(TAG, "Couldn't parse PDU, ASCII: " + new String(pdu));
|
||||||
|
throw new IOException("Bad retrieved PDU");
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRetrievedAcknowledgement(transactionId, usingMmsRadio, useProxy);
|
||||||
|
return retrieved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendRetrievedAcknowledgement(byte[] transactionId,
|
||||||
|
boolean usingRadio,
|
||||||
|
boolean useProxy)
|
||||||
|
throws ApnUnavailableException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
NotifyRespInd notifyResponse = new NotifyRespInd(PduHeaders.CURRENT_MMS_VERSION,
|
||||||
|
transactionId,
|
||||||
|
PduHeaders.STATUS_RETRIEVED);
|
||||||
|
|
||||||
|
OutgoingLegacyMmsConnection connection = new OutgoingLegacyMmsConnection(context);
|
||||||
|
connection.sendNotificationReceived(new PduComposer(context, notifyResponse).make(), usingRadio, useProxy);
|
||||||
|
} catch (InvalidHeaderValueException | IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 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.mms;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.telephony.SmsManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.providers.MmsBodyProvider;
|
||||||
|
import org.thoughtcrime.securesms.util.Hex;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import ws.com.google.android.mms.MmsException;
|
||||||
|
import ws.com.google.android.mms.pdu.PduParser;
|
||||||
|
import ws.com.google.android.mms.pdu.RetrieveConf;
|
||||||
|
|
||||||
|
public class IncomingLollipopMmsConnection extends LollipopMmsConnection implements IncomingMmsConnection {
|
||||||
|
public static final String ACTION = IncomingLollipopMmsConnection.class.getCanonicalName() + "MMS_DOWNLOADED_ACTION";
|
||||||
|
private static final String TAG = IncomingLollipopMmsConnection.class.getSimpleName();
|
||||||
|
|
||||||
|
public IncomingLollipopMmsConnection(Context context) {
|
||||||
|
super(context, ACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public synchronized void onResult(Context context, Intent intent) {
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) {
|
||||||
|
Log.w(TAG, "HTTP status: " + intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, -1));
|
||||||
|
}
|
||||||
|
Log.w(TAG, "code: " + getResultCode() + ", result string: " + getResultData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||||
|
public synchronized RetrieveConf retrieve(String contentLocation, byte[] transactionId) throws MmsException {
|
||||||
|
beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext());
|
||||||
|
|
||||||
|
Log.w(TAG, "downloading multimedia from " + contentLocation + " to " + pointer.getUri());
|
||||||
|
SmsManager.getDefault().downloadMultimediaMessage(getContext(),
|
||||||
|
contentLocation,
|
||||||
|
pointer.getUri(),
|
||||||
|
null,
|
||||||
|
getPendingIntent());
|
||||||
|
|
||||||
|
waitForResult();
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
Util.copy(pointer.getInputStream(), baos);
|
||||||
|
pointer.close();
|
||||||
|
|
||||||
|
Log.w(TAG, baos.size() + "-byte response: " + Hex.dump(baos.toByteArray()));
|
||||||
|
|
||||||
|
return (RetrieveConf) new PduParser(baos.toByteArray()).parse();
|
||||||
|
} catch (IOException | TimeoutException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
throw new MmsException(e);
|
||||||
|
} finally {
|
||||||
|
endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,96 +1,10 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2014 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.mms;
|
package org.thoughtcrime.securesms.mms;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.apache.http.Header;
|
|
||||||
import org.apache.http.HttpHost;
|
|
||||||
import org.apache.http.client.config.RequestConfig;
|
|
||||||
import org.apache.http.client.methods.HttpGetHC4;
|
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import ws.com.google.android.mms.pdu.PduParser;
|
import ws.com.google.android.mms.MmsException;
|
||||||
import ws.com.google.android.mms.pdu.RetrieveConf;
|
import ws.com.google.android.mms.pdu.RetrieveConf;
|
||||||
|
|
||||||
public class IncomingMmsConnection extends MmsConnection {
|
public interface IncomingMmsConnection {
|
||||||
private static final String TAG = IncomingMmsConnection.class.getSimpleName();
|
RetrieveConf retrieve(String contentLocation, byte[] transactionId) throws MmsException, MmsRadioException, ApnUnavailableException, IOException;
|
||||||
|
|
||||||
public IncomingMmsConnection(Context context, Apn apn) {
|
|
||||||
super(context, apn);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected HttpUriRequest constructRequest(boolean useProxy) throws IOException {
|
|
||||||
HttpGetHC4 request = new HttpGetHC4(apn.getMmsc());
|
|
||||||
for (Header header : getBaseHeaders()) {
|
|
||||||
request.addHeader(header);
|
|
||||||
}
|
|
||||||
if (useProxy) {
|
|
||||||
HttpHost proxy = new HttpHost(apn.getProxy(), apn.getPort());
|
|
||||||
request.setConfig(RequestConfig.custom().setProxy(proxy).build());
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isConnectionPossible(Context context, String apn) {
|
|
||||||
try {
|
|
||||||
getApn(context, apn);
|
|
||||||
return true;
|
|
||||||
} catch (ApnUnavailableException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public RetrieveConf retrieve(boolean usingMmsRadio, boolean useProxyIfAvailable)
|
|
||||||
throws IOException, ApnUnavailableException
|
|
||||||
{
|
|
||||||
byte[] pdu = null;
|
|
||||||
|
|
||||||
final boolean useProxy = useProxyIfAvailable && apn.hasProxy();
|
|
||||||
final String targetHost = useProxy
|
|
||||||
? apn.getProxy()
|
|
||||||
: Uri.parse(apn.getMmsc()).getHost();
|
|
||||||
try {
|
|
||||||
if (checkRouteToHost(context, targetHost, usingMmsRadio)) {
|
|
||||||
Log.w(TAG, "got successful route to host " + targetHost);
|
|
||||||
pdu = makeRequest(useProxy);
|
|
||||||
}
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
Log.w(TAG, ioe);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pdu == null) {
|
|
||||||
throw new IOException("Connection manager could not obtain route to host.");
|
|
||||||
}
|
|
||||||
|
|
||||||
RetrieveConf retrieved = (RetrieveConf)new PduParser(pdu).parse();
|
|
||||||
|
|
||||||
if (retrieved == null) {
|
|
||||||
Log.w(TAG, "Couldn't parse PDU, byte response: " + Arrays.toString(pdu));
|
|
||||||
Log.w(TAG, "Couldn't parse PDU, ASCII: " + new String(pdu));
|
|
||||||
throw new IOException("Bad retrieved PDU");
|
|
||||||
}
|
|
||||||
|
|
||||||
return retrieved;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.mms;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@@ -50,19 +51,19 @@ import java.net.URL;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class MmsConnection {
|
@SuppressWarnings("deprecation")
|
||||||
|
public abstract class LegacyMmsConnection {
|
||||||
private static final String TAG = "MmsCommunication";
|
private static final String TAG = "MmsCommunication";
|
||||||
|
|
||||||
protected final Context context;
|
protected final Context context;
|
||||||
protected final Apn apn;
|
protected final Apn apn;
|
||||||
|
|
||||||
protected MmsConnection(Context context, Apn apn) {
|
protected LegacyMmsConnection(Context context) throws ApnUnavailableException {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.apn = apn;
|
this.apn = getApn(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Apn getApn(Context context, String apnName) throws ApnUnavailableException {
|
public static Apn getApn(Context context) throws ApnUnavailableException {
|
||||||
Log.w(TAG, "Getting MMSC params for apn " + apnName);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Optional<Apn> params = ApnDatabase.getInstance(context)
|
Optional<Apn> params = ApnDatabase.getInstance(context)
|
||||||
@@ -79,6 +80,10 @@ public abstract class MmsConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean isCdmaDevice() {
|
||||||
|
return ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
|
||||||
|
}
|
||||||
|
|
||||||
protected static boolean checkRouteToHost(Context context, String host, boolean usingMmsRadio)
|
protected static boolean checkRouteToHost(Context context, String host, boolean usingMmsRadio)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
@@ -146,14 +151,12 @@ public abstract class MmsConnection {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] makeRequest(boolean useProxy) throws IOException {
|
protected byte[] execute(HttpUriRequest request) throws IOException {
|
||||||
Log.w(TAG, "connecting to " + apn.getMmsc() + (useProxy ? " using proxy" : ""));
|
Log.w(TAG, "connecting to " + apn.getMmsc());
|
||||||
|
|
||||||
HttpUriRequest request;
|
|
||||||
CloseableHttpClient client = null;
|
CloseableHttpClient client = null;
|
||||||
CloseableHttpResponse response = null;
|
CloseableHttpResponse response = null;
|
||||||
try {
|
try {
|
||||||
request = constructRequest(useProxy);
|
|
||||||
client = constructHttpClient();
|
client = constructHttpClient();
|
||||||
response = client.execute(request);
|
response = client.execute(request);
|
||||||
|
|
||||||
@@ -170,8 +173,6 @@ public abstract class MmsConnection {
|
|||||||
throw new IOException("unhandled response code");
|
throw new IOException("unhandled response code");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract HttpUriRequest constructRequest(boolean useProxy) throws IOException;
|
|
||||||
|
|
||||||
protected List<Header> getBaseHeaders() {
|
protected List<Header> getBaseHeaders() {
|
||||||
final String number = TelephonyUtil.getManager(context).getLine1Number();
|
final String number = TelephonyUtil.getManager(context).getLine1Number();
|
||||||
return new LinkedList<Header>() {{
|
return new LinkedList<Header>() {{
|
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 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.mms;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
public abstract class LollipopMmsConnection extends BroadcastReceiver {
|
||||||
|
private static final String TAG = LollipopMmsConnection.class.getSimpleName();
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final String action;
|
||||||
|
|
||||||
|
private boolean resultAvailable;
|
||||||
|
|
||||||
|
public abstract void onResult(Context context, Intent intent);
|
||||||
|
|
||||||
|
protected LollipopMmsConnection(Context context, String action) {
|
||||||
|
super();
|
||||||
|
this.context = context;
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void onReceive(Context context, Intent intent) {
|
||||||
|
Log.w(TAG, "onReceive()");
|
||||||
|
if (!action.equals(intent.getAction())) {
|
||||||
|
Log.w(TAG, "received broadcast with unexpected action " + intent.getAction());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onResult(context, intent);
|
||||||
|
|
||||||
|
resultAvailable = true;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void beginTransaction() {
|
||||||
|
getContext().getApplicationContext().registerReceiver(this, new IntentFilter(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void endTransaction() {
|
||||||
|
getContext().getApplicationContext().unregisterReceiver(this);
|
||||||
|
resultAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitForResult() throws TimeoutException {
|
||||||
|
long timeoutExpiration = System.currentTimeMillis() + 30000;
|
||||||
|
while (!resultAvailable) {
|
||||||
|
Util.wait(this, Math.max(1, timeoutExpiration - System.currentTimeMillis()));
|
||||||
|
if (System.currentTimeMillis() >= timeoutExpiration) {
|
||||||
|
throw new TimeoutException("timeout when waiting for MMS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PendingIntent getPendingIntent() {
|
||||||
|
return PendingIntent.getBroadcast(getContext(), 1, new Intent(action), PendingIntent.FLAG_ONE_SHOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
@@ -43,10 +43,6 @@ public class MmsRadio {
|
|||||||
this.wakeLock.setReferenceCounted(true);
|
this.wakeLock.setReferenceCounted(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getApnInformation() {
|
|
||||||
return connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS).getExtraInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void disconnect() {
|
public synchronized void disconnect() {
|
||||||
Log.w("MmsRadio", "MMS Radio Disconnect Called...");
|
Log.w("MmsRadio", "MMS Radio Disconnect Called...");
|
||||||
wakeLock.release();
|
wakeLock.release();
|
||||||
|
@@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 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.mms;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.client.methods.HttpPostHC4;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.entity.ByteArrayEntityHC4;
|
||||||
|
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import ws.com.google.android.mms.pdu.PduParser;
|
||||||
|
import ws.com.google.android.mms.pdu.SendConf;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class OutgoingLegacyMmsConnection extends LegacyMmsConnection implements OutgoingMmsConnection {
|
||||||
|
private final static String TAG = OutgoingLegacyMmsConnection.class.getSimpleName();
|
||||||
|
|
||||||
|
public OutgoingLegacyMmsConnection(Context context) throws ApnUnavailableException {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpUriRequest constructRequest(byte[] pduBytes, boolean useProxy)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
HttpPostHC4 request = new HttpPostHC4(apn.getMmsc());
|
||||||
|
for (Header header : getBaseHeaders()) {
|
||||||
|
request.addHeader(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.setEntity(new ByteArrayEntityHC4(pduBytes));
|
||||||
|
if (useProxy) {
|
||||||
|
HttpHost proxy = new HttpHost(apn.getProxy(), apn.getPort());
|
||||||
|
request.setConfig(RequestConfig.custom().setProxy(proxy).build());
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IOException(iae);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendNotificationReceived(byte[] pduBytes, boolean usingMmsRadio, boolean useProxyIfAvailable)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
sendBytes(pduBytes, usingMmsRadio, useProxyIfAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SendConf send(byte[] pduBytes) throws UndeliverableMessageException {
|
||||||
|
try {
|
||||||
|
MmsRadio radio = MmsRadio.getInstance(context);
|
||||||
|
|
||||||
|
if (isCdmaDevice()) {
|
||||||
|
Log.w(TAG, "Sending MMS directly without radio change...");
|
||||||
|
try {
|
||||||
|
return send(pduBytes, false, false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "Sending MMS with radio change and proxy...");
|
||||||
|
radio.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
return send(pduBytes, true, true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "Sending MMS with radio change and without proxy...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return send(pduBytes, true, false);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log.w(TAG, ioe);
|
||||||
|
throw new UndeliverableMessageException(ioe);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
radio.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (MmsRadioException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
throw new UndeliverableMessageException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendConf send(byte[] pduBytes, boolean useMmsRadio, boolean useProxyIfAvailable) throws IOException {
|
||||||
|
byte[] response = sendBytes(pduBytes, useMmsRadio, useProxyIfAvailable);
|
||||||
|
return (SendConf) new PduParser(response).parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] sendBytes(byte[] pduBytes, boolean useMmsRadio, boolean useProxyIfAvailable) throws IOException {
|
||||||
|
final boolean useProxy = useProxyIfAvailable && apn.hasProxy();
|
||||||
|
final String targetHost = useProxy
|
||||||
|
? apn.getProxy()
|
||||||
|
: Uri.parse(apn.getMmsc()).getHost();
|
||||||
|
|
||||||
|
Log.w(TAG, "Sending MMS of length: " + pduBytes.length
|
||||||
|
+ (useMmsRadio ? ", using mms radio" : "")
|
||||||
|
+ (useProxy ? ", using proxy" : ""));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (checkRouteToHost(context, targetHost, useMmsRadio)) {
|
||||||
|
Log.w(TAG, "got successful route to host " + targetHost);
|
||||||
|
byte[] response = execute(constructRequest(pduBytes, useProxy));
|
||||||
|
if (response != null) return response;
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log.w(TAG, ioe);
|
||||||
|
}
|
||||||
|
throw new IOException("Connection manager could not obtain route to host.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isConnectionPossible(Context context) {
|
||||||
|
try {
|
||||||
|
ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(MmsRadio.TYPE_MOBILE_MMS);
|
||||||
|
if (networkInfo == null) {
|
||||||
|
Log.w(TAG, "MMS network info was null, unsupported by this device");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getApn(context);
|
||||||
|
return true;
|
||||||
|
} catch (ApnUnavailableException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 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.mms;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.telephony.SmsManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.providers.MmsBodyProvider;
|
||||||
|
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import ws.com.google.android.mms.pdu.PduParser;
|
||||||
|
import ws.com.google.android.mms.pdu.SendConf;
|
||||||
|
|
||||||
|
public class OutgoingLollipopMmsConnection extends LollipopMmsConnection implements OutgoingMmsConnection {
|
||||||
|
private static final String TAG = OutgoingLollipopMmsConnection.class.getSimpleName();
|
||||||
|
private static final String ACTION = OutgoingLollipopMmsConnection.class.getCanonicalName() + "MMS_SENT_ACTION";
|
||||||
|
|
||||||
|
private byte[] response;
|
||||||
|
|
||||||
|
public OutgoingLollipopMmsConnection(Context context) {
|
||||||
|
super(context, ACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(VERSION_CODES.LOLLIPOP_MR1)
|
||||||
|
@Override
|
||||||
|
public synchronized void onResult(Context context, Intent intent) {
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) {
|
||||||
|
Log.w(TAG, "HTTP status: " + intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||||
|
public synchronized SendConf send(byte[] pduBytes) throws UndeliverableMessageException {
|
||||||
|
beginTransaction();
|
||||||
|
try {
|
||||||
|
MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext());
|
||||||
|
Util.copy(new ByteArrayInputStream(pduBytes), pointer.getOutputStream());
|
||||||
|
|
||||||
|
SmsManager.getDefault().sendMultimediaMessage(getContext(),
|
||||||
|
pointer.getUri(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
getPendingIntent());
|
||||||
|
|
||||||
|
waitForResult();
|
||||||
|
|
||||||
|
Log.w(TAG, "MMS broadcast received and processed.");
|
||||||
|
pointer.close();
|
||||||
|
|
||||||
|
return (SendConf) new PduParser(response).parse();
|
||||||
|
} catch (IOException | TimeoutException e) {
|
||||||
|
throw new UndeliverableMessageException(e);
|
||||||
|
} finally {
|
||||||
|
endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -1,121 +1,9 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2014 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.mms;
|
package org.thoughtcrime.securesms.mms;
|
||||||
|
|
||||||
import android.content.Context;
|
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.apache.http.Header;
|
|
||||||
import org.apache.http.HttpHost;
|
|
||||||
import org.apache.http.client.config.RequestConfig;
|
|
||||||
import org.apache.http.client.methods.HttpPostHC4;
|
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
|
||||||
import org.apache.http.entity.ByteArrayEntityHC4;
|
|
||||||
import org.thoughtcrime.securesms.util.TelephonyUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import ws.com.google.android.mms.pdu.PduParser;
|
|
||||||
import ws.com.google.android.mms.pdu.SendConf;
|
import ws.com.google.android.mms.pdu.SendConf;
|
||||||
|
|
||||||
public class OutgoingMmsConnection extends MmsConnection {
|
public interface OutgoingMmsConnection {
|
||||||
private final static String TAG = OutgoingMmsConnection.class.getSimpleName();
|
SendConf send(byte[] pduBytes) throws UndeliverableMessageException;
|
||||||
|
|
||||||
private final byte[] mms;
|
|
||||||
|
|
||||||
public OutgoingMmsConnection(Context context, String apnName, byte[] mms) throws ApnUnavailableException {
|
|
||||||
super(context, getApn(context, apnName));
|
|
||||||
this.mms = mms;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected HttpUriRequest constructRequest(boolean useProxy)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
HttpPostHC4 request = new HttpPostHC4(apn.getMmsc());
|
|
||||||
for (Header header : getBaseHeaders()) {
|
|
||||||
request.addHeader(header);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setEntity(new ByteArrayEntityHC4(mms));
|
|
||||||
if (useProxy) {
|
|
||||||
HttpHost proxy = new HttpHost(apn.getProxy(), apn.getPort());
|
|
||||||
request.setConfig(RequestConfig.custom().setProxy(proxy).build());
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
} catch (IllegalArgumentException iae) {
|
|
||||||
throw new IOException(iae);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendNotificationReceived(boolean usingMmsRadio, boolean useProxyIfAvailable)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
sendBytes(usingMmsRadio, useProxyIfAvailable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SendConf send(boolean useMmsRadio, boolean useProxyIfAvailable) throws IOException {
|
|
||||||
byte[] response = sendBytes(useMmsRadio, useProxyIfAvailable);
|
|
||||||
return (SendConf) new PduParser(response).parse();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] sendBytes(boolean useMmsRadio, boolean useProxyIfAvailable) throws IOException {
|
|
||||||
final boolean useProxy = useProxyIfAvailable && apn.hasProxy();
|
|
||||||
final String targetHost = useProxy
|
|
||||||
? apn.getProxy()
|
|
||||||
: Uri.parse(apn.getMmsc()).getHost();
|
|
||||||
|
|
||||||
Log.w(TAG, "Sending MMS of length: " + mms.length
|
|
||||||
+ (useMmsRadio ? ", using mms radio" : "")
|
|
||||||
+ (useProxy ? ", using proxy" : ""));
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (checkRouteToHost(context, targetHost, useMmsRadio)) {
|
|
||||||
Log.w(TAG, "got successful route to host " + targetHost);
|
|
||||||
byte[] response = makeRequest(useProxy);
|
|
||||||
if (response != null) return response;
|
|
||||||
}
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
Log.w(TAG, ioe);
|
|
||||||
}
|
|
||||||
throw new IOException("Connection manager could not obtain route to host.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isConnectionPossible(Context context) {
|
|
||||||
try {
|
|
||||||
ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(MmsRadio.TYPE_MOBILE_MMS);
|
|
||||||
if (networkInfo == null) {
|
|
||||||
Log.w(TAG, "MMS network info was null, unsupported by this device");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getApn(context, networkInfo.getExtraInfo());
|
|
||||||
return true;
|
|
||||||
} catch (ApnUnavailableException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
|||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
|
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
|
||||||
import org.thoughtcrime.securesms.database.ApnDatabase;
|
import org.thoughtcrime.securesms.database.ApnDatabase;
|
||||||
import org.thoughtcrime.securesms.mms.MmsConnection;
|
import org.thoughtcrime.securesms.mms.LegacyMmsConnection;
|
||||||
import org.thoughtcrime.securesms.util.TelephonyUtil;
|
import org.thoughtcrime.securesms.util.TelephonyUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
@@ -52,10 +52,10 @@ public class MmsPreferencesFragment extends PreferenceFragment {
|
|||||||
new LoadApnDefaultsTask().execute();
|
new LoadApnDefaultsTask().execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoadApnDefaultsTask extends AsyncTask<Void, Void, MmsConnection.Apn> {
|
private class LoadApnDefaultsTask extends AsyncTask<Void, Void, LegacyMmsConnection.Apn> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MmsConnection.Apn doInBackground(Void... params) {
|
protected LegacyMmsConnection.Apn doInBackground(Void... params) {
|
||||||
try {
|
try {
|
||||||
Context context = getActivity();
|
Context context = getActivity();
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ public class MmsPreferencesFragment extends PreferenceFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(MmsConnection.Apn apnDefaults) {
|
protected void onPostExecute(LegacyMmsConnection.Apn apnDefaults) {
|
||||||
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_HOST_PREF))
|
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_HOST_PREF))
|
||||||
.setValidator(new CustomDefaultPreference.UriValidator())
|
.setValidator(new CustomDefaultPreference.UriValidator())
|
||||||
.setDefaultValue(apnDefaults.getMmsc());
|
.setDefaultValue(apnDefaults.getMmsc());
|
||||||
|
@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.preferences;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
@@ -40,12 +42,13 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializePlatformSpecificOptions() {
|
private void initializePlatformSpecificOptions() {
|
||||||
PreferenceScreen preferenceScreen = getPreferenceScreen();
|
PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||||
Preference defaultPreference = findPreference(KITKAT_DEFAULT_PREF);
|
Preference defaultPreference = findPreference(KITKAT_DEFAULT_PREF);
|
||||||
Preference allSmsPreference = findPreference(TextSecurePreferences.ALL_SMS_PREF);
|
Preference allSmsPreference = findPreference(TextSecurePreferences.ALL_SMS_PREF);
|
||||||
Preference allMmsPreference = findPreference(TextSecurePreferences.ALL_MMS_PREF);
|
Preference allMmsPreference = findPreference(TextSecurePreferences.ALL_MMS_PREF);
|
||||||
|
Preference manualMmsPreference = findPreference(MMS_PREF);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ) {
|
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
|
||||||
if (allSmsPreference != null) preferenceScreen.removePreference(allSmsPreference);
|
if (allSmsPreference != null) preferenceScreen.removePreference(allSmsPreference);
|
||||||
if (allMmsPreference != null) preferenceScreen.removePreference(allMmsPreference);
|
if (allMmsPreference != null) preferenceScreen.removePreference(allMmsPreference);
|
||||||
|
|
||||||
@@ -63,6 +66,10 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
|
|||||||
} else if (defaultPreference != null) {
|
} else if (defaultPreference != null) {
|
||||||
preferenceScreen.removePreference(defaultPreference);
|
preferenceScreen.removePreference(defaultPreference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && manualMmsPreference != null) {
|
||||||
|
preferenceScreen.removePreference(manualMmsPreference);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ApnPreferencesClickListener implements Preference.OnPreferenceClickListener {
|
private class ApnPreferencesClickListener implements Preference.OnPreferenceClickListener {
|
||||||
|
140
src/org/thoughtcrime/securesms/providers/MmsBodyProvider.java
Normal file
140
src/org/thoughtcrime/securesms/providers/MmsBodyProvider.java
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2015 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.providers;
|
||||||
|
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.UriMatcher;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class MmsBodyProvider extends ContentProvider {
|
||||||
|
private static final String TAG = MmsBodyProvider.class.getSimpleName();
|
||||||
|
private static final String CONTENT_URI_STRING = "content://org.thoughtcrime.provider.securesms.mms/mms";
|
||||||
|
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);
|
||||||
|
private static final int SINGLE_ROW = 1;
|
||||||
|
|
||||||
|
private static final UriMatcher uriMatcher;
|
||||||
|
|
||||||
|
static {
|
||||||
|
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
|
uriMatcher.addURI("org.thoughtcrime.provider.securesms.mms", "mms/#", SINGLE_ROW);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private File getFile(Uri uri) {
|
||||||
|
long id = Long.parseLong(uri.getPathSegments().get(1));
|
||||||
|
return new File(getContext().getCacheDir(), id + ".mmsbody");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||||
|
Log.w(TAG, "openFile(" + uri + ", " + mode + ")");
|
||||||
|
|
||||||
|
switch (uriMatcher.match(uri)) {
|
||||||
|
case SINGLE_ROW:
|
||||||
|
Log.w(TAG, "Fetching message body for a single row...");
|
||||||
|
File tmpFile = getFile(uri);
|
||||||
|
|
||||||
|
final int fileMode;
|
||||||
|
switch (mode) {
|
||||||
|
case "w": fileMode = ParcelFileDescriptor.MODE_TRUNCATE |
|
||||||
|
ParcelFileDescriptor.MODE_CREATE |
|
||||||
|
ParcelFileDescriptor.MODE_WRITE_ONLY; break;
|
||||||
|
case "r": fileMode = ParcelFileDescriptor.MODE_READ_ONLY; break;
|
||||||
|
default: throw new IllegalArgumentException("requested file mode unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "returning file " + tmpFile.getAbsolutePath());
|
||||||
|
return ParcelFileDescriptor.open(tmpFile, fileMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundException("Request for bad message.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(Uri uri, String arg1, String[] arg2) {
|
||||||
|
switch (uriMatcher.match(uri)) {
|
||||||
|
case SINGLE_ROW:
|
||||||
|
return getFile(uri).delete() ? 1 : 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType(Uri arg0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(Uri arg0, ContentValues arg1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
public static Pointer makeTemporaryPointer(Context context) {
|
||||||
|
return new Pointer(context, ContentUris.withAppendedId(MmsBodyProvider.CONTENT_URI, System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Pointer {
|
||||||
|
private final Context context;
|
||||||
|
private final Uri uri;
|
||||||
|
|
||||||
|
public Pointer(Context context, Uri uri) {
|
||||||
|
this.context = context;
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getOutputStream() throws FileNotFoundException {
|
||||||
|
return context.getContentResolver().openOutputStream(uri, "w");
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getInputStream() throws FileNotFoundException {
|
||||||
|
return context.getContentResolver().openInputStream(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
context.getContentResolver().delete(uri, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,6 +17,7 @@
|
|||||||
package org.thoughtcrime.securesms.util;
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Shader;
|
import android.graphics.Shader;
|
||||||
@@ -24,7 +25,10 @@ import android.graphics.Typeface;
|
|||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.provider.Telephony;
|
import android.provider.Telephony;
|
||||||
|
import android.telephony.SmsManager;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
@@ -34,6 +38,7 @@ import android.widget.EditText;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.BuildConfig;
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
import org.thoughtcrime.securesms.TextSecureExpiredException;
|
import org.thoughtcrime.securesms.TextSecureExpiredException;
|
||||||
|
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
|
||||||
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
||||||
import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
|
import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
|
||||||
|
|
||||||
@@ -126,7 +131,7 @@ public class Util {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void wait(Object lock, int timeout) {
|
public static void wait(Object lock, long timeout) {
|
||||||
try {
|
try {
|
||||||
lock.wait(timeout);
|
lock.wait(timeout);
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
@@ -279,4 +284,9 @@ public class Util {
|
|||||||
public static boolean isBuildFresh() {
|
public static boolean isBuildFresh() {
|
||||||
return BuildConfig.BUILD_TIMESTAMP + TimeUnit.DAYS.toMillis(180) > System.currentTimeMillis();
|
return BuildConfig.BUILD_TIMESTAMP + TimeUnit.DAYS.toMillis(180) > System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||||
|
public static boolean isMmsCapable(Context context) {
|
||||||
|
return (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) || OutgoingLegacyMmsConnection.isConnectionPossible(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user