Remove more SMS vestiges.

This commit is contained in:
Greyson Parrelli
2024-03-12 13:47:18 -04:00
parent 6754fef164
commit 825ca0d737
94 changed files with 60 additions and 8017 deletions

View File

@@ -1,19 +0,0 @@
package org.thoughtcrime.securesms.mms;
public class ApnUnavailableException extends Exception {
public ApnUnavailableException() {
}
public ApnUnavailableException(String detailMessage) {
super(detailMessage);
}
public ApnUnavailableException(Throwable throwable) {
super(throwable);
}
public ApnUnavailableException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}

View File

@@ -1,101 +0,0 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.RetrieveConf;
import com.google.android.mms.pdu_alt.SendConf;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import java.io.IOException;
public class CompatMmsConnection implements OutgoingMmsConnection, IncomingMmsConnection {
private static final String TAG = Log.tag(CompatMmsConnection.class);
private Context context;
public CompatMmsConnection(Context context) {
this.context = context;
}
@Nullable
@Override
public SendConf send(@NonNull byte[] pduBytes, int subscriptionId)
throws UndeliverableMessageException
{
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) {
try {
Log.i(TAG, "Sending via Lollipop API");
return new OutgoingLollipopMmsConnection(context).send(pduBytes, subscriptionId);
} catch (UndeliverableMessageException e) {
Log.w(TAG, e);
}
Log.i(TAG, "Falling back to legacy connection...");
}
if (subscriptionId == -1) {
Log.i(TAG, "Sending via legacy connection");
try {
SendConf result = new OutgoingLegacyMmsConnection(context).send(pduBytes, subscriptionId);
if (result != null && result.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) {
return result;
} else {
Log.w(TAG, "Got bad legacy response: " + (result != null ? result.getResponseStatus() : null));
}
} catch (UndeliverableMessageException | ApnUnavailableException e) {
Log.w(TAG, e);
}
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && VERSION.SDK_INT < VERSION_CODES.LOLLIPOP_MR1) {
Log.i(TAG, "Falling back to sending via Lollipop API");
return new OutgoingLollipopMmsConnection(context).send(pduBytes, subscriptionId);
}
throw new UndeliverableMessageException("Both lollipop and legacy connections failed...");
}
@Nullable
@Override
public RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId,
int subscriptionId)
throws MmsException, MmsRadioException, ApnUnavailableException, IOException
{
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) {
Log.i(TAG, "Receiving via Lollipop API");
try {
return new IncomingLollipopMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
} catch (MmsException e) {
Log.w(TAG, e);
}
Log.i(TAG, "Falling back to receiving via legacy connection");
}
if (VERSION.SDK_INT < 22 || subscriptionId == -1) {
Log.i(TAG, "Receiving via legacy API");
try {
return new IncomingLegacyMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
} catch (MmsRadioException | ApnUnavailableException | IOException e) {
Log.w(TAG, e);
}
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && VERSION.SDK_INT < VERSION_CODES.LOLLIPOP_MR1) {
Log.i(TAG, "Falling back to receiving via Lollipop API");
return new IncomingLollipopMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
}
throw new IOException("Both lollipop and fallback APIs failed...");
}
}

View File

