mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
remove some unsued sms code
This commit is contained in:
@@ -400,27 +400,6 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".service.SmsDeliveryListener"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="org.thoughtcrime.securesms.services.MESSAGE_SENT"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".service.MmsListener"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BROADCAST_WAP_PUSH">
|
||||
<intent-filter android:priority="1001">
|
||||
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED"/>
|
||||
<data android:mimeType="application/vnd.wap.mms-message" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
|
||||
<data android:mimeType="application/vnd.wap.mms-message" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".notifications.MarkReadReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package org.thoughtcrime.securesms.backup;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public abstract class FullBackupBase {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = FullBackupBase.class.getSimpleName();
|
||||
|
||||
static class BackupStream {
|
||||
static @NonNull byte[] getBackupKey(@NonNull String passphrase, @Nullable byte[] salt) {
|
||||
try {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||
byte[] input = passphrase.replace(" ", "").getBytes();
|
||||
byte[] hash = input;
|
||||
|
||||
if (salt != null) digest.update(salt);
|
||||
|
||||
for (int i=0;i<250000;i++) {
|
||||
if (i % 1000 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
|
||||
digest.update(hash);
|
||||
hash = digest.digest(input);
|
||||
}
|
||||
|
||||
return ByteUtil.trim(hash, 32);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class BackupEvent {
|
||||
public enum Type {
|
||||
PROGRESS,
|
||||
FINISHED
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final int count;
|
||||
|
||||
BackupEvent(Type type, int count) {
|
||||
this.type = type;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
package org.thoughtcrime.securesms.backup;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.annimon.stream.function.Consumer;
|
||||
import com.annimon.stream.function.Predicate;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.SessionDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.kdf.HKDFv3;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class FullBackupExporter extends FullBackupBase {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = FullBackupExporter.class.getSimpleName();
|
||||
|
||||
public static void export(@NonNull Context context,
|
||||
@NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull File output,
|
||||
@NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(output, passphrase);
|
||||
outputStream.writeDatabaseVersion(input.getVersion());
|
||||
|
||||
List<String> tables = exportSchema(input, outputStream);
|
||||
int count = 0;
|
||||
|
||||
for (String table : tables) {
|
||||
if (table.equals(SmsDatabase.TABLE_NAME) || table.equals(MmsDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0, null, count);
|
||||
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count);
|
||||
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
|
||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), cursor -> exportAttachment(attachmentSecret, cursor, outputStream), count);
|
||||
} else if (!table.equals(SignedPreKeyDatabase.TABLE_NAME) &&
|
||||
!table.equals(OneTimePreKeyDatabase.TABLE_NAME) &&
|
||||
!table.equals(SessionDatabase.TABLE_NAME) &&
|
||||
!table.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME) &&
|
||||
!table.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME))
|
||||
{
|
||||
count = exportTable(table, input, outputStream, null, null, count);
|
||||
}
|
||||
}
|
||||
|
||||
for (BackupProtos.SharedPreference preference : IdentityKeyUtil.getBackupRecord(context)) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(preference);
|
||||
}
|
||||
|
||||
for (File avatar : AvatarHelper.getAvatarFiles(context)) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
outputStream.write(avatar.getName(), new FileInputStream(avatar), avatar.length());
|
||||
}
|
||||
|
||||
outputStream.writeEnd();
|
||||
outputStream.close();
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count));
|
||||
}
|
||||
|
||||
private static List<String> exportSchema(@NonNull SQLiteDatabase input, @NonNull BackupFrameOutputStream outputStream)
|
||||
throws IOException
|
||||
{
|
||||
List<String> tables = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = input.rawQuery("SELECT sql, name, type FROM sqlite_master", null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String sql = cursor.getString(0);
|
||||
String name = cursor.getString(1);
|
||||
String type = cursor.getString(2);
|
||||
|
||||
if (sql != null) {
|
||||
|
||||
boolean isSmsFtsSecretTable = name != null && !name.equals(SearchDatabase.SMS_FTS_TABLE_NAME) && name.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME);
|
||||
boolean isMmsFtsSecretTable = name != null && !name.equals(SearchDatabase.MMS_FTS_TABLE_NAME) && name.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME);
|
||||
|
||||
if (!isSmsFtsSecretTable && !isMmsFtsSecretTable) {
|
||||
if ("table".equals(type)) {
|
||||
tables.add(name);
|
||||
}
|
||||
|
||||
outputStream.write(BackupProtos.SqlStatement.newBuilder().setStatement(cursor.getString(0)).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tables;
|
||||
}
|
||||
|
||||
private static int exportTable(@NonNull String table,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull BackupFrameOutputStream outputStream,
|
||||
@Nullable Predicate<Cursor> predicate,
|
||||
@Nullable Consumer<Cursor> postProcess,
|
||||
int count)
|
||||
throws IOException
|
||||
{
|
||||
String template = "INSERT INTO " + table + " VALUES ";
|
||||
|
||||
try (Cursor cursor = input.rawQuery("SELECT * FROM " + table, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
|
||||
|
||||
if (predicate == null || predicate.test(cursor)) {
|
||||
StringBuilder statement = new StringBuilder(template);
|
||||
BackupProtos.SqlStatement.Builder statementBuilder = BackupProtos.SqlStatement.newBuilder();
|
||||
|
||||
statement.append('(');
|
||||
|
||||
for (int i=0;i<cursor.getColumnCount();i++) {
|
||||
statement.append('?');
|
||||
|
||||
if (cursor.getType(i) == Cursor.FIELD_TYPE_STRING) {
|
||||
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setStringParamter(cursor.getString(i)));
|
||||
} else if (cursor.getType(i) == Cursor.FIELD_TYPE_FLOAT) {
|
||||
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setDoubleParameter(cursor.getDouble(i)));
|
||||
} else if (cursor.getType(i) == Cursor.FIELD_TYPE_INTEGER) {
|
||||
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setIntegerParameter(cursor.getLong(i)));
|
||||
} else if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) {
|
||||
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setBlobParameter(ByteString.copyFrom(cursor.getBlob(i))));
|
||||
} else if (cursor.getType(i) == Cursor.FIELD_TYPE_NULL) {
|
||||
statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setNullparameter(true));
|
||||
} else {
|
||||
throw new AssertionError("unknown type?" + cursor.getType(i));
|
||||
}
|
||||
|
||||
if (i < cursor.getColumnCount()-1) {
|
||||
statement.append(',');
|
||||
}
|
||||
}
|
||||
|
||||
statement.append(')');
|
||||
|
||||
outputStream.write(statementBuilder.setStatement(statement.toString()).build());
|
||||
|
||||
if (postProcess != null) postProcess.accept(cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream) {
|
||||
try {
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID));
|
||||
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.SIZE));
|
||||
|
||||
String data = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA));
|
||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA_RANDOM));
|
||||
|
||||
if (!TextUtils.isEmpty(data) && size <= 0) {
|
||||
size = calculateVeryOldStreamLength(attachmentSecret, random, data);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(data) && size > 0) {
|
||||
InputStream inputStream;
|
||||
|
||||
if (random != null && random.length == 32) inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
|
||||
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
|
||||
|
||||
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static long calculateVeryOldStreamLength(@NonNull AttachmentSecret attachmentSecret, @Nullable byte[] random, @NonNull String data) throws IOException {
|
||||
long result = 0;
|
||||
InputStream inputStream;
|
||||
|
||||
if (random != null && random.length == 32) inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
|
||||
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
|
||||
|
||||
int read;
|
||||
byte[] buffer = new byte[8192];
|
||||
|
||||
while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) {
|
||||
result += read;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long mmsId) {
|
||||
String[] columns = new String[] { MmsDatabase.EXPIRES_IN };
|
||||
String where = MmsDatabase.ID + " = ?";
|
||||
String[] args = new String[] { String.valueOf(mmsId) };
|
||||
|
||||
try (Cursor mmsCursor = db.query(MmsDatabase.TABLE_NAME, columns, where, args, null, null, null)) {
|
||||
if (mmsCursor != null && mmsCursor.moveToFirst()) {
|
||||
return mmsCursor.getLong(0) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static class BackupFrameOutputStream extends BackupStream {
|
||||
|
||||
private final OutputStream outputStream;
|
||||
private final Cipher cipher;
|
||||
private final Mac mac;
|
||||
|
||||
private final byte[] cipherKey;
|
||||
private final byte[] macKey;
|
||||
|
||||
private byte[] iv;
|
||||
private int counter;
|
||||
|
||||
private BackupFrameOutputStream(@NonNull File output, @NonNull String passphrase) throws IOException {
|
||||
try {
|
||||
byte[] salt = Util.getSecretBytes(32);
|
||||
byte[] key = getBackupKey(passphrase, salt);
|
||||
byte[] derived = new HKDFv3().deriveSecrets(key, "Backup Export".getBytes(), 64);
|
||||
byte[][] split = ByteUtil.split(derived, 32, 32);
|
||||
|
||||
this.cipherKey = split[0];
|
||||
this.macKey = split[1];
|
||||
|
||||
this.cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
this.mac = Mac.getInstance("HmacSHA256");
|
||||
this.outputStream = new FileOutputStream(output);
|
||||
this.iv = Util.getSecretBytes(16);
|
||||
this.counter = Conversions.byteArrayToInt(iv);
|
||||
|
||||
mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
|
||||
|
||||
byte[] header = BackupProtos.BackupFrame.newBuilder().setHeader(BackupProtos.Header.newBuilder()
|
||||
.setIv(ByteString.copyFrom(iv))
|
||||
.setSalt(ByteString.copyFrom(salt)))
|
||||
.build().toByteArray();
|
||||
|
||||
outputStream.write(Conversions.intToByteArray(header.length));
|
||||
outputStream.write(header);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(BackupProtos.SharedPreference preference) throws IOException {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder().setPreference(preference).build());
|
||||
}
|
||||
|
||||
public void write(BackupProtos.SqlStatement statement) throws IOException {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder().setStatement(statement).build());
|
||||
}
|
||||
|
||||
public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder()
|
||||
.setAvatar(BackupProtos.Avatar.newBuilder()
|
||||
.setName(avatarName)
|
||||
.setLength(Util.toIntExact(size))
|
||||
.build())
|
||||
.build());
|
||||
|
||||
writeStream(in);
|
||||
}
|
||||
|
||||
public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, long size) throws IOException {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder()
|
||||
.setAttachment(BackupProtos.Attachment.newBuilder()
|
||||
.setRowId(attachmentId.getRowId())
|
||||
.setAttachmentId(attachmentId.getUniqueId())
|
||||
.setLength(Util.toIntExact(size))
|
||||
.build())
|
||||
.build());
|
||||
|
||||
writeStream(in);
|
||||
}
|
||||
|
||||
void writeDatabaseVersion(int version) throws IOException {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder()
|
||||
.setVersion(BackupProtos.DatabaseVersion.newBuilder().setVersion(version))
|
||||
.build());
|
||||
}
|
||||
|
||||
void writeEnd() throws IOException {
|
||||
write(outputStream, BackupProtos.BackupFrame.newBuilder().setEnd(true).build());
|
||||
}
|
||||
|
||||
private void writeStream(@NonNull InputStream inputStream) throws IOException {
|
||||
try {
|
||||
Conversions.intToByteArray(iv, 0, counter++);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
|
||||
mac.update(iv);
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int read;
|
||||
|
||||
while ((read = inputStream.read(buffer)) != -1) {
|
||||
byte[] ciphertext = cipher.update(buffer, 0, read);
|
||||
|
||||
if (ciphertext != null) {
|
||||
outputStream.write(ciphertext);
|
||||
mac.update(ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] remainder = cipher.doFinal();
|
||||
outputStream.write(remainder);
|
||||
mac.update(remainder);
|
||||
|
||||
byte[] attachmentDigest = mac.doFinal();
|
||||
outputStream.write(attachmentDigest, 0, 10);
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void write(@NonNull OutputStream out, @NonNull BackupProtos.BackupFrame frame) throws IOException {
|
||||
try {
|
||||
Conversions.intToByteArray(iv, 0, counter++);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
|
||||
|
||||
byte[] frameCiphertext = cipher.doFinal(frame.toByteArray());
|
||||
byte[] frameMac = mac.doFinal(frameCiphertext);
|
||||
byte[] length = Conversions.intToByteArray(frameCiphertext.length + 10);
|
||||
|
||||
out.write(length);
|
||||
out.write(frameCiphertext);
|
||||
out.write(frameMac, 0, 10);
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void close() throws IOException {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,174 +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.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.LegacyMmsConnection.Apn;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Database to query APN and MMSC information
|
||||
*/
|
||||
public class ApnDatabase {
|
||||
private static final String TAG = ApnDatabase.class.getSimpleName();
|
||||
|
||||
private final SQLiteDatabase db;
|
||||
private final Context context;
|
||||
|
||||
private static final String DATABASE_NAME = "apns.db";
|
||||
private static final String ASSET_PATH = "databases" + File.separator + DATABASE_NAME;
|
||||
|
||||
private static final String TABLE_NAME = "apns";
|
||||
private static final String ID_COLUMN = "_id";
|
||||
private static final String MCC_MNC_COLUMN = "mccmnc";
|
||||
private static final String MCC_COLUMN = "mcc";
|
||||
private static final String MNC_COLUMN = "mnc";
|
||||
private static final String CARRIER_COLUMN = "carrier";
|
||||
private static final String APN_COLUMN = "apn";
|
||||
private static final String MMSC_COLUMN = "mmsc";
|
||||
private static final String PORT_COLUMN = "port";
|
||||
private static final String TYPE_COLUMN = "type";
|
||||
private static final String PROTOCOL_COLUMN = "protocol";
|
||||
private static final String BEARER_COLUMN = "bearer";
|
||||
private static final String ROAMING_PROTOCOL_COLUMN = "roaming_protocol";
|
||||
private static final String CARRIER_ENABLED_COLUMN = "carrier_enabled";
|
||||
private static final String MMS_PROXY_COLUMN = "mmsproxy";
|
||||
private static final String MMS_PORT_COLUMN = "mmsport";
|
||||
private static final String PROXY_COLUMN = "proxy";
|
||||
private static final String MVNO_MATCH_DATA_COLUMN = "mvno_match_data";
|
||||
private static final String MVNO_TYPE_COLUMN = "mvno";
|
||||
private static final String AUTH_TYPE_COLUMN = "authtype";
|
||||
private static final String USER_COLUMN = "user";
|
||||
private static final String PASSWORD_COLUMN = "password";
|
||||
private static final String SERVER_COLUMN = "server";
|
||||
|
||||
private static final String BASE_SELECTION = MCC_MNC_COLUMN + " = ?";
|
||||
|
||||
private static ApnDatabase instance = null;
|
||||
|
||||
public synchronized static ApnDatabase getInstance(Context context) throws IOException {
|
||||
if (instance == null) instance = new ApnDatabase(context.getApplicationContext());
|
||||
return instance;
|
||||
}
|
||||
|
||||
private ApnDatabase(final Context context) throws IOException {
|
||||
this.context = context;
|
||||
|
||||
File dbFile = context.getDatabasePath(DATABASE_NAME);
|
||||
|
||||
if (!dbFile.getParentFile().exists() && !dbFile.getParentFile().mkdir()) {
|
||||
throw new IOException("couldn't make databases directory");
|
||||
}
|
||||
|
||||
Util.copy(context.getAssets().open(ASSET_PATH, AssetManager.ACCESS_STREAMING),
|
||||
new FileOutputStream(dbFile));
|
||||
|
||||
try {
|
||||
this.db = SQLiteDatabase.openDatabase(context.getDatabasePath(DATABASE_NAME).getPath(),
|
||||
null,
|
||||
SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
|
||||
} catch (SQLiteException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Apn getCustomApnParameters() {
|
||||
String mmsc = TextSecurePreferences.getMmscUrl(context).trim();
|
||||
|
||||
if (!TextUtils.isEmpty(mmsc) && !mmsc.startsWith("http"))
|
||||
mmsc = "http://" + mmsc;
|
||||
|
||||
String proxy = TextSecurePreferences.getMmscProxy(context);
|
||||
String port = TextSecurePreferences.getMmscProxyPort(context);
|
||||
String user = TextSecurePreferences.getMmscUsername(context);
|
||||
String pass = TextSecurePreferences.getMmscPassword(context);
|
||||
|
||||
return new Apn(mmsc, proxy, port, user, pass);
|
||||
}
|
||||
|
||||
public Apn getDefaultApnParameters(String mccmnc, String apn) {
|
||||
if (mccmnc == null) {
|
||||
Log.w(TAG, "mccmnc was null, returning null");
|
||||
return Apn.EMPTY;
|
||||
}
|
||||
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
if (apn != null) {
|
||||
Log.w(TAG, "Querying table for MCC+MNC " + mccmnc + " and APN name " + apn);
|
||||
cursor = db.query(TABLE_NAME, null,
|
||||
BASE_SELECTION + " AND " + APN_COLUMN + " = ?",
|
||||
new String[] {mccmnc, apn},
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
if (cursor != null) cursor.close();
|
||||
Log.w(TAG, "Querying table for MCC+MNC " + mccmnc + " without APN name");
|
||||
cursor = db.query(TABLE_NAME, null,
|
||||
BASE_SELECTION,
|
||||
new String[] {mccmnc},
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
Apn params = new Apn(cursor.getString(cursor.getColumnIndexOrThrow(MMSC_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(MMS_PROXY_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(MMS_PORT_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(USER_COLUMN)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(PASSWORD_COLUMN)));
|
||||
Log.w(TAG, "Returning preferred APN " + params);
|
||||
return params;
|
||||
}
|
||||
|
||||
Log.w(TAG, "No matching APNs found, returning null");
|
||||
|
||||
return Apn.EMPTY;
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Optional<Apn> getMmsConnectionParameters(String mccmnc, String apn) {
|
||||
Apn customApn = getCustomApnParameters();
|
||||
Apn defaultApn = getDefaultApnParameters(mccmnc, apn);
|
||||
Apn result = new Apn(customApn, defaultApn,
|
||||
TextSecurePreferences.getUseCustomMmsc(context),
|
||||
TextSecurePreferences.getUseCustomMmscProxy(context),
|
||||
TextSecurePreferences.getUseCustomMmscProxyPort(context),
|
||||
TextSecurePreferences.getUseCustomMmscUsername(context),
|
||||
TextSecurePreferences.getUseCustomMmscPassword(context));
|
||||
|
||||
if (TextUtils.isEmpty(result.getMmsc())) return Optional.absent();
|
||||
else return Optional.of(result);
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.mms.pdu_alt.CharacterSets;
|
||||
import com.google.android.mms.pdu_alt.EncodedStringValue;
|
||||
import com.google.android.mms.pdu_alt.PduBody;
|
||||
import com.google.android.mms.pdu_alt.PduPart;
|
||||
import com.google.android.mms.pdu_alt.RetrieveConf;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobParameters;
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
|
||||
import org.thoughtcrime.securesms.mms.CompatMmsConnection;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||
import org.thoughtcrime.securesms.mms.PartParser;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.providers.SingleUseBlobProvider;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.DuplicateMessageException;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.LegacyMessageException;
|
||||
import org.whispersystems.libsignal.NoSessionException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MmsDownloadJob extends MasterSecretJob {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String TAG = MmsDownloadJob.class.getSimpleName();
|
||||
|
||||
private final long messageId;
|
||||
private final long threadId;
|
||||
private final boolean automatic;
|
||||
|
||||
public MmsDownloadJob(Context context, long messageId, long threadId, boolean automatic) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withPersistence()
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.withRequirement(new NetworkRequirement(context))
|
||||
.withGroupId("mms-operation")
|
||||
.withWakeLock(true, 30, TimeUnit.SECONDS)
|
||||
.create());
|
||||
|
||||
this.messageId = messageId;
|
||||
this.threadId = threadId;
|
||||
this.automatic = automatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {
|
||||
if (automatic && KeyCachingService.getMasterSecret(context) == null) {
|
||||
DatabaseFactory.getMmsDatabase(context).markIncomingNotificationReceived(threadId);
|
||||
MessageNotifier.updateNotification(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Optional<MmsDatabase.MmsNotificationInfo> notification = database.getNotification(messageId);
|
||||
|
||||
if (!notification.isPresent()) {
|
||||
Log.w(TAG, "No notification for ID: " + messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (notification.get().getContentLocation() == null) {
|
||||
throw new MmsException("Notification content location was null.");
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||
throw new MmsException("Not registered");
|
||||
}
|
||||
|
||||
database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING);
|
||||
|
||||
String contentLocation = notification.get().getContentLocation();
|
||||
byte[] transactionId = new byte[0];
|
||||
|
||||
try {
|
||||
if (notification.get().getTransactionId() != null) {
|
||||
transactionId = notification.get().getTransactionId().getBytes(CharacterSets.MIMENAME_ISO_8859_1);
|
||||
} else {
|
||||
Log.w(TAG, "No transaction ID!");
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
Log.w(TAG, "Downloading mms at " + Uri.parse(contentLocation).getHost() + ", subscription ID: " + notification.get().getSubscriptionId());
|
||||
|
||||
RetrieveConf retrieveConf = new CompatMmsConnection(context).retrieve(contentLocation, transactionId, notification.get().getSubscriptionId());
|
||||
|
||||
if (retrieveConf == null) {
|
||||
throw new MmsException("RetrieveConf was null");
|
||||
}
|
||||
|
||||
storeRetrievedMms(contentLocation, messageId, threadId, retrieveConf, notification.get().getSubscriptionId(), notification.get().getFrom());
|
||||
} catch (ApnUnavailableException e) {
|
||||
Log.w(TAG, e);
|
||||
handleDownloadError(messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
|
||||
automatic);
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
handleDownloadError(messageId, threadId,
|
||||
MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
|
||||
automatic);
|
||||
} catch (MmsRadioException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
handleDownloadError(messageId, threadId,
|
||||
MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
|
||||
automatic);
|
||||
} catch (DuplicateMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsDecryptDuplicate(messageId, threadId);
|
||||
} catch (LegacyMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsLegacyVersion(messageId, threadId);
|
||||
} catch (NoSessionException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsNoSession(messageId, threadId);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsDecryptFailed(messageId, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE);
|
||||
|
||||
if (automatic) {
|
||||
database.markIncomingNotificationReceived(threadId);
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void storeRetrievedMms(String contentLocation,
|
||||
long messageId, long threadId, RetrieveConf retrieved,
|
||||
int subscriptionId, @Nullable Address notificationFrom)
|
||||
throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException,
|
||||
LegacyMessageException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
SingleUseBlobProvider provider = SingleUseBlobProvider.getInstance();
|
||||
Optional<Address> group = Optional.absent();
|
||||
Set<Address> members = new HashSet<>();
|
||||
String body = null;
|
||||
List<Attachment> attachments = new LinkedList<>();
|
||||
|
||||
Address from;
|
||||
|
||||
if (retrieved.getFrom() != null) {
|
||||
from = Address.fromExternal(context, Util.toIsoString(retrieved.getFrom().getTextString()));
|
||||
} else if (notificationFrom != null) {
|
||||
from = notificationFrom;
|
||||
} else {
|
||||
from = Address.UNKNOWN;
|
||||
}
|
||||
|
||||
if (retrieved.getTo() != null) {
|
||||
for (EncodedStringValue toValue : retrieved.getTo()) {
|
||||
members.add(Address.fromExternal(context, Util.toIsoString(toValue.getTextString())));
|
||||
}
|
||||
}
|
||||
|
||||
if (retrieved.getCc() != null) {
|
||||
for (EncodedStringValue ccValue : retrieved.getCc()) {
|
||||
members.add(Address.fromExternal(context, Util.toIsoString(ccValue.getTextString())));
|
||||
}
|
||||
}
|
||||
|
||||
members.add(from);
|
||||
members.add(Address.fromExternal(context, TextSecurePreferences.getLocalNumber(context)));
|
||||
|
||||
if (retrieved.getBody() != null) {
|
||||
body = PartParser.getMessageText(retrieved.getBody());
|
||||
PduBody media = PartParser.getSupportedMediaParts(retrieved.getBody());
|
||||
|
||||
for (int i=0;i<media.getPartsNum();i++) {
|
||||
PduPart part = media.getPart(i);
|
||||
|
||||
if (part.getData() != null) {
|
||||
Uri uri = provider.createUri(part.getData());
|
||||
String name = null;
|
||||
|
||||
if (part.getName() != null) name = Util.toIsoString(part.getName());
|
||||
|
||||
attachments.add(new UriAttachment(uri, Util.toIsoString(part.getContentType()),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
|
||||
part.getData().length, name, false, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (members.size() > 2) {
|
||||
group = Optional.of(Address.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), true)));
|
||||
}
|
||||
|
||||
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false);
|
||||
Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
database.delete(messageId);
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDownloadError(long messageId, long threadId, int downloadStatus, boolean automatic)
|
||||
{
|
||||
MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
|
||||
|
||||
db.markDownloadState(messageId, downloadStatus);
|
||||
|
||||
if (automatic) {
|
||||
db.markIncomingNotificationReceived(threadId);
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.google.android.mms.pdu_alt.GenericPdu;
|
||||
import com.google.android.mms.pdu_alt.NotificationInd;
|
||||
import com.google.android.mms.pdu_alt.PduHeaders;
|
||||
import com.google.android.mms.pdu_alt.PduParser;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobParameters;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class MmsReceiveJob extends ContextJob {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String TAG = MmsReceiveJob.class.getSimpleName();
|
||||
|
||||
private final byte[] data;
|
||||
private final int subscriptionId;
|
||||
|
||||
public MmsReceiveJob(Context context, byte[] data, int subscriptionId) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withWakeLock(true)
|
||||
.withPersistence().create());
|
||||
|
||||
this.data = data;
|
||||
this.subscriptionId = subscriptionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun() {
|
||||
if (data == null) {
|
||||
Log.w(TAG, "Received NULL pdu, ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
PduParser parser = new PduParser(data);
|
||||
GenericPdu pdu = null;
|
||||
|
||||
try {
|
||||
pdu = parser.parse();
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
if (isNotification(pdu) && !isBlocked(pdu)) {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu, subscriptionId);
|
||||
|
||||
Log.w(TAG, "Inserted received MMS notification...");
|
||||
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new MmsDownloadJob(context,
|
||||
messageAndThreadId.first,
|
||||
messageAndThreadId.second,
|
||||
true));
|
||||
} else if (isNotification(pdu)) {
|
||||
Log.w(TAG, "*** Received blocked MMS, ignoring...");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isBlocked(GenericPdu pdu) {
|
||||
if (pdu.getFrom() != null && pdu.getFrom().getTextString() != null) {
|
||||
Recipient recipients = Recipient.from(context, Address.fromExternal(context, Util.toIsoString(pdu.getFrom().getTextString())), false);
|
||||
return recipients.isBlocked();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isNotification(GenericPdu pdu) {
|
||||
return pdu != null && pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
|
||||
}
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.android.mms.dom.smil.parser.SmilXmlSerializer;
|
||||
import com.google.android.mms.ContentType;
|
||||
import com.google.android.mms.InvalidHeaderValueException;
|
||||
import com.google.android.mms.pdu_alt.CharacterSets;
|
||||
import com.google.android.mms.pdu_alt.EncodedStringValue;
|
||||
import com.google.android.mms.pdu_alt.PduBody;
|
||||
import com.google.android.mms.pdu_alt.PduComposer;
|
||||
import com.google.android.mms.pdu_alt.PduHeaders;
|
||||
import com.google.android.mms.pdu_alt.PduPart;
|
||||
import com.google.android.mms.pdu_alt.SendConf;
|
||||
import com.google.android.mms.pdu_alt.SendReq;
|
||||
import com.google.android.mms.smil.SmilHelper;
|
||||
import com.klinker.android.send_message.Utils;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobParameters;
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.CompatMmsConnection;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.MmsSendResult;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MmsSendJob extends SendJob {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private static final String TAG = MmsSendJob.class.getSimpleName();
|
||||
|
||||
private final long messageId;
|
||||
|
||||
public MmsSendJob(Context context, long messageId) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withGroupId("mms-operation")
|
||||
.withRequirement(new NetworkRequirement(context))
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.withPersistence()
|
||||
.create());
|
||||
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSend(MasterSecret masterSecret) throws MmsException, NoSuchMessageException, IOException {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
|
||||
|
||||
try {
|
||||
SendReq pdu = constructSendPdu(message);
|
||||
|
||||
validateDestinations(message, pdu);
|
||||
|
||||
final byte[] pduBytes = getPduBytes(pdu);
|
||||
final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, message.getSubscriptionId());
|
||||
final MmsSendResult result = getSendResult(sendConf, pdu);
|
||||
|
||||
database.markAsSent(messageId, false);
|
||||
markAttachmentsUploaded(messageId, message.getAttachments());
|
||||
} catch (UndeliverableMessageException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsSentFailed(messageId);
|
||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||
} catch (InsecureFallbackApprovalException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsPendingInsecureSmsFallback(messageId);
|
||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
|
||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||
}
|
||||
|
||||
private byte[] getPduBytes(SendReq message)
|
||||
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
|
||||
{
|
||||
byte[] pduBytes = new PduComposer(context, message).make();
|
||||
|
||||
if (pduBytes == null) {
|
||||
throw new UndeliverableMessageException("PDU composition failed, null payload");
|
||||
}
|
||||
|
||||
return pduBytes;
|
||||
}
|
||||
|
||||
private MmsSendResult getSendResult(SendConf conf, SendReq message)
|
||||
throws UndeliverableMessageException
|
||||
{
|
||||
if (conf == null) {
|
||||
throw new UndeliverableMessageException("No M-Send.conf received in response to send.");
|
||||
} else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
|
||||
throw new UndeliverableMessageException("Got bad response: " + conf.getResponseStatus());
|
||||
} else if (isInconsistentResponse(message, conf)) {
|
||||
throw new UndeliverableMessageException("Mismatched response!");
|
||||
} else {
|
||||
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInconsistentResponse(SendReq message, SendConf response) {
|
||||
Log.w(TAG, "Comparing: " + Hex.toString(message.getTransactionId()));
|
||||
Log.w(TAG, "With: " + Hex.toString(response.getTransactionId()));
|
||||
return !Arrays.equals(message.getTransactionId(), response.getTransactionId());
|
||||
}
|
||||
|
||||
private void validateDestinations(EncodedStringValue[] destinations) throws UndeliverableMessageException {
|
||||
if (destinations == null) return;
|
||||
|
||||
for (EncodedStringValue destination : destinations) {
|
||||
if (destination == null || !NumberUtil.isValidSmsOrEmail(destination.getString())) {
|
||||
throw new UndeliverableMessageException("Invalid destination: " +
|
||||
(destination == null ? null : destination.getString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDestinations(OutgoingMediaMessage media, SendReq message) throws UndeliverableMessageException {
|
||||
validateDestinations(message.getTo());
|
||||
validateDestinations(message.getCc());
|
||||
validateDestinations(message.getBcc());
|
||||
|
||||
if (message.getTo() == null && message.getCc() == null && message.getBcc() == null) {
|
||||
throw new UndeliverableMessageException("No to, cc, or bcc specified!");
|
||||
}
|
||||
|
||||
if (media.isSecure()) {
|
||||
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
|
||||
}
|
||||
}
|
||||
|
||||
private SendReq constructSendPdu(OutgoingMediaMessage message)
|
||||
throws UndeliverableMessageException
|
||||
{
|
||||
SendReq req = new SendReq();
|
||||
String lineNumber = getMyNumber(context);
|
||||
Address destination = message.getRecipient().getAddress();
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId());
|
||||
List<Attachment> scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments());
|
||||
|
||||
if (!TextUtils.isEmpty(lineNumber)) {
|
||||
req.setFrom(new EncodedStringValue(lineNumber));
|
||||
} else {
|
||||
req.setFrom(new EncodedStringValue(TextSecurePreferences.getLocalNumber(context)));
|
||||
}
|
||||
|
||||
if (destination.isMmsGroup()) {
|
||||
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(destination.toGroupString(), false);
|
||||
|
||||
for (Recipient member : members) {
|
||||
if (message.getDistributionType() == ThreadDatabase.DistributionTypes.BROADCAST) {
|
||||
req.addBcc(new EncodedStringValue(member.getAddress().serialize()));
|
||||
} else {
|
||||
req.addTo(new EncodedStringValue(member.getAddress().serialize()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
req.addTo(new EncodedStringValue(destination.serialize()));
|
||||
}
|
||||
|
||||
req.setDate(System.currentTimeMillis() / 1000);
|
||||
|
||||
PduBody body = new PduBody();
|
||||
int size = 0;
|
||||
|
||||
if (!TextUtils.isEmpty(message.getBody())) {
|
||||
PduPart part = new PduPart();
|
||||
String name = String.valueOf(System.currentTimeMillis());
|
||||
part.setData(Util.toUtf8Bytes(message.getBody()));
|
||||
part.setCharset(CharacterSets.UTF_8);
|
||||
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
|
||||
part.setContentId(name.getBytes());
|
||||
part.setContentLocation((name + ".txt").getBytes());
|
||||
part.setName((name + ".txt").getBytes());
|
||||
|
||||
body.addPart(part);
|
||||
size += getPartSize(part);
|
||||
}
|
||||
|
||||
for (Attachment attachment : scaledAttachments) {
|
||||
try {
|
||||
if (attachment.getDataUri() == null) throw new IOException("Assertion failed, attachment for outgoing MMS has no data!");
|
||||
|
||||
String fileName = attachment.getFileName();
|
||||
PduPart part = new PduPart();
|
||||
|
||||
if (fileName == null) {
|
||||
fileName = String.valueOf(Math.abs(Util.getSecureRandom().nextLong()));
|
||||
String fileExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType(attachment.getContentType());
|
||||
|
||||
if (fileExtension != null) fileName = fileName + "." + fileExtension;
|
||||
}
|
||||
|
||||
if (attachment.getContentType().startsWith("text")) {
|
||||
part.setCharset(CharacterSets.UTF_8);
|
||||
}
|
||||
|
||||
part.setContentType(attachment.getContentType().getBytes());
|
||||
part.setContentLocation(fileName.getBytes());
|
||||
part.setName(fileName.getBytes());
|
||||
|
||||
int index = fileName.lastIndexOf(".");
|
||||
String contentId = (index == -1) ? fileName : fileName.substring(0, index);
|
||||
part.setContentId(contentId.getBytes());
|
||||
part.setData(Util.readFully(PartAuthority.getAttachmentStream(context, attachment.getDataUri())));
|
||||
|
||||
body.addPart(part);
|
||||
size += getPartSize(part);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
SmilXmlSerializer.serialize(SmilHelper.createSmilDocument(body), out);
|
||||
PduPart smilPart = new PduPart();
|
||||
smilPart.setContentId("smil".getBytes());
|
||||
smilPart.setContentLocation("smil.xml".getBytes());
|
||||
smilPart.setContentType(ContentType.APP_SMIL.getBytes());
|
||||
smilPart.setData(out.toByteArray());
|
||||
body.addPart(0, smilPart);
|
||||
|
||||
req.setBody(body);
|
||||
req.setMessageSize(size);
|
||||
req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
|
||||
req.setExpiry(7 * 24 * 60 * 60);
|
||||
|
||||
try {
|
||||
req.setPriority(PduHeaders.PRIORITY_NORMAL);
|
||||
req.setDeliveryReport(PduHeaders.VALUE_NO);
|
||||
req.setReadReport(PduHeaders.VALUE_NO);
|
||||
} catch (InvalidHeaderValueException e) {}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
private long getPartSize(PduPart part) {
|
||||
return part.getName().length + part.getContentLocation().length +
|
||||
part.getContentType().length + part.getData().length +
|
||||
part.getContentId().length;
|
||||
}
|
||||
|
||||
private void notifyMediaMessageDeliveryFailed(Context context, long messageId) {
|
||||
long threadId = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId);
|
||||
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
|
||||
|
||||
if (recipient != null) {
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
private String getMyNumber(Context context) throws UndeliverableMessageException {
|
||||
try {
|
||||
return Utils.getMyPhoneNumber(context);
|
||||
} catch (SecurityException e) {
|
||||
throw new UndeliverableMessageException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.telephony.SmsManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.NetworkOrServiceRequirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirement;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobParameters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SmsSendJob extends SendJob {
|
||||
|
||||
private static final long serialVersionUID = -5118520036244759718L;
|
||||
private static final String TAG = SmsSendJob.class.getSimpleName();
|
||||
|
||||
private final long messageId;
|
||||
|
||||
public SmsSendJob(Context context, long messageId, String name) {
|
||||
super(context, constructParameters(context, name));
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {}
|
||||
|
||||
@Override
|
||||
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException {
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(messageId);
|
||||
|
||||
try {
|
||||
Log.w(TAG, "Sending message: " + messageId);
|
||||
|
||||
deliver(record);
|
||||
} catch (UndeliverableMessageException ude) {
|
||||
Log.w(TAG, ude);
|
||||
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId());
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
Log.w(TAG, "onCanceled()");
|
||||
long threadId = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageId);
|
||||
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
|
||||
|
||||
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
|
||||
}
|
||||
|
||||
private void deliver(SmsMessageRecord message)
|
||||
throws UndeliverableMessageException
|
||||
{
|
||||
if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
|
||||
throw new UndeliverableMessageException("Trying to send a secure SMS?");
|
||||
}
|
||||
|
||||
String recipient = message.getIndividualRecipient().getAddress().serialize();
|
||||
|
||||
// See issue #1516 for bug report, and discussion on commits related to #4833 for problems
|
||||
// related to the original fix to #1516. This still may not be a correct fix if networks allow
|
||||
// SMS/MMS sending to alphanumeric recipients other than email addresses, but should also
|
||||
// help to fix issue #3099.
|
||||
if (!NumberUtil.isValidEmail(recipient)) {
|
||||
recipient = PhoneNumberUtils.stripSeparators(PhoneNumberUtils.convertKeypadLettersToDigits(recipient));
|
||||
}
|
||||
|
||||
if (!NumberUtil.isValidSmsOrEmail(recipient)) {
|
||||
throw new UndeliverableMessageException("Not a valid SMS destination! " + recipient);
|
||||
}
|
||||
|
||||
ArrayList<String> messages = SmsManager.getDefault().divideMessage(message.getBody());
|
||||
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false);
|
||||
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
|
||||
|
||||
// NOTE 11/04/14 -- There's apparently a bug where for some unknown recipients
|
||||
// and messages, this will throw an NPE. We have no idea why, so we're just
|
||||
// catching it and marking the message as a failure. That way at least it doesn't
|
||||
// repeatedly crash every time you start the app.
|
||||
try {
|
||||
getSmsManagerFor(message.getSubscriptionId()).sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
|
||||
} catch (NullPointerException | IllegalArgumentException npe) {
|
||||
Log.w(TAG, npe);
|
||||
Log.w(TAG, "Recipient: " + recipient);
|
||||
Log.w(TAG, "Message Parts: " + messages.size());
|
||||
|
||||
try {
|
||||
for (int i=0;i<messages.size();i++) {
|
||||
getSmsManagerFor(message.getSubscriptionId()).sendTextMessage(recipient, null, messages.get(i),
|
||||
sentIntents.get(i),
|
||||
deliveredIntents == null ? null : deliveredIntents.get(i));
|
||||
}
|
||||
} catch (NullPointerException | IllegalArgumentException npe2) {
|
||||
Log.w(TAG, npe);
|
||||
throw new UndeliverableMessageException(npe2);
|
||||
}
|
||||
} catch (SecurityException se) {
|
||||
Log.w(TAG, se);
|
||||
throw new UndeliverableMessageException(se);
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<PendingIntent> constructSentIntents(long messageId, long type,
|
||||
ArrayList<String> messages, boolean secure)
|
||||
{
|
||||
ArrayList<PendingIntent> sentIntents = new ArrayList<>(messages.size());
|
||||
|
||||
for (String ignored : messages) {
|
||||
sentIntents.add(PendingIntent.getBroadcast(context, 0,
|
||||
constructSentIntent(context, messageId, type, secure, false),
|
||||
0));
|
||||
}
|
||||
|
||||
return sentIntents;
|
||||
}
|
||||
|
||||
private ArrayList<PendingIntent> constructDeliveredIntents(long messageId, long type, ArrayList<String> messages) {
|
||||
if (!TextSecurePreferences.isSmsDeliveryReportsEnabled(context)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ArrayList<PendingIntent> deliveredIntents = new ArrayList<>(messages.size());
|
||||
|
||||
for (String ignored : messages) {
|
||||
deliveredIntents.add(PendingIntent.getBroadcast(context, 0,
|
||||
constructDeliveredIntent(context, messageId, type),
|
||||
0));
|
||||
}
|
||||
|
||||
return deliveredIntents;
|
||||
}
|
||||
|
||||
private Intent constructSentIntent(Context context, long messageId, long type,
|
||||
boolean upgraded, boolean push)
|
||||
{
|
||||
Intent pending = new Intent(SmsDeliveryListener.SENT_SMS_ACTION,
|
||||
Uri.parse("custom://" + messageId + System.currentTimeMillis()),
|
||||
context, SmsDeliveryListener.class);
|
||||
|
||||
pending.putExtra("type", type);
|
||||
pending.putExtra("message_id", messageId);
|
||||
pending.putExtra("upgraded", upgraded);
|
||||
pending.putExtra("push", push);
|
||||
|
||||
return pending;
|
||||
}
|
||||
|
||||
private Intent constructDeliveredIntent(Context context, long messageId, long type) {
|
||||
Intent pending = new Intent(SmsDeliveryListener.DELIVERED_SMS_ACTION,
|
||||
Uri.parse("custom://" + messageId + System.currentTimeMillis()),
|
||||
context, SmsDeliveryListener.class);
|
||||
pending.putExtra("type", type);
|
||||
pending.putExtra("message_id", messageId);
|
||||
|
||||
return pending;
|
||||
}
|
||||
|
||||
private SmsManager getSmsManagerFor(int subscriptionId) {
|
||||
if (Build.VERSION.SDK_INT >= 22 && subscriptionId != -1) {
|
||||
return SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
|
||||
} else {
|
||||
return SmsManager.getDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private static JobParameters constructParameters(Context context, String name) {
|
||||
JobParameters.Builder builder = JobParameters.newBuilder()
|
||||
.withPersistence()
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.withRetryCount(15)
|
||||
.withGroupId(name);
|
||||
|
||||
if (TextSecurePreferences.isWifiSmsEnabled(context)) {
|
||||
builder.withRequirement(new NetworkOrServiceRequirement(context));
|
||||
} else {
|
||||
builder.withRequirement(new ServiceRequirement(context));
|
||||
}
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.telephony.SmsManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobParameters;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
|
||||
|
||||
public class SmsSentJob extends MasterSecretJob {
|
||||
|
||||
private static final long serialVersionUID = -2624694558755317560L;
|
||||
private static final String TAG = SmsSentJob.class.getSimpleName();
|
||||
|
||||
private final long messageId;
|
||||
private final String action;
|
||||
private final int result;
|
||||
|
||||
public SmsSentJob(Context context, long messageId, String action, int result) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withPersistence()
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.create());
|
||||
|
||||
this.messageId = messageId;
|
||||
this.action = action;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) {
|
||||
Log.w(TAG, "Got SMS callback: " + action + " , " + result);
|
||||
|
||||
switch (action) {
|
||||
case SmsDeliveryListener.SENT_SMS_ACTION:
|
||||
handleSentResult(messageId, result);
|
||||
break;
|
||||
case SmsDeliveryListener.DELIVERED_SMS_ACTION:
|
||||
handleDeliveredResult(messageId, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
|
||||
}
|
||||
|
||||
private void handleDeliveredResult(long messageId, int result) {
|
||||
DatabaseFactory.getSmsDatabase(context).markStatus(messageId, result);
|
||||
}
|
||||
|
||||
private void handleSentResult(long messageId, int result) {
|
||||
try {
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(messageId);
|
||||
|
||||
switch (result) {
|
||||
case Activity.RESULT_OK:
|
||||
database.markAsSent(messageId, false);
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_NO_SERVICE:
|
||||
case SmsManager.RESULT_ERROR_RADIO_OFF:
|
||||
Log.w(TAG, "Service connectivity problem, requeuing...");
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new SmsSendJob(context, messageId, record.getIndividualRecipient().getAddress().serialize()));
|
||||
break;
|
||||
default:
|
||||
database.markAsSentFailed(messageId);
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
|
||||
}
|
||||
} catch (NoSuchMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
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.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CompatMmsConnection implements OutgoingMmsConnection, IncomingMmsConnection {
|
||||
private static final String TAG = CompatMmsConnection.class.getSimpleName();
|
||||
|
||||
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.w(TAG, "Sending via Lollipop API");
|
||||
return new OutgoingLollipopMmsConnection(context).send(pduBytes, subscriptionId);
|
||||
} catch (UndeliverableMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
Log.w(TAG, "Falling back to legacy connection...");
|
||||
}
|
||||
|
||||
if (subscriptionId == -1) {
|
||||
Log.w(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.w(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.w(TAG, "Receiving via Lollipop API");
|
||||
try {
|
||||
return new IncomingLollipopMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
Log.w(TAG, "Falling back to receiving via legacy connection");
|
||||
}
|
||||
|
||||
if (VERSION.SDK_INT < 22 || subscriptionId == -1) {
|
||||
Log.w(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.w(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,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.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
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 java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class IncomingLegacyMmsConnection extends LegacyMmsConnection implements IncomingMmsConnection {
|
||||
private static final String TAG = IncomingLegacyMmsConnection.class.getSimpleName();
|
||||
|
||||
public IncomingLegacyMmsConnection(Context context) throws ApnUnavailableException {
|
||||
super(context);
|
||||
}
|
||||
|
||||
private HttpUriRequest constructRequest(Apn contentApn, boolean useProxy) throws IOException {
|
||||
HttpGetHC4 request;
|
||||
|
||||
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.w(TAG, "Connecting directly...");
|
||||
try {
|
||||
return retrieve(contentApn, transactionId, false, false);
|
||||
} catch (IOException | ApnUnavailableException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "Changing radio to MMS mode..");
|
||||
radio.connect();
|
||||
|
||||
try {
|
||||
Log.w(TAG, "Downloading in MMS mode with proxy...");
|
||||
|
||||
try {
|
||||
return retrieve(contentApn, transactionId, true, true);
|
||||
} catch (IOException | ApnUnavailableException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
Log.w(TAG, "Downloading in MMS mode without proxy...");
|
||||
|
||||
return retrieve(contentApn, transactionId, true, false);
|
||||
|
||||
} finally {
|
||||
radio.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public RetrieveConf retrieve(Apn contentApn, byte[] transactionId, boolean usingMmsRadio, boolean useProxyIfAvailable)
|
||||
throws IOException, ApnUnavailableException
|
||||
{
|
||||
byte[] pdu = null;
|
||||
|
||||
final boolean useProxy = useProxyIfAvailable && contentApn.hasProxy();
|
||||
final String targetHost = useProxy
|
||||
? contentApn.getProxy()
|
||||
: Uri.parse(contentApn.getMmsc()).getHost();
|
||||
if (checkRouteToHost(context, targetHost, usingMmsRadio)) {
|
||||
Log.w(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,296 +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.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
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.thoughtcrime.securesms.database.ApnDatabase;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TelephonyUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
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.Set;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class LegacyMmsConnection {
|
||||
|
||||
public static final String USER_AGENT = "Android-Mms/2.0";
|
||||
|
||||
private static final String TAG = LegacyMmsConnection.class.getSimpleName();
|
||||
|
||||
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.w(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.w(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();
|
||||
Util.copy(in, baos);
|
||||
|
||||
Log.w(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.w(TAG, "connecting to " + apn.getMmsc());
|
||||
|
||||
CloseableHttpClient client = null;
|
||||
CloseableHttpResponse response = null;
|
||||
try {
|
||||
client = constructHttpClient();
|
||||
response = client.execute(request);
|
||||
|
||||
Log.w(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 = TelephonyUtil.getManager(context).getLine1Number(); ;
|
||||
|
||||
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));
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
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 String toString() {
|
||||
return Apn.class.getSimpleName() +
|
||||
"{ mmsc: \"" + mmsc + "\"" +
|
||||
", proxy: " + (proxy == null ? "none" : '"' + proxy + '"') +
|
||||
", port: " + (port == null ? "(none)" : port) +
|
||||
", user: " + (username == null ? "none" : '"' + username + '"') +
|
||||
", pass: " + (password == null ? "none" : '"' + password + '"') + " }";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +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 android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
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.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class OutgoingLegacyMmsConnection extends LegacyMmsConnection implements OutgoingMmsConnection {
|
||||
private final static String TAG = OutgoingLegacyMmsConnection.class.getSimpleName();
|
||||
|
||||
public OutgoingLegacyMmsConnection(Context context) throws ApnUnavailableException {
|
||||
super(context);
|
||||
}
|
||||
|
||||
private HttpUriRequest constructRequest(byte[] pduBytes, boolean useProxy)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
HttpPostHC4 request = new HttpPostHC4(apn.getMmsc());
|
||||
for (Header header : getBaseHeaders()) {
|
||||
request.addHeader(header);
|
||||
}
|
||||
|
||||
request.setEntity(new ByteArrayEntityHC4(pduBytes));
|
||||
if (useProxy) {
|
||||
HttpHost proxy = new HttpHost(apn.getProxy(), apn.getPort());
|
||||
request.setConfig(RequestConfig.custom().setProxy(proxy).build());
|
||||
}
|
||||
return request;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new IOException(iae);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendNotificationReceived(byte[] pduBytes, boolean usingMmsRadio, boolean useProxyIfAvailable)
|
||||
throws IOException
|
||||
{
|
||||
sendBytes(pduBytes, usingMmsRadio, useProxyIfAvailable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SendConf send(@NonNull byte[] pduBytes, int subscriptionId) throws UndeliverableMessageException {
|
||||
try {
|
||||
MmsRadio radio = MmsRadio.getInstance(context);
|
||||
|
||||
if (isDirectConnect()) {
|
||||
Log.w(TAG, "Sending MMS directly without radio change...");
|
||||
try {
|
||||
return send(pduBytes, false, false);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "Sending MMS with radio change and proxy...");
|
||||
radio.connect();
|
||||
|
||||
try {
|
||||
try {
|
||||
return send(pduBytes, true, true);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
Log.w(TAG, "Sending MMS with radio change and without proxy...");
|
||||
|
||||
try {
|
||||
return send(pduBytes, true, false);
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
throw new UndeliverableMessageException(ioe);
|
||||
}
|
||||
} finally {
|
||||
radio.disconnect();
|
||||
}
|
||||
|
||||
} catch (MmsRadioException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new UndeliverableMessageException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private SendConf send(byte[] pduBytes, boolean useMmsRadio, boolean useProxyIfAvailable) throws IOException {
|
||||
byte[] response = sendBytes(pduBytes, useMmsRadio, useProxyIfAvailable);
|
||||
return (SendConf) new PduParser(response).parse();
|
||||
}
|
||||
|
||||
private byte[] sendBytes(byte[] pduBytes, boolean useMmsRadio, boolean useProxyIfAvailable) throws IOException {
|
||||
final boolean useProxy = useProxyIfAvailable && apn.hasProxy();
|
||||
final String targetHost = useProxy
|
||||
? apn.getProxy()
|
||||
: Uri.parse(apn.getMmsc()).getHost();
|
||||
|
||||
Log.w(TAG, "Sending MMS of length: " + pduBytes.length
|
||||
+ (useMmsRadio ? ", using mms radio" : "")
|
||||
+ (useProxy ? ", using proxy" : ""));
|
||||
|
||||
try {
|
||||
if (checkRouteToHost(context, targetHost, useMmsRadio)) {
|
||||
Log.w(TAG, "got successful route to host " + targetHost);
|
||||
byte[] response = execute(constructRequest(pduBytes, useProxy));
|
||||
if (response != null) return response;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
}
|
||||
throw new IOException("Connection manager could not obtain route to host.");
|
||||
}
|
||||
|
||||
|
||||
public static boolean isConnectionPossible(Context context) {
|
||||
try {
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(MmsRadio.TYPE_MOBILE_MMS);
|
||||
if (networkInfo == null) {
|
||||
Log.w(TAG, "MMS network info was null, unsupported by this device");
|
||||
return false;
|
||||
}
|
||||
|
||||
getApn(context);
|
||||
return true;
|
||||
} catch (ApnUnavailableException e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.provider.Telephony;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.jobs.MmsReceiveJob;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class MmsListener extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = MmsListener.class.getSimpleName();
|
||||
|
||||
private boolean isRelevant(Context context, Intent intent) {
|
||||
if (!ApplicationMigrationService.isDatabaseImported(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
|
||||
Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction()) &&
|
||||
Util.isDefaultSmsProvider(context))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT &&
|
||||
TextSecurePreferences.isInterceptAllMmsEnabled(context))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.w(TAG, "Got MMS broadcast..." + intent.getAction());
|
||||
|
||||
if ((Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION.equals(intent.getAction()) &&
|
||||
Util.isDefaultSmsProvider(context)) ||
|
||||
(Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction()) &&
|
||||
isRelevant(context, intent)))
|
||||
{
|
||||
Log.w(TAG, "Relevant!");
|
||||
int subscriptionId = intent.getExtras().getInt("subscription", -1);
|
||||
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new MmsReceiveJob(context, intent.getByteArrayExtra("data"), subscriptionId));
|
||||
|
||||
abortBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.telephony.SmsMessage;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.SmsSentJob;
|
||||
|
||||
public class SmsDeliveryListener extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = SmsDeliveryListener.class.getSimpleName();
|
||||
|
||||
public static final String SENT_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SENT_SMS_ACTION";
|
||||
public static final String DELIVERED_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DELIVERED_SMS_ACTION";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
long messageId = intent.getLongExtra("message_id", -1);
|
||||
|
||||
switch (intent.getAction()) {
|
||||
case SENT_SMS_ACTION:
|
||||
int result = getResultCode();
|
||||
|
||||
jobManager.add(new SmsSentJob(context, messageId, SENT_SMS_ACTION, result));
|
||||
break;
|
||||
case DELIVERED_SMS_ACTION:
|
||||
byte[] pdu = intent.getByteArrayExtra("pdu");
|
||||
|
||||
if (pdu == null) {
|
||||
Log.w(TAG, "No PDU in delivery receipt!");
|
||||
break;
|
||||
}
|
||||
|
||||
SmsMessage message = SmsMessage.createFromPdu(pdu);
|
||||
|
||||
if (message == null) {
|
||||
Log.w(TAG, "Delivery receipt failed to parse!");
|
||||
break;
|
||||
}
|
||||
|
||||
int status = message.getStatus();
|
||||
|
||||
Log.w(TAG, "Original status: " + status);
|
||||
|
||||
// Note: https://developer.android.com/reference/android/telephony/SmsMessage.html#getStatus()
|
||||
// " CDMA: For not interfering with status codes from GSM, the value is shifted to the bits 31-16"
|
||||
// Note: https://stackoverflow.com/a/33240109
|
||||
if ("3gpp2".equals(intent.getStringExtra("format"))) {
|
||||
Log.w(TAG, "Correcting for CDMA delivery receipt...");
|
||||
if (status >> 24 <= 0) status = SmsDatabase.Status.STATUS_COMPLETE;
|
||||
else if (status >> 24 == 2) status = SmsDatabase.Status.STATUS_PENDING;
|
||||
else if (status >> 24 == 3) status = SmsDatabase.Status.STATUS_FAILED;
|
||||
}
|
||||
|
||||
jobManager.add(new SmsSentJob(context, messageId, DELIVERED_SMS_ACTION, status));
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Unknown action: " + intent.getAction());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
@@ -34,11 +33,9 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.MmsSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
@@ -209,13 +206,13 @@ public class MessageSender {
|
||||
}
|
||||
|
||||
private static void sendSms(Context context, Recipient recipient, long messageId) {
|
||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
jobManager.add(new SmsSendJob(context, messageId, recipient.getName()));
|
||||
// JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
// jobManager.add(new SmsSendJob(context, messageId, recipient.getName()));
|
||||
}
|
||||
|
||||
private static void sendMms(Context context, long messageId) {
|
||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
jobManager.add(new MmsSendJob(context, messageId));
|
||||
// JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
// jobManager.add(new MmsSendJob(context, messageId));
|
||||
}
|
||||
|
||||
private static boolean isPushTextSend(Context context, Recipient recipient, boolean keyExchange) {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
public class MmsCharacterCalculator extends CharacterCalculator {
|
||||
|
||||
private static final int MAX_SIZE = 5000;
|
||||
|
||||
@Override
|
||||
public CharacterState calculateCharacters(String messageBody) {
|
||||
return new CharacterState(1, MAX_SIZE - messageBody.length(), MAX_SIZE);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +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.util;
|
||||
|
||||
import android.telephony.SmsMessage;
|
||||
import android.util.Log;
|
||||
|
||||
public class SmsCharacterCalculator extends CharacterCalculator {
|
||||
|
||||
private static final String TAG = SmsCharacterCalculator.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public CharacterState calculateCharacters(String messageBody) {
|
||||
int[] length;
|
||||
int messagesSpent;
|
||||
int charactersSpent;
|
||||
int charactersRemaining;
|
||||
|
||||
try {
|
||||
length = SmsMessage.calculateLength(messageBody, false);
|
||||
messagesSpent = length[0];
|
||||
charactersSpent = length[1];
|
||||
charactersRemaining = length[2];
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(TAG, e);
|
||||
messagesSpent = 1;
|
||||
charactersSpent = messageBody.length();
|
||||
charactersRemaining = 1000;
|
||||
}
|
||||
|
||||
int maxMessageSize;
|
||||
|
||||
if (messagesSpent > 0) {
|
||||
maxMessageSize = (charactersSpent + charactersRemaining) / messagesSpent;
|
||||
} else {
|
||||
maxMessageSize = (charactersSpent + charactersRemaining);
|
||||
}
|
||||
|
||||
return new CharacterState(messagesSpent, charactersRemaining, maxMessageSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.components.ComposeText;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -374,11 +373,6 @@ public class Util {
|
||||
return 90 - age;
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||
public static boolean isMmsCapable(Context context) {
|
||||
return (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) || OutgoingLegacyMmsConnection.isConnectionPossible(context);
|
||||
}
|
||||
|
||||
public static boolean isMainThread() {
|
||||
return Looper.myLooper() == Looper.getMainLooper();
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
public class TextSecureTestCase extends InstrumentationTestCase {
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
System.setProperty("dexmaker.dexcache", getInstrumentation().getTargetContext().getCacheDir().getPath());
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return getInstrumentation().getContext();
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyBoolean;
|
||||
import static org.mockito.Matchers.anyFloat;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.powermock.api.mockito.PowerMockito.mock;
|
||||
import static org.powermock.api.mockito.PowerMockito.mockStatic;
|
||||
import static org.powermock.api.mockito.PowerMockito.when;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({ Log.class, Handler.class, Looper.class, TextUtils.class, PreferenceManager.class })
|
||||
public abstract class BaseUnitTest {
|
||||
|
||||
protected Context context = mock(Context.class);
|
||||
protected SharedPreferences sharedPreferences = mock(SharedPreferences.class);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mockStatic(Looper.class);
|
||||
mockStatic(Log.class);
|
||||
mockStatic(Handler.class);
|
||||
mockStatic(TextUtils.class);
|
||||
mockStatic(PreferenceManager.class);
|
||||
|
||||
when(PreferenceManager.getDefaultSharedPreferences(any(Context.class))).thenReturn(sharedPreferences);
|
||||
when(Looper.getMainLooper()).thenReturn(null);
|
||||
PowerMockito.whenNew(Handler.class).withAnyArguments().thenReturn(null);
|
||||
|
||||
Answer<?> logAnswer = new Answer<Void>() {
|
||||
@Override public Void answer(InvocationOnMock invocation) throws Throwable {
|
||||
final String tag = (String)invocation.getArguments()[0];
|
||||
final String msg = (String)invocation.getArguments()[1];
|
||||
System.out.println(invocation.getMethod().getName().toUpperCase() + "/[" + tag + "] " + msg);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
PowerMockito.doAnswer(logAnswer).when(Log.class, "d", anyString(), anyString());
|
||||
PowerMockito.doAnswer(logAnswer).when(Log.class, "i", anyString(), anyString());
|
||||
PowerMockito.doAnswer(logAnswer).when(Log.class, "w", anyString(), anyString());
|
||||
PowerMockito.doAnswer(logAnswer).when(Log.class, "e", anyString(), anyString());
|
||||
PowerMockito.doAnswer(logAnswer).when(Log.class, "wtf", anyString(), anyString());
|
||||
|
||||
PowerMockito.doAnswer(new Answer<Boolean>() {
|
||||
@Override
|
||||
public Boolean answer(InvocationOnMock invocation) throws Throwable {
|
||||
final String s = (String)invocation.getArguments()[0];
|
||||
return s == null || s.length() == 0;
|
||||
}
|
||||
}).when(TextUtils.class, "isEmpty", anyString());
|
||||
|
||||
when(sharedPreferences.getString(anyString(), anyString())).thenReturn("");
|
||||
when(sharedPreferences.getLong(anyString(), anyLong())).thenReturn(0L);
|
||||
when(sharedPreferences.getInt(anyString(), anyInt())).thenReturn(0);
|
||||
when(sharedPreferences.getBoolean(anyString(), anyBoolean())).thenReturn(false);
|
||||
when(sharedPreferences.getFloat(anyString(), anyFloat())).thenReturn(0f);
|
||||
when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPreferences);
|
||||
when(context.getPackageName()).thenReturn("org.thoughtcrime.securesms");
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
public class AddressTest {
|
||||
|
||||
@Before
|
||||
public void setup() {}
|
||||
|
||||
@Test
|
||||
public void testAddressString() throws Exception {
|
||||
Address.ExternalAddressFormatter formatter = new Address.ExternalAddressFormatter("+14152222222");
|
||||
assertEquals(formatter.format("bonbon"), "bonbon");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddressShortCode() throws Exception {
|
||||
Address.ExternalAddressFormatter formatter = new Address.ExternalAddressFormatter("+14152222222");
|
||||
assertEquals(formatter.format("40404"), "40404");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmailAddress() throws Exception {
|
||||
Address.ExternalAddressFormatter formatter = new Address.ExternalAddressFormatter("+14152222222");
|
||||
assertEquals(formatter.format("junk@junk.net"), "junk@junk.net");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumberArbitrary() throws Exception {
|
||||
Address.ExternalAddressFormatter formatter = new Address.ExternalAddressFormatter("+14152222222");
|
||||
assertEquals(formatter.format("(415) 111-1122"), "+14151111122");
|
||||
assertEquals(formatter.format("(415) 111 1123"), "+14151111123");
|
||||
assertEquals(formatter.format("415-111-1124"), "+14151111124");
|
||||
assertEquals(formatter.format("415.111.1125"), "+14151111125");
|
||||
assertEquals(formatter.format("+1 415.111.1126"), "+14151111126");
|
||||
assertEquals(formatter.format("+1 415 111 1127"), "+14151111127");
|
||||
assertEquals(formatter.format("+1 (415) 111 1128"), "+14151111128");
|
||||
|
||||
formatter = new Address.ExternalAddressFormatter("+442079460010");
|
||||
assertEquals(formatter.format("(020) 7946 0018"), "+442079460018");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroup() throws Exception {
|
||||
Address.ExternalAddressFormatter formatter = new Address.ExternalAddressFormatter("+14152222222");
|
||||
assertEquals(formatter.format("__textsecure_group__!foobar"), "__textsecure_group__!foobar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLostLocalNumber() throws Exception {
|
||||
Address.ExternalAddressFormatter formatter = new Address.ExternalAddressFormatter("US", true);
|
||||
assertEquals(formatter.format("(415) 111-1122"), "+14151111122");
|
||||
}
|
||||
|
||||
}
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class CursorRecyclerViewAdapterTest {
|
||||
private CursorRecyclerViewAdapter adapter;
|
||||
private Context context;
|
||||
private Cursor cursor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context = mock(Context.class);
|
||||
cursor = mock(Cursor.class);
|
||||
when(cursor.getCount()).thenReturn(100);
|
||||
when(cursor.moveToPosition(anyInt())).thenReturn(true);
|
||||
|
||||
adapter = new CursorRecyclerViewAdapter(context, cursor) {
|
||||
@Override
|
||||
public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanityCount() throws Exception {
|
||||
assertEquals(adapter.getItemCount(), 100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderCount() throws Exception {
|
||||
adapter.setHeaderView(new View(context));
|
||||
assertEquals(adapter.getItemCount(), 101);
|
||||
|
||||
assertEquals(adapter.getItemViewType(0), CursorRecyclerViewAdapter.HEADER_TYPE);
|
||||
assertNotEquals(adapter.getItemViewType(1), CursorRecyclerViewAdapter.HEADER_TYPE);
|
||||
assertNotEquals(adapter.getItemViewType(100), CursorRecyclerViewAdapter.HEADER_TYPE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFooterCount() throws Exception {
|
||||
adapter.setFooterView(new View(context));
|
||||
assertEquals(adapter.getItemCount(), 101);
|
||||
assertEquals(adapter.getItemViewType(100), CursorRecyclerViewAdapter.FOOTER_TYPE);
|
||||
assertNotEquals(adapter.getItemViewType(0), CursorRecyclerViewAdapter.FOOTER_TYPE);
|
||||
assertNotEquals(adapter.getItemViewType(99), CursorRecyclerViewAdapter.FOOTER_TYPE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderFooterCount() throws Exception {
|
||||
adapter.setHeaderView(new View(context));
|
||||
adapter.setFooterView(new View(context));
|
||||
assertEquals(adapter.getItemCount(), 102);
|
||||
assertEquals(adapter.getItemViewType(101), CursorRecyclerViewAdapter.FOOTER_TYPE);
|
||||
assertEquals(adapter.getItemViewType(0), CursorRecyclerViewAdapter.HEADER_TYPE);
|
||||
assertNotEquals(adapter.getItemViewType(1), CursorRecyclerViewAdapter.HEADER_TYPE);
|
||||
assertNotEquals(adapter.getItemViewType(100), CursorRecyclerViewAdapter.FOOTER_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.thoughtcrime.securesms.BaseUnitTest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Matchers.anyBoolean;
|
||||
import static org.mockito.Matchers.contains;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class SmsListenerTest extends BaseUnitTest {
|
||||
private static Map<String, String> CHALLENGES = new HashMap<String,String>() {{
|
||||
put("Your TextSecure verification code: 337-337", "337337");
|
||||
put("XXX\nYour TextSecure verification code: 1337-1337", "13371337");
|
||||
put("Your TextSecure verification code: 337-1337", "3371337");
|
||||
put("Your TextSecure verification code: 1337-337", "1337337");
|
||||
put("Your TextSecure verification code: 1337-1337", "13371337");
|
||||
put("XXXYour TextSecure verification code: 1337-1337", "13371337");
|
||||
put("Your TextSecure verification code: 1337-1337XXX", "13371337");
|
||||
put("Your TextSecure verification code 1337-1337", "13371337");
|
||||
|
||||
put("Your Signal verification code: 337-337", "337337");
|
||||
put("XXX\nYour Signal verification code: 1337-1337", "13371337");
|
||||
put("Your Signal verification code: 337-1337", "3371337");
|
||||
put("Your Signal verification code: 1337-337", "1337337");
|
||||
put("Your Signal verification code: 1337-1337", "13371337");
|
||||
put("XXXYour Signal verification code: 1337-1337", "13371337");
|
||||
put("Your Signal verification code: 1337-1337XXX", "13371337");
|
||||
put("Your Signal verification code 1337-1337", "13371337");
|
||||
}};
|
||||
|
||||
private SmsListener listener;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
listener = new SmsListener();
|
||||
when(sharedPreferences.getBoolean(contains("pref_verifying"), anyBoolean())).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChallenges() throws Exception {
|
||||
for (Entry<String,String> challenge : CHALLENGES.entrySet()) {
|
||||
if (!listener.isChallenge(context, challenge.getKey())) {
|
||||
throw new AssertionFailedError("SmsListener didn't recognize body as a challenge.");
|
||||
}
|
||||
assertEquals(listener.parseChallenge(challenge.getKey()), challenge.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
public class DelimiterUtilTest {
|
||||
|
||||
@Before
|
||||
public void setup() {}
|
||||
|
||||
@Test
|
||||
public void testEscape() {
|
||||
assertEquals(DelimiterUtil.escape("MTV Music", ' '), "MTV\\ Music");
|
||||
assertEquals(DelimiterUtil.escape("MTV Music", ' '), "MTV\\ \\ Music");
|
||||
|
||||
assertEquals(DelimiterUtil.escape("MTV,Music", ','), "MTV\\,Music");
|
||||
assertEquals(DelimiterUtil.escape("MTV,,Music", ','), "MTV\\,\\,Music");
|
||||
|
||||
assertEquals(DelimiterUtil.escape("MTV Music", '+'), "MTV Music");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplit() {
|
||||
String[] parts = DelimiterUtil.split("MTV\\ Music", ' ');
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "MTV\\ Music");
|
||||
|
||||
parts = DelimiterUtil.split("MTV Music", ' ');
|
||||
assertEquals(parts.length, 2);
|
||||
assertEquals(parts[0], "MTV");
|
||||
assertEquals(parts[1], "Music");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeSplit() {
|
||||
String input = "MTV Music";
|
||||
String intermediate = DelimiterUtil.escape(input, ' ');
|
||||
String[] parts = DelimiterUtil.split(intermediate, ' ');
|
||||
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "MTV\\ Music");
|
||||
assertEquals(DelimiterUtil.unescape(parts[0], ' '), "MTV Music");
|
||||
|
||||
input = "MTV\\ Music";
|
||||
intermediate = DelimiterUtil.escape(input, ' ');
|
||||
parts = DelimiterUtil.split(intermediate, ' ');
|
||||
|
||||
assertEquals(parts.length, 1);
|
||||
assertEquals(parts[0], "MTV\\\\ Music");
|
||||
assertEquals(DelimiterUtil.unescape(parts[0], ' '), "MTV\\ Music");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.thoughtcrime.securesms.BaseUnitTest;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ListPartitionTest extends BaseUnitTest {
|
||||
|
||||
@Test public void testPartitionEven() {
|
||||
List<Integer> list = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<100;i++) {
|
||||
list.add(i);
|
||||
}
|
||||
|
||||
List<List<Integer>> partitions = Util.partition(list, 10);
|
||||
|
||||
assertEquals(partitions.size(), 10);
|
||||
|
||||
int counter = 0;
|
||||
|
||||
for (int i=0;i<partitions.size();i++) {
|
||||
List<Integer> partition = partitions.get(i);
|
||||
assertEquals(partition.size(), 10);
|
||||
|
||||
for (int j=0;j<partition.size();j++) {
|
||||
assertEquals((int)partition.get(j), counter++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void testPartitionOdd() {
|
||||
List<Integer> list = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<100;i++) {
|
||||
list.add(i);
|
||||
}
|
||||
|
||||
list.add(100);
|
||||
|
||||
List<List<Integer>> partitions = Util.partition(list, 10);
|
||||
|
||||
assertEquals(partitions.size(), 11);
|
||||
|
||||
int counter = 0;
|
||||
|
||||
for (int i=0;i<partitions.size()-1;i++) {
|
||||
List<Integer> partition = partitions.get(i);
|
||||
assertEquals(partition.size(), 10);
|
||||
|
||||
for (int j=0;j<partition.size();j++) {
|
||||
assertEquals((int)partition.get(j), counter++);
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(partitions.get(10).size(), 1);
|
||||
assertEquals((int)partitions.get(10).get(0), 100);
|
||||
}
|
||||
|
||||
@Test public void testPathological() {
|
||||
List<Integer> list = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<100;i++) {
|
||||
list.add(i);
|
||||
}
|
||||
|
||||
List<List<Integer>> partitions = Util.partition(list, 1);
|
||||
|
||||
assertEquals(partitions.size(), 100);
|
||||
|
||||
int counter = 0;
|
||||
|
||||
for (int i=0;i<partitions.size();i++) {
|
||||
List<Integer> partition = partitions.get(i);
|
||||
assertEquals(partition.size(), 1);
|
||||
|
||||
for (int j=0;j<partition.size();j++) {
|
||||
assertEquals((int)partition.get(j), counter++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.thoughtcrime.securesms.BaseUnitTest;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class PhoneNumberFormatterTest extends BaseUnitTest {
|
||||
private static final String LOCAL_NUMBER_US = "+15555555555";
|
||||
private static final String NUMBER_CH = "+41446681800";
|
||||
private static final String NUMBER_UK = "+442079460018";
|
||||
private static final String NUMBER_DE = "+4930123456";
|
||||
private static final String NUMBER_MOBILE_DE = "+49171123456";
|
||||
private static final String COUNTRY_CODE_CH = "41";
|
||||
private static final String COUNTRY_CODE_UK = "44";
|
||||
private static final String COUNTRY_CODE_DE = "49";
|
||||
|
||||
@Test
|
||||
public void testFormatNumber() throws Exception, InvalidNumberException {
|
||||
assertThat(PhoneNumberFormatter.formatNumber("(555) 555-5555", LOCAL_NUMBER_US)).isEqualTo(LOCAL_NUMBER_US);
|
||||
assertThat(PhoneNumberFormatter.formatNumber("555-5555", LOCAL_NUMBER_US)).isEqualTo(LOCAL_NUMBER_US);
|
||||
assertThat(PhoneNumberFormatter.formatNumber("(123) 555-5555", LOCAL_NUMBER_US)).isNotEqualTo(LOCAL_NUMBER_US);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatNumberEmail() throws Exception {
|
||||
try {
|
||||
PhoneNumberFormatter.formatNumber("person@domain.com", LOCAL_NUMBER_US);
|
||||
throw new AssertionFailedError("should have thrown on email");
|
||||
} catch (InvalidNumberException ine) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatNumberE164() throws Exception, InvalidNumberException {
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_UK, "(020) 7946 0018")).isEqualTo(NUMBER_UK);
|
||||
// assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_UK, "044 20 7946 0018")).isEqualTo(NUMBER_UK);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_UK, "+442079460018")).isEqualTo(NUMBER_UK);
|
||||
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_CH, "+41 44 668 18 00")).isEqualTo(NUMBER_CH);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_CH, "+41 (044) 6681800")).isEqualTo(NUMBER_CH);
|
||||
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0049 030 123456")).isEqualTo(NUMBER_DE);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0049 (0)30123456")).isEqualTo(NUMBER_DE);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0049((0)30)123456")).isEqualTo(NUMBER_DE);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "+49 (0) 30 1 2 3 45 6 ")).isEqualTo(NUMBER_DE);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "030 123456")).isEqualTo(NUMBER_DE);
|
||||
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0171123456")).isEqualTo(NUMBER_MOBILE_DE);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0171/123456")).isEqualTo(NUMBER_MOBILE_DE);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "+490171/123456")).isEqualTo(NUMBER_MOBILE_DE);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "00490171/123456")).isEqualTo(NUMBER_MOBILE_DE);
|
||||
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0049171/123456")).isEqualTo(NUMBER_MOBILE_DE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatRemoteNumberE164() throws Exception, InvalidNumberException {
|
||||
assertThat(PhoneNumberFormatter.formatNumber("+4402079460018", LOCAL_NUMBER_US)).isEqualTo(NUMBER_UK);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,105 +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.util;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.thoughtcrime.securesms.BaseUnitTest;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class Rfc5724UriTest extends BaseUnitTest {
|
||||
|
||||
@Test public void testInvalidPath() throws Exception {
|
||||
final String[] invalidSchemaUris = {
|
||||
"",
|
||||
":",
|
||||
"sms:",
|
||||
":sms",
|
||||
"sms:?goto=fail",
|
||||
"sms:?goto=fail&fail=goto"
|
||||
};
|
||||
|
||||
for (String uri : invalidSchemaUris) {
|
||||
try {
|
||||
new Rfc5724Uri(uri);
|
||||
throw new AssertionFailedError("URISyntaxException should be thrown");
|
||||
} catch (URISyntaxException e) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void testGetSchema() throws Exception {
|
||||
final String[][] uriTestPairs = {
|
||||
{"sms:+15555555555", "sms"},
|
||||
{"sMs:+15555555555", "sMs"},
|
||||
{"smsto:+15555555555?", "smsto"},
|
||||
{"mms:+15555555555?a=b", "mms"},
|
||||
{"mmsto:+15555555555?a=b&c=d", "mmsto"}
|
||||
};
|
||||
|
||||
for (String[] uriTestPair : uriTestPairs) {
|
||||
final Rfc5724Uri testUri = new Rfc5724Uri(uriTestPair[0]);
|
||||
assertTrue(testUri.getSchema().equals(uriTestPair[1]));
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void testGetPath() throws Exception {
|
||||
final String[][] uriTestPairs = {
|
||||
{"sms:+15555555555", "+15555555555"},
|
||||
{"sms:%2B555555555", "%2B555555555"},
|
||||
{"smsto:+15555555555?", "+15555555555"},
|
||||
{"mms:+15555555555?a=b", "+15555555555"},
|
||||
{"mmsto:+15555555555?a=b&c=d", "+15555555555"},
|
||||
{"sms:+15555555555,+14444444444", "+15555555555,+14444444444"},
|
||||
{"sms:+15555555555,+14444444444?", "+15555555555,+14444444444"},
|
||||
{"sms:+15555555555,+14444444444?a=b", "+15555555555,+14444444444"},
|
||||
{"sms:+15555555555,+14444444444?a=b&c=d", "+15555555555,+14444444444"}
|
||||
};
|
||||
|
||||
for (String[] uriTestPair : uriTestPairs) {
|
||||
final Rfc5724Uri testUri = new Rfc5724Uri(uriTestPair[0]);
|
||||
assertTrue(testUri.getPath().equals(uriTestPair[1]));
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void testGetQueryParams() throws Exception {
|
||||
final String[][] uriTestPairs = {
|
||||
{"sms:+15555555555", "a", null},
|
||||
{"mms:+15555555555?b=", "a", null},
|
||||
{"mmsto:+15555555555?a=", "a", ""},
|
||||
{"sms:+15555555555?a=b", "a", "b"},
|
||||
{"sms:+15555555555?a=b&c=d", "a", "b"},
|
||||
{"sms:+15555555555?a=b&c=d", "b", null},
|
||||
{"sms:+15555555555?a=b&c=d", "c", "d"},
|
||||
{"sms:+15555555555?a=b&c=d", "d", null}
|
||||
};
|
||||
|
||||
for (String[] uriTestPair : uriTestPairs) {
|
||||
final Rfc5724Uri testUri = new Rfc5724Uri(uriTestPair[0]);
|
||||
final String paramResult = testUri.getQueryParams().get(uriTestPair[1]);
|
||||
|
||||
if (paramResult == null) assertTrue(uriTestPair[2] == null);
|
||||
else assertTrue(paramResult.equals(uriTestPair[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
public class ShortCodeUtilTest {
|
||||
|
||||
@Test
|
||||
public void testShortCodes() throws Exception {
|
||||
assertTrue(ShortCodeUtil.isShortCode("+14152222222", "40404"));
|
||||
assertTrue(ShortCodeUtil.isShortCode("+14152222222", "431"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+14152222222", "4157778888"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+14152222222", "+14157778888"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+14152222222", "415-777-8888"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+14152222222", "(415) 777-8888"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+14152222222", "8882222"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+14152222222", "888-2222"));
|
||||
|
||||
assertTrue(ShortCodeUtil.isShortCode("+491723742522", "670"));
|
||||
assertTrue(ShortCodeUtil.isShortCode("+491723742522", "115"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+491723742522", "089-12345678"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+491723742522", "089/12345678"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+491723742522", "12345678"));
|
||||
|
||||
assertTrue(ShortCodeUtil.isShortCode("+298123456", "4040"));
|
||||
assertTrue(ShortCodeUtil.isShortCode("+298123456", "6701"));
|
||||
assertTrue(ShortCodeUtil.isShortCode("+298123456", "433"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+298123456", "123456"));
|
||||
|
||||
assertTrue(ShortCodeUtil.isShortCode("+61414915066", "19808948"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+61414915066", "119808948"));
|
||||
|
||||
assertTrue(ShortCodeUtil.isShortCode("+79166503388", "8080"));
|
||||
assertTrue(ShortCodeUtil.isShortCode("+79166503388", "6701"));
|
||||
assertTrue(ShortCodeUtil.isShortCode("+79166503388", "431"));
|
||||
assertFalse(ShortCodeUtil.isShortCode("+79166503388", "111-22-33"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user