mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Remove more SMS vestiges.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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...");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 + '"') + " }";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user