@@ -1,157 +0,0 @@
/**
* 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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.InvalidHeaderValueException;
import com.google.android.mms.pdu_alt.NotifyRespInd;
import com.google.android.mms.pdu_alt.PduComposer;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.PduParser;
import com.google.android.mms.pdu_alt.RetrieveConf;
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 org.signal.core.util.logging.Log;
import java.io.IOException;
import java.util.Arrays;
@SuppressWarnings("deprecation")
public class IncomingLegacyMmsConnection extends LegacyMmsConnection implements IncomingMmsConnection {
private static final String TAG = Log.tag(IncomingLegacyMmsConnection.class);
public IncomingLegacyMmsConnection(Context context) throws ApnUnavailableException {
super(context);
}
private HttpUriRequest constructRequest(Apn contentApn, boolean useProxy) throws IOException {
HttpGetHC4 request;
try {
request = new HttpGetHC4(contentApn.getMmsc());
} catch (IllegalArgumentException e) {
// #7339
throw new IOException(e);
}
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 @Nullable RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId, int subscriptionId)
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 (isDirectConnect()) {
Log.i(TAG, "Connecting directly...");
try {
return retrieve(contentApn, transactionId, false, false);
} catch (IOException | ApnUnavailableException e) {
Log.w(TAG, e);
}
}
Log.i(TAG, "Changing radio to MMS mode..");
radio.connect();
try {
Log.i(TAG, "Downloading in MMS mode with proxy...");
try {
return retrieve(contentApn, transactionId, true, true);
} catch (IOException | ApnUnavailableException e) {
Log.w(TAG, e);
}
Log.i(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();
if (checkRouteToHost(context, targetHost, usingMmsRadio)) {
Log.i(TAG, "got successful route to host " + targetHost);
pdu = execute(constructRequest(contentApn, useProxy));
}
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);
}
}
}

View File

@@ -1,156 +0,0 @@
/**
* 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.content.Intent;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.InvalidHeaderValueException;
import com.google.android.mms.pdu_alt.NotifyRespInd;
import com.google.android.mms.pdu_alt.PduComposer;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.PduParser;
import com.google.android.mms.pdu_alt.RetrieveConf;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.providers.MmsBodyProvider;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
public class IncomingLollipopMmsConnection extends LollipopMmsConnection implements IncomingMmsConnection {
public static final String ACTION = IncomingLollipopMmsConnection.class.getCanonicalName() + "MMS_DOWNLOADED_ACTION";
private static final String TAG = Log.tag(IncomingLollipopMmsConnection.class);
public IncomingLollipopMmsConnection(Context context) {
super(context, ACTION);
}
@Override
public synchronized void onResult(Context context, Intent intent) {
if (VERSION.SDK_INT >= 22) {
Log.i(TAG, "HTTP status: " + intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, -1));
}
Log.i(TAG, "code: " + getResultCode() + ", result string: " + getResultData());
}
@Override
public synchronized @Nullable RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId,
int subscriptionId) throws MmsException
{
beginTransaction();
try {
MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext());
final String transactionIdString = Util.toIsoString(transactionId);
Log.i(TAG, String.format(Locale.ENGLISH, "Downloading subscriptionId=%s multimedia from '%s' [transactionId='%s'] to '%s'",
subscriptionId,
contentLocation,
transactionIdString,
pointer.getUri()));
SmsManager smsManager;
if (VERSION.SDK_INT >= 22 && subscriptionId != -1) {
smsManager = SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
smsManager = SmsManager.getDefault();
}
final Bundle configOverrides = smsManager.getCarrierConfigValues();
if (configOverrides.getBoolean(SmsManager.MMS_CONFIG_APPEND_TRANSACTION_ID)) {
if (!contentLocation.contains(transactionIdString)) {
Log.i(TAG, "Appending transactionId to contentLocation at the direction of CarrierConfigValues. New location: " + contentLocation);
contentLocation += transactionIdString;
} else {
Log.i(TAG, "Skipping 'append transaction id' as contentLocation already contains it");
}
}
if (TextUtils.isEmpty(configOverrides.getString(SmsManager.MMS_CONFIG_USER_AGENT))) {
configOverrides.remove(SmsManager.MMS_CONFIG_USER_AGENT);
}
if (TextUtils.isEmpty(configOverrides.getString(SmsManager.MMS_CONFIG_UA_PROF_URL))) {
configOverrides.remove(SmsManager.MMS_CONFIG_UA_PROF_URL);
}
smsManager.downloadMultimediaMessage(getContext(),
contentLocation,
pointer.getUri(),
configOverrides,
getPendingIntent());
waitForResult();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamUtil.copy(pointer.getInputStream(), baos);
pointer.close();
Log.i(TAG, baos.size() + "-byte response: ");// + Hex.dump(baos.toByteArray()));
Bundle configValues = smsManager.getCarrierConfigValues();
boolean parseContentDisposition = configValues.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION);
RetrieveConf retrieved;
try {
retrieved = (RetrieveConf) new PduParser(baos.toByteArray(), parseContentDisposition).parse();
} catch (AssertionError | NullPointerException e) {
Log.w(TAG, "Badly formatted MMS message caused the parser to fail.", e);
throw new MmsException(e);
}
if (retrieved == null) return null;
sendRetrievedAcknowledgement(transactionId, retrieved.getMmsVersion(), subscriptionId);
return retrieved;
} catch (IOException | TimeoutException e) {
Log.w(TAG, e);
throw new MmsException(e);
} finally {
endTransaction();
}
}
private void sendRetrievedAcknowledgement(byte[] transactionId, int mmsVersion, int subscriptionId) {
try {
NotifyRespInd retrieveResponse = new NotifyRespInd(mmsVersion, transactionId, PduHeaders.STATUS_RETRIEVED);
new OutgoingLollipopMmsConnection(getContext()).send(new PduComposer(getContext(), retrieveResponse).make(), subscriptionId);
} catch (UndeliverableMessageException e) {
Log.w(TAG, e);
} catch (InvalidHeaderValueException e) {
Log.w(TAG, e);
}
}
}

View File

@@ -1,13 +0,0 @@
package org.thoughtcrime.securesms.mms;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.RetrieveConf;
import java.io.IOException;
public interface IncomingMmsConnection {
@Nullable
RetrieveConf retrieve(@NonNull String contentLocation, byte[] transactionId, int subscriptionId) throws MmsException, MmsRadioException, ApnUnavailableException, IOException;
}

View File

@@ -1,313 +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.mms;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import org.apache.http.Header;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.NoConnectionReuseStrategyHC4;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.ApnDatabase;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@SuppressWarnings("deprecation")
public abstract class LegacyMmsConnection {
public static final String USER_AGENT = "Android-Mms/2.0";
private static final String TAG = Log.tag(LegacyMmsConnection.class);
protected final Context context;
protected final Apn apn;
protected LegacyMmsConnection(Context context) throws ApnUnavailableException {
this.context = context;
this.apn = getApn(context);
}
public static Apn getApn(Context context) throws ApnUnavailableException {
try {
Optional<Apn> params = ApnDatabase.getInstance(context)
.getMmsConnectionParameters(TelephonyUtil.getMccMnc(context),
TelephonyUtil.getApn(context));
if (!params.isPresent()) {
throw new ApnUnavailableException("No parameters available from ApnDefaults.");
}
return params.get();
} catch (IOException ioe) {
throw new ApnUnavailableException("ApnDatabase threw an IOException", ioe);
}
}
protected boolean isDirectConnect() {
// We think Sprint supports direct connection over wifi/data, but not Verizon
Set<String> sprintMccMncs = new HashSet<String>() {{
add("312530");
add("311880");
add("311870");
add("311490");
add("310120");
add("316010");
add("312190");
}};
return ServiceUtil.getTelephonyManager(context).getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA &&
sprintMccMncs.contains(TelephonyUtil.getMccMnc(context));
}
@SuppressWarnings("TryWithIdenticalCatches")
protected static boolean checkRouteToHost(Context context, String host, boolean usingMmsRadio)
throws IOException
{
InetAddress inetAddress = InetAddress.getByName(host);
if (!usingMmsRadio) {
if (inetAddress.isSiteLocalAddress()) {
throw new IOException("RFC1918 address in non-MMS radio situation!");
}
Log.w(TAG, "returning vacuous success since MMS radio is not in use");
return true;
}
if (inetAddress == null) {
throw new IOException("Unable to lookup host: InetAddress.getByName() returned null.");
}
byte[] ipAddressBytes = inetAddress.getAddress();
if (ipAddressBytes == null) {
Log.w(TAG, "resolved IP address bytes are null, returning true to attempt a connection anyway.");
return true;
}
Log.i(TAG, "Checking route to address: " + host + ", " + inetAddress.getHostAddress());
ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
try {
final Method requestRouteMethod = manager.getClass().getMethod("requestRouteToHostAddress", Integer.TYPE, InetAddress.class);
final boolean routeToHostObtained = (Boolean) requestRouteMethod.invoke(manager, MmsRadio.TYPE_MOBILE_MMS, inetAddress);
Log.i(TAG, "requestRouteToHostAddress(" + inetAddress + ") -> " + routeToHostObtained);
return routeToHostObtained;
} catch (NoSuchMethodException nsme) {
Log.w(TAG, nsme);
} catch (IllegalAccessException iae) {
Log.w(TAG, iae);
} catch (InvocationTargetException ite) {
Log.w(TAG, ite);
}
return false;
}
protected static byte[] parseResponse(InputStream is) throws IOException {
InputStream in = new BufferedInputStream(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamUtil.copy(in, baos);
Log.i(TAG, "Received full server response, " + baos.size() + " bytes");
return baos.toByteArray();
}
protected CloseableHttpClient constructHttpClient() throws IOException {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(20 * 1000)
.setConnectionRequestTimeout(20 * 1000)
.setSocketTimeout(20 * 1000)
.setMaxRedirects(20)
.build();
URL mmsc = new URL(apn.getMmsc());
CredentialsProvider credsProvider = new BasicCredentialsProvider();
if (apn.hasAuthentication()) {
credsProvider.setCredentials(new AuthScope(mmsc.getHost(), mmsc.getPort() > -1 ? mmsc.getPort() : mmsc.getDefaultPort()),
new UsernamePasswordCredentials(apn.getUsername(), apn.getPassword()));
}
return HttpClients.custom()
.setConnectionReuseStrategy(new NoConnectionReuseStrategyHC4())
.setRedirectStrategy(new LaxRedirectStrategy())
.setUserAgent(TextSecurePreferences.getMmsUserAgent(context, USER_AGENT))
.setConnectionManager(new BasicHttpClientConnectionManager())
.setDefaultRequestConfig(config)
.setDefaultCredentialsProvider(credsProvider)
.build();
}
protected byte[] execute(HttpUriRequest request) throws IOException {
Log.i(TAG, "connecting to " + apn.getMmsc());
CloseableHttpClient client = null;
CloseableHttpResponse response = null;
try {
client = constructHttpClient();
response = client.execute(request);
Log.i(TAG, "* response code: " + response.getStatusLine());
if (response.getStatusLine().getStatusCode() == 200) {
return parseResponse(response.getEntity().getContent());
}
} catch (NullPointerException npe) {
// TODO determine root cause
// see: https://github.com/signalapp/Signal-Android/issues/4379
throw new IOException(npe);
} finally {
if (response != null) response.close();
if (client != null) client.close();
}
throw new IOException("unhandled response code");
}
protected List<Header> getBaseHeaders() {
final String number = getLine1Number(context);
return new LinkedList<Header>() {{
add(new BasicHeader("Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"));
add(new BasicHeader("x-wap-profile", "http://www.google.com/oha/rdf/ua-profile-kila.xml"));
add(new BasicHeader("Content-Type", "application/vnd.wap.mms-message"));
add(new BasicHeader("x-carrier-magic", "http://magic.google.com"));
if (!TextUtils.isEmpty(number)) {
add(new BasicHeader("x-up-calling-line-id", number));
add(new BasicHeader("X-MDN", number));
}
}};
}
@SuppressLint("HardwareIds")
private static String getLine1Number(@NonNull Context context) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
return TelephonyUtil.getManager(context).getLine1Number();
} else {
return "";
}
}
public static class Apn {
public static Apn EMPTY = new Apn("", "", "", "", "");
private final String mmsc;
private final String proxy;
private final String port;
private final String username;
private final String password;
public Apn(String mmsc, String proxy, String port, String username, String password) {
this.mmsc = mmsc;
this.proxy = proxy;
this.port = port;
this.username = username;
this.password = password;
}
public Apn(Apn customApn, Apn defaultApn,
boolean useCustomMmsc,
boolean useCustomProxy,
boolean useCustomProxyPort,
boolean useCustomUsername,
boolean useCustomPassword)
{
this.mmsc = useCustomMmsc ? customApn.mmsc : defaultApn.mmsc;
this.proxy = useCustomProxy ? customApn.proxy : defaultApn.proxy;
this.port = useCustomProxyPort ? customApn.port : defaultApn.port;
this.username = useCustomUsername ? customApn.username : defaultApn.username;
this.password = useCustomPassword ? customApn.password : defaultApn.password;
}
public boolean hasProxy() {
return !TextUtils.isEmpty(proxy);
}
public String getMmsc() {
return mmsc;
}
public String getProxy() {
return hasProxy() ? proxy : null;
}
public int getPort() {
return TextUtils.isEmpty(port) ? 80 : Integer.parseInt(port);
}
public boolean hasAuthentication() {
return !TextUtils.isEmpty(username);
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
@Override
public @NonNull String toString() {
return Log.tag(Apn.class) +
"{ mmsc: \"" + mmsc + "\"" +
", proxy: " + (proxy == null ? "none" : '"' + proxy + '"') +
", port: " + (port == null ? "(none)" : port) +
", user: " + (username == null ? "none" : '"' + username + '"') +
", pass: " + (password == null ? "none" : '"' + password + '"') + " }";
}
}
}

View File

@@ -1,87 +0,0 @@
/**
* 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 org.signal.core.util.PendingIntentFlags;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Util;
import java.util.concurrent.TimeoutException;
public abstract class LollipopMmsConnection extends BroadcastReceiver {
private static final String TAG = Log.tag(LollipopMmsConnection.class);
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.i(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() + 60000;
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), PendingIntentFlags.oneShot());
}
protected Context getContext() {
return context;
}
}

View File

@@ -33,10 +33,6 @@ public abstract class MediaConstraints {
return new PushMediaConstraints(sentMediaQuality);
}
public static MediaConstraints getMmsMediaConstraints(int subscriptionId) {
return new MmsMediaConstraints(subscriptionId);
}
public abstract int getImageMaxWidth(Context context);
public abstract int getImageMaxHeight(Context context);
public abstract int getImageMaxSize(Context context);

View File

@@ -1,36 +0,0 @@
/**
* 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;
public class MediaNotFoundException extends Exception {
public MediaNotFoundException() {
}
public MediaNotFoundException(String detailMessage) {
super(detailMessage);
}
public MediaNotFoundException(Throwable throwable) {
super(throwable);
}
public MediaNotFoundException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}

View File

@@ -1,40 +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.mms;
public class MediaTooLargeException extends Exception {
public MediaTooLargeException() {
// TODO Auto-generated constructor stub
}
public MediaTooLargeException(String detailMessage) {
super(detailMessage);
// TODO Auto-generated constructor stub
}
public MediaTooLargeException(Throwable throwable) {
super(throwable);
// TODO Auto-generated constructor stub
}
public MediaTooLargeException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
// TODO Auto-generated constructor stub
}
}

View File

@@ -1,53 +0,0 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.mms.service_alt.MmsConfig;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
final class MmsConfigManager {
private static final Map<Integer, MmsConfig> mmsConfigMap = new HashMap<>();
@WorkerThread
synchronized static @NonNull MmsConfig getMmsConfig(Context context, int subscriptionId) {
MmsConfig mmsConfig = mmsConfigMap.get(subscriptionId);
if (mmsConfig != null) {
return mmsConfig;
}
MmsConfig loadedConfig = loadMmsConfig(context, subscriptionId);
mmsConfigMap.put(subscriptionId, loadedConfig);
return loadedConfig;
}
private static @NonNull MmsConfig loadMmsConfig(Context context, int subscriptionId) {
Optional<SubscriptionInfoCompat> subscriptionInfo = new SubscriptionManagerCompat(context).getActiveSubscriptionInfo(subscriptionId);
if (subscriptionInfo.isPresent()) {
SubscriptionInfoCompat subscriptionInfoCompat = subscriptionInfo.get();
Configuration configuration = context.getResources().getConfiguration();
configuration.mcc = subscriptionInfoCompat.getMcc();
configuration.mnc = subscriptionInfoCompat.getMnc();
Context subContext = context.createConfigurationContext(configuration);
return new MmsConfig(subContext, subscriptionId);
}
return new MmsConfig(context, subscriptionId);
}
}

View File

@@ -1,79 +0,0 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import com.android.mms.service_alt.MmsConfig;
final class MmsMediaConstraints extends MediaConstraints {
private final int subscriptionId;
private static final int MIN_IMAGE_DIMEN = 1024;
MmsMediaConstraints(int subscriptionId) {
this.subscriptionId = subscriptionId;
}
@Override
public int getImageMaxWidth(Context context) {
return Math.max(MIN_IMAGE_DIMEN, getOverriddenMmsConfig(context).getMaxImageWidth());
}
@Override
public int getImageMaxHeight(Context context) {
return Math.max(MIN_IMAGE_DIMEN, getOverriddenMmsConfig(context).getMaxImageHeight());
}
@Override
public int[] getImageDimensionTargets(Context context) {
int[] targets = new int[4];
targets[0] = getImageMaxHeight(context);
for (int i = 1; i < targets.length; i++) {
targets[i] = targets[i - 1] / 2;
}
return targets;
}
@Override
public int getImageMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public long getGifMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public long getVideoMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public long getUncompressedVideoMaxSize(Context context) {
return Math.max(getVideoMaxSize(context), 15 * 1024 * 1024);
}
@Override
public long getAudioMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public long getDocumentMaxSize(Context context) {
return getMaxMessageSize(context);
}
private int getMaxMessageSize(Context context) {
return getOverriddenMmsConfig(context).getMaxMessageSize();
}
private MmsConfig.Overridden getOverriddenMmsConfig(Context context) {
MmsConfig mmsConfig = MmsConfigManager.getMmsConfig(context, subscriptionId);
return new MmsConfig.Overridden(mmsConfig, null);
}
}

View File

@@ -1,165 +0,0 @@
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 org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MmsRadio {
private static final String TAG = Log.tag(MmsRadio.class);
private static MmsRadio instance;
public static synchronized MmsRadio getInstance(Context context) {
if (instance == null)
instance = new MmsRadio(context.getApplicationContext());
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, "signal:mms");
this.wakeLock.setReferenceCounted(true);
}
public synchronized void disconnect() {
Log.i(TAG, "MMS Radio Disconnect Called...");
wakeLock.release();
connectedCounter--;
Log.i(TAG, "Reference count: " + connectedCounter);
if (connectedCounter == 0) {
Log.i(TAG, "Turning off MMS radio...");
try {
final Method stopUsingNetworkFeatureMethod = connectivityManager.getClass().getMethod("stopUsingNetworkFeature", Integer.TYPE, String.class);
stopUsingNetworkFeatureMethod.invoke(connectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
} catch (NoSuchMethodException nsme) {
Log.w(TAG, nsme);
} catch (IllegalAccessException iae) {
Log.w(TAG, iae);
} catch (InvocationTargetException ite) {
Log.w(TAG, ite);
}
if (connectivityListener != null) {
Log.i(TAG, "Unregistering receiver...");
context.unregisterReceiver(connectivityListener);
connectivityListener = null;
}
}
}
public synchronized void connect() throws MmsRadioException {
int status;
try {
final Method startUsingNetworkFeatureMethod = connectivityManager.getClass().getMethod("startUsingNetworkFeature", Integer.TYPE, String.class);
status = (int)startUsingNetworkFeatureMethod.invoke(connectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
} catch (NoSuchMethodException nsme) {
throw new MmsRadioException(nsme);
} catch (IllegalAccessException iae) {
throw new MmsRadioException(iae);
} catch (InvocationTargetException ite) {
throw new MmsRadioException(ite);
}
Log.i(TAG, "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(TAG, "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);
Log.i(TAG, "Connected: " + info);
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.i(TAG, "Notifying connected...");
notifyAll();
return;
}
if (!isConnected() && (isConnectivityFailure() || !isConnectivityPossible())) {
Log.i(TAG, "Notifying not connected...");
notifyAll();
return;
}
}
private class ConnectivityListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Got connectivity change...");
issueConnectivityChange();
}
}
}

View File

@@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.mms;
public class MmsRadioException extends Throwable {
public MmsRadioException(String s) {
super(s);
}
public MmsRadioException(Exception e) {
super(e);
}
}

View File

@@ -1,20 +0,0 @@
package org.thoughtcrime.securesms.mms;
public class MmsSendResult {
private final byte[] messageId;
private final int responseStatus;
public MmsSendResult(byte[] messageId, int responseStatus) {
this.messageId = messageId;
this.responseStatus = responseStatus;
}
public int getResponseStatus() {
return responseStatus;
}
public byte[] getMessageId() {
return messageId;
}
}

View File

@@ -1,163 +0,0 @@
/**
* 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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.PduParser;
import com.google.android.mms.pdu_alt.SendConf;
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.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import java.io.IOException;
@SuppressWarnings("deprecation")
public class OutgoingLegacyMmsConnection extends LegacyMmsConnection implements OutgoingMmsConnection {
private final static String TAG = Log.tag(OutgoingLegacyMmsConnection.class);
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 @Nullable SendConf send(@NonNull byte[] pduBytes, int subscriptionId) throws UndeliverableMessageException {
try {
MmsRadio radio = MmsRadio.getInstance(context);
if (isDirectConnect()) {
Log.i(TAG, "Sending MMS directly without radio change...");
try {
return send(pduBytes, false, false);
} catch (IOException e) {
Log.w(TAG, e);
}
}
Log.i(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.i(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.i(TAG, "Sending MMS of length: " + pduBytes.length
+ (useMmsRadio ? ", using mms radio" : "")
+ (useProxy ? ", using proxy" : ""));
try {
if (checkRouteToHost(context, targetHost, useMmsRadio)) {
Log.i(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;
}
}
}

View File

@@ -1,113 +0,0 @@
/**
* 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.Bundle;
import android.telephony.SmsManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.mms.service_alt.MmsConfig;
import com.google.android.mms.pdu_alt.PduParser;
import com.google.android.mms.pdu_alt.SendConf;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.providers.MmsBodyProvider;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class OutgoingLollipopMmsConnection extends LollipopMmsConnection implements OutgoingMmsConnection {
private static final String TAG = Log.tag(OutgoingLollipopMmsConnection.class);
private static final String ACTION = OutgoingLollipopMmsConnection.class.getCanonicalName() + "MMS_SENT_ACTION";
private byte[] response;
public OutgoingLollipopMmsConnection(Context context) {
super(context, ACTION);
}
@TargetApi(22)
@Override
public synchronized void onResult(Context context, Intent intent) {
if (VERSION.SDK_INT >= 22) {
Log.i(TAG, "HTTP status: " + intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, -1));
}
response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA);
}
@Override
public @Nullable synchronized SendConf send(@NonNull byte[] pduBytes, int subscriptionId)
throws UndeliverableMessageException
{
beginTransaction();
try {
MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext());
StreamUtil.copy(new ByteArrayInputStream(pduBytes), pointer.getOutputStream());
SmsManager smsManager;
if (VERSION.SDK_INT >= 22 && subscriptionId != -1) {
smsManager = SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
smsManager = SmsManager.getDefault();
}
Bundle configOverrides = new Bundle();
configOverrides.putBoolean(SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED, true);
MmsConfig mmsConfig = MmsConfigManager.getMmsConfig(getContext(), subscriptionId);
if (mmsConfig != null) {
MmsConfig.Overridden overridden = new MmsConfig.Overridden(mmsConfig, new Bundle());
configOverrides.putString(SmsManager.MMS_CONFIG_HTTP_PARAMS, overridden.getHttpParams());
configOverrides.putInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE, overridden.getMaxMessageSize());
}
smsManager.sendMultimediaMessage(getContext(),
pointer.getUri(),
null,
configOverrides,
getPendingIntent());
waitForResult();
Log.i(TAG, "MMS broadcast received and processed.");
pointer.close();
if (response == null) {
throw new UndeliverableMessageException("Null response.");
}
return (SendConf) new PduParser(response).parse();
} catch (IOException | TimeoutException e) {
throw new UndeliverableMessageException(e);
} finally {
endTransaction();
}
}
}

View File

@@ -1,14 +0,0 @@
package org.thoughtcrime.securesms.mms;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.SendConf;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
public interface OutgoingMmsConnection {
@Nullable
SendConf send(@NonNull byte[] pduBytes, int subscriptionId) throws UndeliverableMessageException;
}

View File

@@ -1,98 +0,0 @@
package org.thoughtcrime.securesms.mms;
import com.google.android.mms.ContentType;
import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.PduBody;
import com.google.android.mms.pdu_alt.PduPart;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Util;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
public class PartParser {
private static final String TAG = Log.tag(PartParser.class);
private static final List<String> DOCUMENT_TYPES = Arrays.asList("text/vcard", "text/x-vcard");
public static String getMessageText(PduBody body) {
String bodyText = null;
for (int i=0;i<body.getPartsNum();i++) {
if (isText(body.getPart(i)) && !isDocument(body.getPart(i))) {
String partText;
try {
String characterSet = CharacterSets.getMimeName(body.getPart(i).getCharset());
if (characterSet.equals(CharacterSets.MIMENAME_ANY_CHARSET))
characterSet = CharacterSets.MIMENAME_UTF_8;
if (body.getPart(i).getData() != null) {
partText = new String(body.getPart(i).getData(), characterSet);
} else {
partText = "";
}
} catch (UnsupportedEncodingException e) {
Log.w(TAG, e);
partText = "Unsupported Encoding!";
}
bodyText = (bodyText == null) ? partText : bodyText + " " + partText;
}
}
return bodyText;
}
public static PduBody getSupportedMediaParts(PduBody body) {
PduBody stripped = new PduBody();
for (int i=0;i<body.getPartsNum();i++) {
if (isDisplayableMedia(body.getPart(i)) || isDocument(body.getPart(i))) {
stripped.addPart(body.getPart(i));
}
}
return stripped;
}
public static int getSupportedMediaPartCount(PduBody body) {
int partCount = 0;
for (int i=0;i<body.getPartsNum();i++) {
if (isDisplayableMedia(body.getPart(i)) || isDocument(body.getPart(i))) {
partCount++;
}
}
return partCount;
}
public static boolean isImage(PduPart part) {
return ContentType.isImageType(Util.toIsoString(part.getContentType()));
}
public static boolean isAudio(PduPart part) {
return ContentType.isAudioType(Util.toIsoString(part.getContentType()));
}
public static boolean isVideo(PduPart part) {
return ContentType.isVideoType(Util.toIsoString(part.getContentType()));
}
public static boolean isText(PduPart part) {
return ContentType.isTextType(Util.toIsoString(part.getContentType()));
}
public static boolean isDisplayableMedia(PduPart part) {
return isImage(part) || isAudio(part) || isVideo(part);
}
public static boolean isDocument(PduPart part) {
return DOCUMENT_TYPES.contains(Util.toIsoString(part.getContentType()).toLowerCase());
}
}