Add sms export library and sample app.

fork-5.53.8
Alex Hart 2022-08-08 09:22:48 -03:00 zatwierdzone przez Cody Henthorne
rodzic 6120f90dcb
commit 5212b33b47
78 zmienionych plików z 1923 dodań i 59 usunięć

Wyświetl plik

@ -19,8 +19,8 @@ import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.Conversions;
import org.signal.core.util.CursorUtil;
import org.signal.core.util.SetUtil;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.kdf.HKDF;
import org.signal.libsignal.protocol.kdf.HKDFv3;
import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.attachments.AttachmentId;
@ -50,7 +50,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@ -79,22 +78,22 @@ public class FullBackupExporter extends FullBackupBase {
private static final String TAG = Log.tag(FullBackupExporter.class);
private static final long DATABASE_VERSION_RECORD_COUNT = 1L;
private static final long TABLE_RECORD_COUNT_MULTIPLIER = 3L;
private static final long DATABASE_VERSION_RECORD_COUNT = 1L;
private static final long TABLE_RECORD_COUNT_MULTIPLIER = 3L;
private static final long IDENTITY_KEY_BACKUP_RECORD_COUNT = 2L;
private static final long FINAL_MESSAGE_COUNT = 1L;
private static final long FINAL_MESSAGE_COUNT = 1L;
private static final Set<String> BLACKLISTED_TABLES = SetUtil.newHashSet(
SignedPreKeyDatabase.TABLE_NAME,
OneTimePreKeyDatabase.TABLE_NAME,
SessionDatabase.TABLE_NAME,
SearchDatabase.SMS_FTS_TABLE_NAME,
SearchDatabase.MMS_FTS_TABLE_NAME,
EmojiSearchDatabase.TABLE_NAME,
SenderKeyDatabase.TABLE_NAME,
SenderKeySharedDatabase.TABLE_NAME,
PendingRetryReceiptDatabase.TABLE_NAME,
AvatarPickerDatabase.TABLE_NAME
SignedPreKeyDatabase.TABLE_NAME,
OneTimePreKeyDatabase.TABLE_NAME,
SessionDatabase.TABLE_NAME,
SearchDatabase.SMS_FTS_TABLE_NAME,
SearchDatabase.MMS_FTS_TABLE_NAME,
EmojiSearchDatabase.TABLE_NAME,
SenderKeyDatabase.TABLE_NAME,
SenderKeySharedDatabase.TABLE_NAME,
PendingRetryReceiptDatabase.TABLE_NAME,
AvatarPickerDatabase.TABLE_NAME
);
public static void export(@NonNull Context context,
@ -315,7 +314,7 @@ public class FullBackupExporter extends FullBackupBase {
statement.append('(');
for (int i=0;i<cursor.getColumnCount();i++) {
for (int i = 0; i < cursor.getColumnCount(); i++) {
statement.append('?');
if (cursor.getType(i) == Cursor.FIELD_TYPE_STRING) {
@ -329,10 +328,10 @@ public class FullBackupExporter extends FullBackupBase {
} 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));
throw new AssertionError("unknown type?" + cursor.getType(i));
}
if (i < cursor.getColumnCount()-1) {
if (i < cursor.getColumnCount() - 1) {
statement.append(',');
}
}
@ -375,7 +374,7 @@ public class FullBackupExporter extends FullBackupBase {
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));
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
@ -390,8 +389,8 @@ public class FullBackupExporter extends FullBackupBase {
private static int exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count, long estimatedCount) {
try {
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID));
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH));
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID));
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH));
String data = cursor.getString(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_PATH));
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM));
@ -410,13 +409,13 @@ public class FullBackupExporter extends FullBackupBase {
}
private static long calculateVeryOldStreamLength(@NonNull AttachmentSecret attachmentSecret, @Nullable byte[] random, @NonNull String data) throws IOException {
long result = 0;
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));
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
int read;
int read;
byte[] buffer = new byte[8192];
while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) {
@ -479,7 +478,7 @@ public class FullBackupExporter extends FullBackupBase {
private static boolean isNonExpiringMmsMessage(@NonNull Cursor cursor) {
return cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 &&
cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) <= 0;
cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) <= 0;
}
private static boolean isNonExpiringSmsMessage(@NonNull Cursor cursor) {
@ -509,7 +508,7 @@ public class FullBackupExporter extends FullBackupBase {
}
private static boolean isForNonExpiringMmsMessage(@NonNull SQLiteDatabase db, long mmsId) {
String[] columns = new String[] { MmsDatabase.RECIPIENT_ID, MmsDatabase.EXPIRES_IN, MmsDatabase.VIEW_ONCE};
String[] columns = new String[] { MmsDatabase.RECIPIENT_ID, MmsDatabase.EXPIRES_IN, MmsDatabase.VIEW_ONCE };
String where = MmsDatabase.ID + " = ?";
String[] args = new String[] { String.valueOf(mmsId) };
@ -528,8 +527,8 @@ public class FullBackupExporter extends FullBackupBase {
private final Cipher cipher;
private final Mac mac;
private final byte[] cipherKey;
private final byte[] macKey;
private final byte[] cipherKey;
private final byte[] macKey;
private byte[] iv;
private int counter;
@ -695,5 +694,6 @@ public class FullBackupExporter extends FullBackupBase {
boolean isCanceled();
}
public static final class BackupCanceledException extends IOException { }
public static final class BackupCanceledException extends IOException {
}
}

Wyświetl plik

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.contacts.paged
import androidx.annotation.VisibleForTesting
import io.reactivex.rxjava3.core.Single
import org.signal.core.util.Stopwatch
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.concurrent.safeBlockingGet
import org.signal.core.util.logging.Log
@ -10,7 +11,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.IdentityUtil
import org.thoughtcrime.securesms.util.Stopwatch
import org.whispersystems.signalservice.api.services.ProfileService
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.IdentityCheckResponse

Wyświetl plik

@ -11,6 +11,7 @@ import org.signal.contacts.ContactLinkConfiguration
import org.signal.contacts.SystemContactsRepository
import org.signal.contacts.SystemContactsRepository.ContactIterator
import org.signal.contacts.SystemContactsRepository.ContactPhoneDetails
import org.signal.core.util.Stopwatch
import org.signal.core.util.StringUtil
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BuildConfig
@ -30,7 +31,6 @@ import org.thoughtcrime.securesms.registration.RegistrationUtil
import org.thoughtcrime.securesms.sms.IncomingJoinedMessage
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.Stopwatch
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.push.SignalServiceAddress

Wyświetl plik

@ -25,10 +25,9 @@ import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.push.IasTrustStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.signal.core.util.SetUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;

Wyświetl plik

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.contacts.sync
import android.content.Context
import androidx.annotation.WorkerThread
import org.signal.contacts.SystemContactsRepository
import org.signal.core.util.Stopwatch
import org.signal.core.util.logging.Log
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.BuildConfig
@ -13,7 +14,6 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.Stopwatch
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.services.CdsiV2Service
import java.io.IOException

Wyświetl plik

@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.push.ServiceId;

Wyświetl plik

@ -106,7 +106,6 @@ import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectFor
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs;
import org.thoughtcrime.securesms.conversation.quotes.MessageQuotesBottomSheet;
import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog;
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
@ -179,7 +178,7 @@ import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SignalProxyUtil;
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.TopToastPopup;

Wyświetl plik

@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import java.util.ArrayList;
import java.util.HashSet;

Wyświetl plik

@ -153,7 +153,7 @@ import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SignalProxyUtil;
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;

Wyświetl plik

@ -8,6 +8,7 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase
import net.zetetic.database.sqlcipher.SQLiteOpenHelper
import org.signal.core.util.CursorUtil
import org.signal.core.util.SqlUtil
import org.signal.core.util.Stopwatch
import org.signal.core.util.delete
import org.signal.core.util.getTableRowCount
import org.signal.core.util.logging.Log
@ -15,7 +16,6 @@ import org.thoughtcrime.securesms.crypto.DatabaseSecret
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider
import org.thoughtcrime.securesms.database.model.LogEntry
import org.thoughtcrime.securesms.util.ByteUnit
import org.thoughtcrime.securesms.util.Stopwatch
import java.io.Closeable
import java.util.concurrent.TimeUnit
import kotlin.math.abs

Wyświetl plik

@ -17,6 +17,7 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.signal.core.util.CursorUtil
import org.signal.core.util.Hex
import org.signal.core.util.SqlUtil
import org.signal.core.util.Stopwatch
import org.signal.core.util.logging.Log
import org.signal.core.util.requireString
import org.thoughtcrime.securesms.color.MaterialColor
@ -40,7 +41,6 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FileUtils
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.Stopwatch
import org.thoughtcrime.securesms.util.Triple
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.push.ACI

Wyświetl plik

@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.groups.ui.creategroup.details.AddGroupDetailsA
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;

Wyświetl plik

@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.payments.TransactionSubmissionResult;
import org.thoughtcrime.securesms.payments.Wallet;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.whispersystems.signalservice.api.payments.Money;
import java.util.Objects;

Wyświetl plik

@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;

Wyświetl plik

@ -33,9 +33,8 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper.WriteOperationResult
import org.thoughtcrime.securesms.storage.StorageSyncModels;
import org.thoughtcrime.securesms.storage.StorageSyncValidations;
import org.thoughtcrime.securesms.storage.StoryDistributionListRecordProcessor;
import org.thoughtcrime.securesms.stories.Stories;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;

Wyświetl plik

@ -25,7 +25,7 @@ import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import java.io.IOException;
import java.io.OutputStream;

Wyświetl plik

@ -46,7 +46,7 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.stories.Stories;
import org.thoughtcrime.securesms.stories.viewer.page.StoryDisplay;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.ByteArrayOutputStream;

Wyświetl plik

@ -53,7 +53,7 @@ import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.stories.Stories;
import org.thoughtcrime.securesms.stories.viewer.page.StoryDisplay;
import org.thoughtcrime.securesms.util.MemoryFileDescriptor;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.video.VideoUtil;

Wyświetl plik

@ -25,7 +25,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.signal.core.util.SqlUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.Util;

Wyświetl plik

@ -29,7 +29,7 @@ import androidx.camera.core.ImageProxy;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

Wyświetl plik

@ -11,13 +11,13 @@ import androidx.lifecycle.Transformations
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.signal.core.util.Stopwatch
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaRepository
import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.Stopwatch
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.fragments.requireListener

Wyświetl plik

@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.BadGroupIdException;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Job;
@ -26,7 +25,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.signal.core.util.SetUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;

Wyświetl plik

@ -7,7 +7,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;

Wyświetl plik

@ -13,7 +13,7 @@ import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
public final class QrCode {

Wyświetl plik

@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.signal.core.util.CursorUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.concurrent.FilteredExecutor;
import org.whispersystems.signalservice.api.push.ACI;

Wyświetl plik

@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewMod
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.SupportEmailUtil;
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;

Wyświetl plik

@ -7,6 +7,7 @@ import android.os.Looper;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;

Wyświetl plik

@ -0,0 +1,58 @@
package org.signal.core.util
/**
* A Result that allows for generic definitions of success/failure values.
*/
sealed class Result<out S, out F> {
data class Failure<out F>(val failure: F) : Result<Nothing, F>()
data class Success<out S>(val success: S) : Result<S, Nothing>()
companion object {
fun <S> success(value: S) = Success(value)
fun <F> failure(value: F) = Failure(value)
}
/**
* Maps an Result<S, F> to an Result<T, F>. Failure values will pass through, while
* right values will be operated on by the parameter.
*/
fun <T> map(onSuccess: (S) -> T): Result<T, F> {
return when (this) {
is Failure -> this
is Success -> success(onSuccess(success))
}
}
/**
* Allows the caller to operate on the Result such that the correct function is applied
* to the value it contains.
*/
fun <T> either(
onSuccess: (S) -> T,
onFailure: (F) -> T
): T {
return when (this) {
is Success -> onSuccess(success)
is Failure -> onFailure(failure)
}
}
}
/**
* Maps an Result<L, R> to an Result<L, T>. Failure values will pass through, while
* right values will be operated on by the parameter.
*
* Note this is an extension method in order to make the generics happy.
*/
fun <T, S, F> Result<S, F>.flatMap(onSuccess: (S) -> Result<T, F>): Result<T, F> {
return when (this) {
is Result.Success -> onSuccess(success)
is Result.Failure -> this
}
}
/**
* Try is a specialization of Result where the Failure is fixed to Throwable.
*/
typealias Try<S> = Result<S, Throwable>

Wyświetl plik

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.util;
package org.signal.core.util;
import androidx.annotation.NonNull;

Wyświetl plik

@ -48,6 +48,7 @@ dependencyResolutionManagement {
alias('androidx-biometric').to('androidx.biometric:biometric:1.1.0')
alias('androidx-sharetarget').to('androidx.sharetarget:sharetarget:1.1.0')
alias('androidx-sqlite').to('androidx.sqlite:sqlite:2.1.0')
alias('androidx-core-role').to('androidx.core:core-role:1.0.0')
// Material
alias('material-material').to('com.google.android.material:material:1.5.0')

Wyświetl plik

@ -336,6 +336,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="b037fdfb267dc0141ab9f4e4e85daf87b175cf311248d54b501b58ec42345315" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-role" version="1.0.0">
<artifact name="core-role-1.0.0.aar">
<sha256 value="796378da90f5e74a7664ae650d62b904ee96444f87006a7972f97285b223370d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.cursoradapter" name="cursoradapter" version="1.0.0">
<artifact name="cursoradapter-1.0.0.aar">
<sha256 value="a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564" origin="Generated by Gradle"/>
@ -2850,6 +2855,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="25434813eef31de5f7b2d11bb744b8434be3750e3010fc9803e54671076f071d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest" name="ktlint" version="0.42.1">
<artifact name="ktlint-0.42.1.jar">
<sha256 value="aafdc2c1e66746a3c383cd6fb94343f0b7a856c2cfbfd40ff4464c726618a9a7" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-0.42.1.module">
<sha256 value="f06ba76eb422ad7b7da5ccf048d06d54dc5261ef953393a9043abd4f958c6e29" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest" name="ktlint" version="0.43.2">
<artifact name="ktlint-0.43.2.jar">
<sha256 value="99ec69ef0628695c24dbbc2cc4b8d7c61a754697d624f5233fc65f43faf2d235" origin="Generated by Gradle"/>
@ -2858,6 +2871,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="8bbdf6bc56cb12aa8ddea097e9ae862cde9a7c11bc32332dedda73241fb220dc" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-core" version="0.42.1">
<artifact name="ktlint-core-0.42.1.jar">
<sha256 value="a7bd968f4f408521e44a781594a2237df0199aab1ad2942c52bf8ad21e15dea4" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-core-0.42.1.module">
<sha256 value="6b1efb95887d9172d109df25afc2ef89fa0f09e4b230a47f56c57ad53bfb17ba" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-core" version="0.43.2">
<artifact name="ktlint-core-0.43.2.jar">
<sha256 value="401515a76b780a32ef9dfeaf69f77316934c4bb90f339488638311789eca7a1a" origin="Generated by Gradle"/>
@ -2866,6 +2887,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="aa276dfa9dcfab2f0459c81e7f903712058230d0908d545cc4bc8674273a51d7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-baseline" version="0.42.1">
<artifact name="ktlint-reporter-baseline-0.42.1.jar">
<sha256 value="6a6de6072e3a8b7b96ef9b8486985889977500761ff37f0467689af9fcbc2843" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-reporter-baseline-0.42.1.module">
<sha256 value="7476d04c105bfec627889c9f2807f524d26ab316dd57d42f7748db7ffbe8ad4f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-baseline" version="0.43.2">
<artifact name="ktlint-reporter-baseline-0.43.2.jar">
<sha256 value="733ee7e2cadb321d6597b3501c70c7da73117adaa0c6bc084dfc16c455d68806" origin="Generated by Gradle"/>
@ -2874,6 +2903,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="3b6466c5813d2deb31a534ae694c41c36b93aec787eb2a8aff162a1288c63533" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-checkstyle" version="0.42.1">
<artifact name="ktlint-reporter-checkstyle-0.42.1.jar">
<sha256 value="dad0e9626f6cbfec9df70eb8100ba5ea62d421e5c179b9b0e1f69586b0ba1fa6" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-reporter-checkstyle-0.42.1.module">
<sha256 value="017768838d4276018aaebe07a271f0022b5f3e66952bc2f0ceae202da4cb66be" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-checkstyle" version="0.43.2">
<artifact name="ktlint-reporter-checkstyle-0.43.2.jar">
<sha256 value="becafb4006b9f2e82c99749864a1a8de340ee84ac7271631a68981a44f51e808" origin="Generated by Gradle"/>
@ -2882,6 +2919,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="3937057372b1cab189647a1e2fa25aa19cb5f72168ca663421b9e250b4e77d05" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-html" version="0.42.1">
<artifact name="ktlint-reporter-html-0.42.1.jar">
<sha256 value="ca2c35bf0f436434a6fd8a95a8e47321b62d02cb242a4989c17a5d5b27ecea74" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-reporter-html-0.42.1.module">
<sha256 value="61fdc1ded68e730b76f269c94d1024484d565df629bfcd5eb45fd4ce05353def" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-html" version="0.43.2">
<artifact name="ktlint-reporter-html-0.43.2.jar">
<sha256 value="800392e150d3266e72ca53c6ccca3136d4e26445dd9216c6ac6cfc1ba3afafe5" origin="Generated by Gradle"/>
@ -2890,6 +2935,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="432a6fbb008f1373d3e8bde4ab9d905620ff87fd9f3b50a5654b7717f0a3eaab" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-json" version="0.42.1">
<artifact name="ktlint-reporter-json-0.42.1.jar">
<sha256 value="d173003331b292dec16bcd5f898546cfcaf4c61c2214136808e21f222a1afd1c" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-reporter-json-0.42.1.module">
<sha256 value="3cd549d0c0bf07182cfe69bf6f1a7643473ec1669d1fca12194b2586f25525ed" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-json" version="0.43.2">
<artifact name="ktlint-reporter-json-0.43.2.jar">
<sha256 value="9d4a94190d96d671000a06a50c9d1ce111d0dcf629bef8b4f0221a9e3f3699a0" origin="Generated by Gradle"/>
@ -2898,6 +2951,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="7e7be45882eb7abc67a62d12980018f2bb067d88d9947395a84ad678099b5179" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-plain" version="0.42.1">
<artifact name="ktlint-reporter-plain-0.42.1.jar">
<sha256 value="df673cd3e88e330e45dc37d58c2789b37b3ed8c3d2edcc4bd52cf719f2a7ee4c" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-reporter-plain-0.42.1.module">
<sha256 value="2afb405369eee884f7dcc1e17a2c5f37b1836d7de7ac506196c5b325584febe0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-plain" version="0.43.2">
<artifact name="ktlint-reporter-plain-0.43.2.jar">
<sha256 value="1cab63f431ec4e9463df7a767f131ccfa8d76259c01fecc63a4c000063e8ee43" origin="Generated by Gradle"/>
@ -2906,6 +2967,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="ea97899a3d8b6f8e18c7ae1a5d2f7147f976844f1bd2a51c27b7d8285d90a5ec" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-sarif" version="0.42.1">
<artifact name="ktlint-reporter-sarif-0.42.1.jar">
<sha256 value="13723186b353287cbdfd60ede056f25dbfb21a7a398be782ab64c9b4ef0ab593" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-reporter-sarif-0.42.1.module">
<sha256 value="d480e84b60a747582cfe4e4b1608806511bc4cebe7c5c394920e842160c5cf7a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-reporter-sarif" version="0.43.2">
<artifact name="ktlint-reporter-sarif-0.43.2.jar">
<sha256 value="ed0046aaa4a2e4544197bfdccf88d472ef413a55ad05b6dc8aae41338e9d3748" origin="Generated by Gradle"/>
@ -2914,6 +2983,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="7ff665bb3f0f36af38b80087c9a0067a9dff3c89b6a2c1c78a1f6e1455eb1d09" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-ruleset-experimental" version="0.42.1">
<artifact name="ktlint-ruleset-experimental-0.42.1.jar">
<sha256 value="9cdc257cba3d0568c553da9ebc90d0d8eda0743f150e2f0f9d3c60626165840d" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-ruleset-experimental-0.42.1.module">
<sha256 value="a3f839fb54c9443f60bde4518c69c65b3f5fa807deb4104f472c7ee22d6e2ae5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-ruleset-experimental" version="0.43.2">
<artifact name="ktlint-ruleset-experimental-0.43.2.jar">
<sha256 value="d89e0edcdca0ae375c090565e323520ab5d424d82fd6ac6290ea986d360f0b11" origin="Generated by Gradle"/>
@ -2922,6 +2999,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="2d85cd883fe88c4b5429f266de027afca9f9c53a4f49bf14822a4fdf4abeb67a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-ruleset-standard" version="0.42.1">
<artifact name="ktlint-ruleset-standard-0.42.1.jar">
<sha256 value="cd3a1f034a554a2e1877aead61a252f1eadc9adfed345edec0ce863dcff4e61c" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-ruleset-standard-0.42.1.module">
<sha256 value="cfb11e428ae3564249b96ebc08e5170596b3b3790250a9133782681f6b56a036" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-ruleset-standard" version="0.43.2">
<artifact name="ktlint-ruleset-standard-0.43.2.jar">
<sha256 value="6774dc9d42aa7c7fdd4a7f3732b56fdab99ba78ce0c4eb5159036525657d0014" origin="Generated by Gradle"/>
@ -2930,6 +3015,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="7ce4e3721b8a6a2e0dd9607e8e5e5b337f5be4f9ed3f6a5dde9ff6d189355303" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-ruleset-test" version="0.42.1">
<artifact name="ktlint-ruleset-test-0.42.1.jar">
<sha256 value="0e9001347428a5be6b6b3a8bb322204259805e04b0d4bb6ed427d8a451db5097" origin="Generated by Gradle"/>
</artifact>
<artifact name="ktlint-ruleset-test-0.42.1.module">
<sha256 value="c0c9319daa040e6e3c0f4b8503138f9764dfdfc81672f4d2f7f9824cc4d7db39" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.pinterest.ktlint" name="ktlint-ruleset-test" version="0.43.2">
<artifact name="ktlint-ruleset-test-0.43.2.jar">
<sha256 value="7270c4d98b2cda268c25397a02b7dea0ab8cb923958cb3853121e0d9366ce797" origin="Generated by Gradle"/>
@ -3095,6 +3188,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="c0e6e3d3bc67c819f3ea8fd3c755006d38e675433eaaaf2dab3b11d2fb4f2428" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp" name="okhttp" version="2.5.0">
<artifact name="okhttp-2.5.0.jar">
<sha256 value="1cc716e29539adcda677949508162796daffedb4794cbf947a6f65e696f0381c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp" name="okhttp" version="2.7.4">
<artifact name="okhttp-2.7.4.jar">
<sha256 value="c88be9af1509d5aeec9394a818c0fa08e26fad9d64ba134e6f977e0bb20cb114" origin="Generated by Gradle"/>
@ -3103,6 +3201,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="1595d91482c30d2bdaacb2bf3a5d62659f1ecd3c22096124cc8a2c2356a9901e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp" name="okhttp-urlconnection" version="2.5.0">
<artifact name="okhttp-urlconnection-2.5.0.jar">
<sha256 value="79ec6f4e79e683105e87fe83278a531c693e538d30e3b9237000ce7c94fcb2cf" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp" name="parent" version="2.7.4">
<artifact name="parent-2.7.4.pom">
<sha256 value="b20bd74ba01b55b30d6b7d10b9373f2a324b1f3638ecda0275c93e454223c7c8" origin="Generated by Gradle"/>
@ -3134,6 +3237,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="f1c10b1480d1ab75fb051a07b273e37cda2525e97e1607ee83e6833b70e1bfce" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okio" name="okio" version="1.6.0">
<artifact name="okio-1.6.0.jar">
<sha256 value="114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okio" name="okio" version="2.2.2">
<artifact name="okio-2.2.2.jar">
<sha256 value="e58c97406a6bb1138893750299ac63c6aa04b38b6b49eae1bfcad1a63ef9ba1b" origin="Generated by Gradle"/>
@ -4602,6 +4710,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="2d10df04835f07b87aa81a0e7fec406d2c1ac11c54e7e08f08eb511afe5bb48f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-compiler-embeddable" version="1.5.20">
<artifact name="kotlin-compiler-embeddable-1.5.20.jar">
<sha256 value="11d51087eb70b5abbad6fbf459a4349a0335916588000b5ecd990f01482e38ff" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-compiler-embeddable" version="1.5.31">
<artifact name="kotlin-compiler-embeddable-1.5.31.jar">
<sha256 value="e39811a9e4c102e779c659eefe90b041c66ce87578c1bfdac07cf504d1551745" origin="Generated by Gradle"/>
@ -4634,6 +4747,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="43c12720a4f276b35dc04396128e9b7fb1a8abd07a16cb409c6c0d6706a2316b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-daemon-embeddable" version="1.5.20">
<artifact name="kotlin-daemon-embeddable-1.5.20.jar">
<sha256 value="5a2e1e6869d130d937b39c668ea6bca758ef8960d168847f6e13aa2a2add424a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-daemon-embeddable" version="1.5.31">
<artifact name="kotlin-daemon-embeddable-1.5.31.jar">
<sha256 value="f61eaf89e5e3848631650b25cdfb66fe8cae0281a054d9d986716000a15ba8d6" origin="Generated by Gradle"/>
@ -4738,6 +4856,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="909d0b8a326568c4db341f21b5f0e221c75c002896a4ea3b170aa5a1569a0e54" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-reflect" version="1.5.20">
<artifact name="kotlin-reflect-1.5.20.jar">
<sha256 value="fd6782d18bcc17ffa98221a1c34e4a42a7e3e6b4a4b72b474b5c82e14c8bab5a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-reflect" version="1.5.31">
<artifact name="kotlin-reflect-1.5.31.jar">
<sha256 value="6e0f5490e6b9649ddd2670534e4d3a03bd283c3358b8eef5d1304fd5f8a5a4fb" origin="Generated by Gradle"/>
@ -4754,6 +4877,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="da71e1e3ddc2388d675641672e2f0316db9c9d212f946f026d4a0e6a1336a7f3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-script-runtime" version="1.5.20">
<artifact name="kotlin-script-runtime-1.5.20.jar">
<sha256 value="e8a44d7195dc7ee4abb5cda5791e37aacd20b1b76378b13da109dd626536380f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-script-runtime" version="1.5.31">
<artifact name="kotlin-script-runtime-1.5.31.jar">
<sha256 value="24e450fee7645ed3590981dddccf397c0d9ebb725815c94c4f555cc3db2f9f96" origin="Generated by Gradle"/>
@ -4859,6 +4987,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="305ad32461e1929a1cc32ba89861d9c41936a11189e6f486016eaf8b31c5bbcb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.5.20">
<artifact name="kotlin-stdlib-1.5.20.jar">
<sha256 value="80cd79c26aac46d72d782de1ecb326061e93c6e688d994b48627ffd668ba63a8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.5.31">
<artifact name="kotlin-stdlib-1.5.31.jar">
<sha256 value="4800ceacb2ec0bb9959a087154b8e35318ead1ea4eba32d4bb1b9734222a7e68" origin="Generated by Gradle"/>
@ -4920,6 +5053,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="2ef683038382532fc14da37c50e1c4609bb76a7e6ff2e330f062c164217f4483" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-common" version="1.5.20">
<artifact name="kotlin-stdlib-common-1.5.20.jar">
<sha256 value="9819529804bf9296e3853acd5ae824df95d8f8c61309e7768b7cae5ca1361d36" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-common" version="1.5.31">
<artifact name="kotlin-stdlib-common-1.5.31.jar">
<sha256 value="dfa2a18e26b028388ee1968d199bf6f166f737ab7049c25a5e2da614404e22ad" origin="Generated by Gradle"/>
@ -4960,6 +5098,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="064c379ad8b7e787ae8863c414bfc732814070c6841b525ba3627c1c333ceabb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-jdk7" version="1.5.20">
<artifact name="kotlin-stdlib-jdk7-1.5.20.jar">
<sha256 value="b110f6d20204303099af0d5f2c846ac60bc6ae5663ef5f22e726ca4627359d06" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-jdk7" version="1.5.31">
<artifact name="kotlin-stdlib-jdk7-1.5.31.jar">
<sha256 value="a25bf47353ce899d843cbddee516d621a73473e7fba97f8d0301e7b4aed7c15f" origin="Generated by Gradle"/>
@ -5000,6 +5143,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="e552e23edc0e7bc29f341645fb9327c82527e37dfe4bde13ba4a3af36de23fdb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-jdk8" version="1.5.20">
<artifact name="kotlin-stdlib-jdk8-1.5.20.jar">
<sha256 value="a7e9cffe569c43eb8f0fe3139978b0943fe92abcc513f7cf04544f2797f8d38a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-jdk8" version="1.5.31">
<artifact name="kotlin-stdlib-jdk8-1.5.31.jar">
<sha256 value="b548f7767aacf029d2417e47440742bd6d3ebede19b60386e23554ce5c4c5fdc" origin="Generated by Gradle"/>

Wyświetl plik

@ -12,6 +12,8 @@ include ':device-transfer'
include ':device-transfer-app'
include ':image-editor'
include ':image-editor-app'
include ':sms-exporter'
include ':sms-exporter-app'
include ':donations'
include ':donations-app'
include ':spinner'
@ -33,6 +35,9 @@ project(':libsignal-service').projectDir = file('libsignal/service')
project(':image-editor').projectDir = file('image-editor/lib')
project(':image-editor-app').projectDir = file('image-editor/app')
project(':sms-exporter').projectDir = file('sms-exporter/lib')
project(':sms-exporter-app').projectDir = file('sms-exporter/app')
project(':donations').projectDir = file('donations/lib')
project(':donations-app').projectDir = file('donations/app')

Wyświetl plik

@ -0,0 +1,19 @@
apply from: "$rootProject.projectDir/signalModuleApp.gradle"
android {
defaultConfig {
applicationId "org.signal.smsexporter.app"
}
}
dependencies {
implementation libs.androidx.core.ktx
implementation libs.androidx.appcompat
implementation libs.androidx.core.role
implementation libs.material.material
implementation libs.rxjava3.rxjava
implementation libs.rxjava3.rxandroid
implementation libs.rxjava3.rxkotlin
implementation project(':core-util')
implementation project(':sms-exporter')
}

Wyświetl plik

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Wyświetl plik

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.signal.smsexporter.app">
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Signal">
<service android:name=".TestSmsExportService" android:foregroundServiceType="dataSync" />
<service
android:name=".SendResponseViaMessageService"
android:exported="true"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="smsto" />
</intent-filter>
</service>
<receiver
android:name=".BroadcastSmsReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver>
<receiver
android:name=".BroadcastWapPushReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<activity
android:name="org.signal.smsexporter.app.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="smsto" />
</intent-filter>
</activity>
</application>
</manifest>

Wyświetl plik

@ -0,0 +1,35 @@
package org.signal.smsexporter.app
import android.graphics.Bitmap
import android.graphics.Color
import androidx.core.graphics.applyCanvas
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.util.Random
object BitmapGenerator {
private val colors = listOf(
Color.BLACK,
Color.BLUE,
Color.GRAY,
Color.GREEN,
Color.RED,
Color.CYAN
)
fun getStream(): InputStream {
val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
bitmap.applyCanvas {
val random = Random()
drawColor(colors[random.nextInt(colors.size - 1)])
}
val out = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)
val data = out.toByteArray()
return ByteArrayInputStream(data)
}
}

Wyświetl plik

@ -0,0 +1,10 @@
package org.signal.smsexporter.app
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class BroadcastSmsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
}
}

Wyświetl plik

@ -0,0 +1,10 @@
package org.signal.smsexporter.app
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class BroadcastWapPushReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
}
}

Wyświetl plik

@ -0,0 +1,140 @@
package org.signal.smsexporter.app
import android.content.Intent
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.google.android.material.button.MaterialButton
import com.google.android.material.progressindicator.LinearProgressIndicator
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.smsexporter.DefaultSmsHelper
import org.signal.smsexporter.ReleaseSmsAppFailure
import org.signal.smsexporter.SmsExportProgress
import org.signal.smsexporter.SmsExportService
class MainActivity : AppCompatActivity(R.layout.main_activity) {
private lateinit var exportSmsButton: MaterialButton
private lateinit var setAsDefaultSmsButton: MaterialButton
private lateinit var clearDefaultSmsButton: MaterialButton
private lateinit var exportStatus: TextView
private lateinit var exportProgress: LinearProgressIndicator
private val disposables = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exportSmsButton = findViewById(R.id.export_sms)
setAsDefaultSmsButton = findViewById(R.id.set_as_default_sms)
clearDefaultSmsButton = findViewById(R.id.clear_default_sms)
exportStatus = findViewById(R.id.export_status)
exportProgress = findViewById(R.id.export_progress)
disposables += SmsExportService.progressState.onBackpressureLatest().subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread()).subscribe {
when (it) {
SmsExportProgress.Done -> {
exportStatus.text = "Done"
exportProgress.isVisible = true
}
is SmsExportProgress.InProgress -> {
exportStatus.text = "$it"
exportProgress.isVisible = true
exportProgress.progress = it.progress
exportProgress.max = it.total
}
SmsExportProgress.Init -> {
exportStatus.text = "Init"
exportProgress.isVisible = false
}
SmsExportProgress.Starting -> {
exportStatus.text = "Starting"
exportProgress.isVisible = true
}
}
}
setAsDefaultSmsButton.setOnClickListener {
DefaultSmsHelper.becomeDefaultSms(this).either(
onFailure = { onAppIsIneligableForDefaultSmsSelection() },
onSuccess = this::onStartActivityForDefaultSmsSelection
)
}
clearDefaultSmsButton.setOnClickListener {
DefaultSmsHelper.releaseDefaultSms(this).either(
onFailure = {
when (it) {
ReleaseSmsAppFailure.APP_IS_INELIGABLE_TO_RELEASE_SMS_SELECTION -> onAppIsIneligableForReleaseSmsSelection()
ReleaseSmsAppFailure.NO_METHOD_TO_RELEASE_SMS_AVIALABLE -> onNoMethodToReleaseSmsAvailable()
}
},
onSuccess = this::onStartActivityForReleaseSmsSelection
)
}
exportSmsButton.setOnClickListener {
exportSmsButton.isEnabled = false
ContextCompat.startForegroundService(this, Intent(this, TestSmsExportService::class.java))
}
presentButtonState()
}
override fun onResume() {
super.onResume()
presentButtonState()
}
override fun onDestroy() {
super.onDestroy()
disposables.clear()
}
private fun presentButtonState() {
setAsDefaultSmsButton.isVisible = !DefaultSmsHelper.isDefaultSms(this)
clearDefaultSmsButton.isVisible = DefaultSmsHelper.isDefaultSms(this)
exportSmsButton.isVisible = DefaultSmsHelper.isDefaultSms(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
1 -> presentButtonState()
2 -> presentButtonState()
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
private fun onStartActivityForDefaultSmsSelection(intent: Intent) {
startActivityForResult(intent, 1)
}
private fun onAppIsIneligableForDefaultSmsSelection() {
if (DefaultSmsHelper.isDefaultSms(this)) {
Toast.makeText(this, "Already the SMS manager.", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Cannot be SMS manager.", Toast.LENGTH_SHORT).show()
}
}
private fun onStartActivityForReleaseSmsSelection(intent: Intent) {
startActivityForResult(intent, 2)
}
private fun onAppIsIneligableForReleaseSmsSelection() {
if (!DefaultSmsHelper.isDefaultSms(this)) {
Toast.makeText(this, "Already not the SMS manager.", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Cannot be SMS manager.", Toast.LENGTH_SHORT).show()
}
}
private fun onNoMethodToReleaseSmsAvailable() {
Toast.makeText(this, "Cannot automatically release sms. Display manual instructions.", Toast.LENGTH_SHORT).show()
}
}

Wyświetl plik

@ -0,0 +1,11 @@
package org.signal.smsexporter.app
import android.app.Service
import android.content.Intent
import android.os.IBinder
class SendResponseViaMessageService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
}

Wyświetl plik

@ -0,0 +1,165 @@
package org.signal.smsexporter.app
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import org.signal.core.util.logging.Log
import org.signal.smsexporter.ExportableMessage
import org.signal.smsexporter.SmsExportService
import org.signal.smsexporter.SmsExportState
import java.io.InputStream
class TestSmsExportService : SmsExportService() {
companion object {
private val TAG = Log.tag(TestSmsExportService::class.java)
private const val NOTIFICATION_ID = 1234
private const val NOTIFICATION_CHANNEL_ID = "sms_export"
private const val startTime = 1659377120L
}
override fun getNotification(progress: Int, total: Int): ExportNotification {
ensureNotificationChannel()
return ExportNotification(
id = NOTIFICATION_ID,
NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Test Exporter")
.setProgress(total, progress, false)
.build()
)
}
override fun getExportState(exportableMessage: ExportableMessage): SmsExportState {
return SmsExportState()
}
override fun getUnexportedMessageCount(): Int {
return 50
}
override fun getUnexportedMessages(): Iterable<ExportableMessage> {
return object : Iterable<ExportableMessage> {
override fun iterator(): Iterator<ExportableMessage> {
return ExportableMessageIterator(getUnexportedMessageCount())
}
}
}
override fun onMessageExportStarted(exportableMessage: ExportableMessage) {
Log.d(TAG, "onMessageExportStarted() called with: exportableMessage = $exportableMessage")
}
override fun onMessageExportSucceeded(exportableMessage: ExportableMessage) {
Log.d(TAG, "onMessageExportSucceeded() called with: exportableMessage = $exportableMessage")
}
override fun onMessageExportFailed(exportableMessage: ExportableMessage) {
Log.d(TAG, "onMessageExportFailed() called with: exportableMessage = $exportableMessage")
}
override fun onMessageIdCreated(exportableMessage: ExportableMessage, messageId: Long) {
Log.d(TAG, "onMessageIdCreated() called with: exportableMessage = $exportableMessage, messageId = $messageId")
}
override fun onAttachmentPartExportStarted(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) {
Log.d(TAG, "onAttachmentPartExportStarted() called with: exportableMessage = $exportableMessage, attachment = $part")
}
override fun onAttachmentPartExportSucceeded(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) {
Log.d(TAG, "onAttachmentPartExportSucceeded() called with: exportableMessage = $exportableMessage, attachment = $part")
}
override fun onAttachmentPartExportFailed(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) {
Log.d(TAG, "onAttachmentPartExportFailed() called with: exportableMessage = $exportableMessage, attachment = $part")
}
override fun onRecipientExportStarted(exportableMessage: ExportableMessage, recipient: String) {
Log.d(TAG, "onRecipientExportStarted() called with: exportableMessage = $exportableMessage, recipient = $recipient")
}
override fun onRecipientExportSucceeded(exportableMessage: ExportableMessage, recipient: String) {
Log.d(TAG, "onRecipientExportSucceeded() called with: exportableMessage = $exportableMessage, recipient = $recipient")
}
override fun onRecipientExportFailed(exportableMessage: ExportableMessage, recipient: String) {
Log.d(TAG, "onRecipientExportFailed() called with: exportableMessage = $exportableMessage, recipient = $recipient")
}
override fun getInputStream(part: ExportableMessage.Mms.Part): InputStream {
return BitmapGenerator.getStream()
}
override fun onAttachmentWrittenToDisk(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) {
Log.d(TAG, "onAttachmentWrittenToDisk() called with: exportableMessage = $exportableMessage, attachment = $part")
}
override fun onExportPassCompleted() {
Log.d(TAG, "onExportPassCompleted() called")
}
private fun ensureNotificationChannel() {
val notificationManager = NotificationManagerCompat.from(this)
val channel = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
if (channel == null) {
val newChannel = NotificationChannelCompat
.Builder(NOTIFICATION_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setName("misc")
.build()
notificationManager.createNotificationChannel(newChannel)
}
}
private class ExportableMessageIterator(private val size: Int) : Iterator<ExportableMessage> {
private var emitted: Int = 0
override fun hasNext(): Boolean {
return emitted < size
}
override fun next(): ExportableMessage {
val message = if (emitted % 2 == 0) {
getSmsMessage(emitted)
} else {
getMmsMessage(emitted)
}
emitted++
return message
}
private fun getMmsMessage(it: Int): ExportableMessage.Mms {
val me = "+15065550101"
val addresses = setOf(me, "+15065550102", "+15065550121")
val address = addresses.random()
return ExportableMessage.Mms(
id = "$it",
addresses = addresses,
dateSent = startTime + it - 1,
dateReceived = startTime + it,
isRead = true,
isOutgoing = address == me,
sender = address,
parts = listOf(
ExportableMessage.Mms.Part.Text("Hello, $it from $address"),
ExportableMessage.Mms.Part.Stream("$it", "image/jpeg")
)
)
}
private fun getSmsMessage(it: Int): ExportableMessage.Sms {
return ExportableMessage.Sms(
id = it.toString(),
address = "+15065550102",
body = "Hello, World! $it",
dateSent = startTime + it - 1,
dateReceived = startTime + it,
isRead = true,
isOutgoing = it % 4 == 0
)
}
}
}

Wyświetl plik

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Wyświetl plik

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

Wyświetl plik

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/export_sms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/export_sms"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/clear_default_sms"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/set_as_default_sms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_as_default_sms_app"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/clear_default_sms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/clear_default_sms_app"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/export_sms" />
<TextView
android:id="@+id/export_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@id/export_progress" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/export_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
tools:max="100"
tools:progress="50" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Wyświetl plik

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.4 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.8 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 982 B

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.7 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.9 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 3.8 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.8 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 5.8 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 3.8 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 7.6 KiB

Wyświetl plik

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

Wyświetl plik

@ -0,0 +1,6 @@
<resources>
<string name="app_name">Sms Exporter Test App</string>
<string name="set_as_default_sms_app">Set as default SMS app</string>
<string name="clear_default_sms_app">Clear default SMS app</string>
<string name="export_sms">Export SMS</string>
</resources>

Wyświetl plik

@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Signal" parent="Theme.MaterialComponents.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

Wyświetl plik

@ -0,0 +1,18 @@
apply from: "$rootProject.projectDir/signalModule.gradle"
dependencies {
lintChecks project(':lintchecks')
implementation project(':core-util')
coreLibraryDesugaring libs.android.tools.desugar
implementation libs.androidx.core.ktx
implementation libs.androidx.annotation
implementation libs.androidx.appcompat
implementation libs.androidx.core.role
implementation libs.android.smsmms
implementation libs.rxjava3.rxjava
implementation libs.rxjava3.rxandroid
implementation libs.rxjava3.rxkotlin
}

Wyświetl plik

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Wyświetl plik

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.signal.smsexporter">
</manifest>

Wyświetl plik

@ -0,0 +1,13 @@
package org.signal.smsexporter
enum class BecomeSmsAppFailure {
/**
* Already the default sms app
*/
ALREADY_DEFAULT_SMS,
/**
* The system doesn't think we are allowed to become the sms app
*/
ROLE_IS_NOT_AVAILABLE
}

Wyświetl plik

@ -0,0 +1,26 @@
package org.signal.smsexporter
import android.content.Context
import org.signal.smsexporter.internal.BecomeDefaultSmsUseCase
import org.signal.smsexporter.internal.IsDefaultSms
import org.signal.smsexporter.internal.ReleaseDefaultSmsUseCase
/**
* Basic API for checking / becoming / releasing default SMS
*/
object DefaultSmsHelper {
/**
* Checks whether this app is currently the default SMS app
*/
fun isDefaultSms(context: Context) = IsDefaultSms.checkIsDefaultSms(context)
/**
* Attempts to get an Intent which can be launched to become the default SMS app
*/
fun becomeDefaultSms(context: Context) = BecomeDefaultSmsUseCase.execute(context)
/**
* Attempts to get an Intent which can be launched to relinquish the role of default SMS app
*/
fun releaseDefaultSms(context: Context) = ReleaseDefaultSmsUseCase.execute(context)
}

Wyświetl plik

@ -0,0 +1,54 @@
package org.signal.smsexporter
/**
* Represents an exportable MMS or SMS message
*/
sealed interface ExportableMessage {
/**
* An exportable SMS message
*/
data class Sms(
val id: String,
val address: String,
val dateReceived: Long,
val dateSent: Long,
val isRead: Boolean,
val isOutgoing: Boolean,
val body: String
) : ExportableMessage
/**
* An exportable MMS message
*/
data class Mms(
val id: String,
val addresses: Set<String>,
val dateReceived: Long,
val dateSent: Long,
val isRead: Boolean,
val isOutgoing: Boolean,
val parts: List<Part>,
val sender: CharSequence
) : ExportableMessage {
/**
* An attachment, attached to an MMS message
*/
sealed interface Part {
val contentType: String
val contentId: String
data class Text(val text: String) : Part {
override val contentType: String = "text/plain"
override val contentId: String = "text"
}
data class Stream(
val id: String,
override val contentType: String
) : Part {
override val contentId: String = id
}
}
}
}

Wyświetl plik

@ -0,0 +1,13 @@
package org.signal.smsexporter
enum class ReleaseSmsAppFailure {
/**
* Occurs when we are not the default sms app
*/
APP_IS_INELIGABLE_TO_RELEASE_SMS_SELECTION,
/**
* No good way to release sms. Have to instruct user manually.
*/
NO_METHOD_TO_RELEASE_SMS_AVIALABLE
}

Wyświetl plik

@ -0,0 +1,29 @@
package org.signal.smsexporter
/**
* Expresses the current progress of SMS exporting.
*/
sealed class SmsExportProgress {
/**
* Have not started yet.
*/
object Init : SmsExportProgress()
/**
* Starting up and about to start processing messages
*/
object Starting : SmsExportProgress()
/**
* Processing messages
*/
data class InProgress(
val progress: Int,
val total: Int
) : SmsExportProgress()
/**
* All done.
*/
object Done : SmsExportProgress()
}

Wyświetl plik

@ -0,0 +1,312 @@
package org.signal.smsexporter
import android.app.Notification
import android.app.Service
import android.content.Intent
import android.os.IBinder
import io.reactivex.rxjava3.processors.BehaviorProcessor
import org.signal.core.util.Result
import org.signal.core.util.Try
import org.signal.core.util.logging.Log
import org.signal.smsexporter.internal.mms.ExportMmsMessagesUseCase
import org.signal.smsexporter.internal.mms.ExportMmsPartsUseCase
import org.signal.smsexporter.internal.mms.ExportMmsRecipientsUseCase
import org.signal.smsexporter.internal.mms.GetOrCreateMmsThreadIdsUseCase
import org.signal.smsexporter.internal.sms.ExportSmsMessagesUseCase
import java.io.InputStream
import java.util.concurrent.Executor
import java.util.concurrent.Executors
/**
* Exports SMS and MMS messages to the system database.
*/
abstract class SmsExportService : Service() {
companion object {
private val TAG = Log.tag(SmsExportService::class.java)
/**
* Progress state which can be listened to by interested components, such as fragments.
*/
val progressState: BehaviorProcessor<SmsExportProgress> = BehaviorProcessor.createDefault(SmsExportProgress.Init)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private val threadCache: MutableMap<Set<String>, Long> = mutableMapOf()
private var isStarted = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "Got start command in SMS Export Service")
startExport()
return START_NOT_STICKY
}
private fun startExport() {
if (isStarted) {
return
}
Log.d(TAG, "Running export...")
isStarted = true
updateNotification(-1, -1)
progressState.onNext(SmsExportProgress.Starting)
var progress = 0
executor.execute {
val totalCount = getUnexportedMessageCount()
getUnexportedMessages().forEach { message ->
val exportState = getExportState(message)
if (exportState.progress != SmsExportState.Progress.COMPLETED) {
when (message) {
is ExportableMessage.Sms -> exportSms(exportState, message)
is ExportableMessage.Mms -> exportMms(exportState, message)
}
progress++
updateNotification(progress, totalCount)
progressState.onNext(SmsExportProgress.InProgress(progress, totalCount))
}
}
onExportPassCompleted()
progressState.onNext(SmsExportProgress.Done)
stopForeground(true)
isStarted = false
}
}
/**
* The executor that this service should do its work on.
*/
protected open val executor: Executor = Executors.newSingleThreadExecutor()
/**
* Produces the notification and notification id to display for this foreground service.
* The progress and total represent how many messages we've processed, and how many total
* we have to process. Failures and successes are both aggregated in this progress. You can
* query for "failure" state *after* we signal completion of a run.
*/
protected abstract fun getNotification(progress: Int, total: Int): ExportNotification
/**
* Gets the initial export state. This is only called once per message, before any processing
* is done. It is used as a "known" state value, and via the onX methods below, it is up to the
* application to properly update the underlying data structure when changes occur.
*/
protected abstract fun getExportState(exportableMessage: ExportableMessage): SmsExportState
/**
* Gets the total number of messages to process. This is only used for the notification and
* progress events.
*/
protected abstract fun getUnexportedMessageCount(): Int
/**
* Gets an iterable of exportable messages.
*/
protected abstract fun getUnexportedMessages(): Iterable<ExportableMessage>
/**
* We've started the export process for a given MMS / SMS message
*/
protected abstract fun onMessageExportStarted(exportableMessage: ExportableMessage)
/**
* We've completely succeeded exporting a given MMS / SMS message
*/
protected abstract fun onMessageExportSucceeded(exportableMessage: ExportableMessage)
/**
* We've failed to completely export a given MMS / SMS message
*/
protected abstract fun onMessageExportFailed(exportableMessage: ExportableMessage)
/**
* We've written the message contents to the system database and were handed back an id.
*/
protected abstract fun onMessageIdCreated(exportableMessage: ExportableMessage, messageId: Long)
/**
* We've begun trying to export a part row for an attachment for the given message
*/
protected abstract fun onAttachmentPartExportStarted(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part)
/**
* We've successfully exported the attachment part for a given message
*/
protected abstract fun onAttachmentPartExportSucceeded(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part)
/**
* We failed to export the attachment part for a given message.
*/
protected abstract fun onAttachmentPartExportFailed(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part)
/**
* We've begun trying to export a recipient addr for a given message
*/
protected abstract fun onRecipientExportStarted(exportableMessage: ExportableMessage, recipient: String)
/**
* We've successfully exported a recipient addr for a given message
*/
protected abstract fun onRecipientExportSucceeded(exportableMessage: ExportableMessage, recipient: String)
/**
* We've failed to export a recipient addr for a given message
*/
protected abstract fun onRecipientExportFailed(exportableMessage: ExportableMessage, recipient: String)
/**
* Gets the input stream for the given attachment, so that it might be written out to disk.
*/
protected abstract fun getInputStream(part: ExportableMessage.Mms.Part): InputStream
/**
* Called after the attachment is successfully written to disk.
*/
protected abstract fun onAttachmentWrittenToDisk(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part)
/**
* Called when an export pass completes. It is up to the implementation to determine whether
* there are still messages to export.
*/
protected abstract fun onExportPassCompleted()
private fun updateNotification(progress: Int, total: Int) {
val exportNotification = getNotification(progress, total)
startForeground(exportNotification.id, exportNotification.notification)
}
private fun exportSms(smsExportState: SmsExportState, sms: ExportableMessage.Sms) {
onMessageExportStarted(sms)
val mayAlreadyExist = smsExportState.progress == SmsExportState.Progress.STARTED
ExportSmsMessagesUseCase.execute(this, sms, mayAlreadyExist).either(onSuccess = {
onMessageExportSucceeded(sms)
}, onFailure = {
onMessageExportFailed(sms)
})
}
private fun exportMms(smsExportState: SmsExportState, mms: ExportableMessage.Mms) {
onMessageExportStarted(mms)
val threadIdOutput: GetOrCreateMmsThreadIdsUseCase.Output? = getThreadId(mms)
val exportMmsOutput: ExportMmsMessagesUseCase.Output? = threadIdOutput?.let { exportMms(smsExportState, it) }
val exportMmsPartsOutput: List<ExportMmsPartsUseCase.Output?>? = exportMmsOutput?.let { exportMmsParts(smsExportState, it) }
val writeMmsPartsOutput: List<Result<Unit, Throwable>>? = exportMmsPartsOutput?.filterNotNull()?.map { writeAttachmentToDisk(smsExportState, it) }
val exportMmsRecipients: List<Unit?>? = exportMmsOutput?.let { exportMmsRecipients(smsExportState, it) }
if (threadIdOutput != null &&
exportMmsOutput != null &&
exportMmsPartsOutput != null && !exportMmsPartsOutput.contains(null) &&
writeMmsPartsOutput != null && writeMmsPartsOutput.all { it is Result.Success } &&
exportMmsRecipients != null && !exportMmsRecipients.contains(null)
) {
onMessageExportSucceeded(mms)
} else {
onMessageExportFailed(mms)
}
}
private fun getThreadId(mms: ExportableMessage.Mms): GetOrCreateMmsThreadIdsUseCase.Output? {
return GetOrCreateMmsThreadIdsUseCase.execute(this, mms, threadCache).either(
onSuccess = { output ->
output
},
onFailure = {
Log.w(TAG, "Failed to get thread id for export", it)
null
}
)
}
private fun exportMms(smsExportState: SmsExportState, threadIdOutput: GetOrCreateMmsThreadIdsUseCase.Output): ExportMmsMessagesUseCase.Output? {
return ExportMmsMessagesUseCase.execute(this, threadIdOutput, smsExportState.progress == SmsExportState.Progress.STARTED).either(
onSuccess = {
onMessageIdCreated(it.mms, it.messageId)
it
},
onFailure = {
Log.w(TAG, "Failed to export MMS into system database", it)
null
}
)
}
private fun exportMmsParts(smsExportState: SmsExportState, exportMmsOutput: ExportMmsMessagesUseCase.Output): List<ExportMmsPartsUseCase.Output?> {
val attachments = exportMmsOutput.mms.parts
return if (attachments.isEmpty()) {
emptyList()
} else {
attachments.filterNot { it.contentId in smsExportState.completedAttachments }.map { attachment ->
onAttachmentPartExportStarted(exportMmsOutput.mms, attachment)
ExportMmsPartsUseCase.execute(this, attachment, exportMmsOutput, smsExportState.startedAttachments.contains(attachment.contentId)).either(
onSuccess = {
onAttachmentPartExportSucceeded(exportMmsOutput.mms, attachment)
it
},
onFailure = {
onAttachmentPartExportFailed(exportMmsOutput.mms, attachment)
Log.d(TAG, "Could not export MMS Part", it)
null
}
)
}
}
}
private fun exportMmsRecipients(smsExportState: SmsExportState, exportMmsOutput: ExportMmsMessagesUseCase.Output): List<Unit?> {
val recipients = exportMmsOutput.mms.addresses.map { it.toString() }.toSet()
return if (recipients.isEmpty()) {
emptyList()
} else {
recipients.filterNot { it in smsExportState.completedRecipients }.map { recipient ->
onRecipientExportStarted(exportMmsOutput.mms, recipient)
ExportMmsRecipientsUseCase.execute(this, exportMmsOutput.messageId, recipient, exportMmsOutput.mms.sender.toString(), smsExportState.startedRecipients.contains(recipient)).either(
onSuccess = {
onRecipientExportSucceeded(exportMmsOutput.mms, recipient)
},
onFailure = {
onRecipientExportFailed(exportMmsOutput.mms, recipient)
Log.w(TAG, "Failed to export MMS Recipient", it)
null
}
)
}
}
}
private fun writeAttachmentToDisk(smsExportState: SmsExportState, output: ExportMmsPartsUseCase.Output): Try<Unit> {
if (output.part.contentId in smsExportState.completedAttachments) {
return Try.success(Unit)
}
if (output.part is ExportableMessage.Mms.Part.Text) {
onAttachmentWrittenToDisk(output.message, output.part)
Try.success(Unit)
}
return try {
contentResolver.openOutputStream(output.uri)!!.use { out ->
getInputStream(output.part).use {
it.copyTo(out)
}
}
onAttachmentWrittenToDisk(output.message, output.part)
Try.success(Unit)
} catch (e: Exception) {
Log.d(TAG, "Failed to write attachment to disk.", e)
Try.failure(e)
}
}
data class ExportNotification(
val id: Int,
val notification: Notification
)
}

Wyświetl plik

@ -0,0 +1,21 @@
package org.signal.smsexporter
/**
* Describes the current "Export State" of a given message. This should be updated
* by and persisted by the application whenever a state change occurs.
*/
data class SmsExportState(
val messageId: Long = -1L,
val startedRecipients: Set<String> = emptySet(),
val completedRecipients: Set<String> = emptySet(),
val startedAttachments: Set<String> = emptySet(),
val completedAttachments: Set<String> = emptySet(),
val copiedAttachments: Set<String> = emptySet(),
val progress: Progress = Progress.INIT
) {
enum class Progress {
INIT,
STARTED,
COMPLETED
}
}

Wyświetl plik

@ -0,0 +1,36 @@
package org.signal.smsexporter.internal
import android.app.role.RoleManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Telephony
import androidx.core.role.RoleManagerCompat
import org.signal.core.util.Result
import org.signal.smsexporter.BecomeSmsAppFailure
/**
* Requests that this app becomes the default SMS app. The exact UX here is
* API dependant.
*
* Returns an intent to fire for a result, or a Failure.
*/
internal object BecomeDefaultSmsUseCase {
fun execute(context: Context): Result<Intent, BecomeSmsAppFailure> {
return if (IsDefaultSms.checkIsDefaultSms(context)) {
Result.failure(BecomeSmsAppFailure.ALREADY_DEFAULT_SMS)
} else if (Build.VERSION.SDK_INT >= 29) {
val roleManager = context.getSystemService(RoleManager::class.java)
if (roleManager.isRoleAvailable(RoleManagerCompat.ROLE_SMS)) {
Result.success(roleManager.createRequestRoleIntent(RoleManagerCompat.ROLE_SMS))
} else {
Result.failure(BecomeSmsAppFailure.ROLE_IS_NOT_AVAILABLE)
}
} else {
Result.success(
Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName)
)
}
}
}

Wyświetl plik

@ -0,0 +1,20 @@
package org.signal.smsexporter.internal
import android.app.role.RoleManager
import android.content.Context
import android.os.Build
import android.provider.Telephony
import androidx.core.role.RoleManagerCompat
/**
* Uses the appropriate service to check if we are the default sms
*/
internal object IsDefaultSms {
fun checkIsDefaultSms(context: Context): Boolean {
return if (Build.VERSION.SDK_INT >= 29) {
context.getSystemService(RoleManager::class.java).isRoleHeld(RoleManagerCompat.ROLE_SMS)
} else {
context.packageName == Telephony.Sms.getDefaultSmsPackage(context)
}
}
}

Wyświetl plik

@ -0,0 +1,31 @@
package org.signal.smsexporter.internal
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Settings
import org.signal.core.util.Result
import org.signal.smsexporter.ReleaseSmsAppFailure
/**
* Request to no longer be the default SMS app. This has a pretty bad UX, we need
* to get the user to manually do it in settings. On API 24+ we can launch the default
* app settings screen, whereas on 19 to 23, we can't. In this situation, we should
* display some UX (perhaps based off API level) explaining to the user exactly what to
* do.
*
* Returns the Intent to fire off, or a Failure.
*/
internal object ReleaseDefaultSmsUseCase {
fun execute(context: Context): Result<Intent, ReleaseSmsAppFailure> {
return if (!IsDefaultSms.checkIsDefaultSms(context)) {
Result.failure(ReleaseSmsAppFailure.APP_IS_INELIGABLE_TO_RELEASE_SMS_SELECTION)
} else if (Build.VERSION.SDK_INT >= 24) {
Result.success(
Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
)
} else {
Result.failure(ReleaseSmsAppFailure.NO_METHOD_TO_RELEASE_SMS_AVIALABLE)
}
}
}

Wyświetl plik

@ -0,0 +1,79 @@
package org.signal.smsexporter.internal.mms
import android.content.ContentUris
import android.content.Context
import android.provider.Telephony
import androidx.core.content.contentValuesOf
import com.google.android.mms.pdu_alt.PduHeaders
import org.signal.core.util.Try
import org.signal.core.util.logging.Log
import org.signal.smsexporter.ExportableMessage
/**
* Takes a list of messages and inserts them as a single batch. This includes
* thread id get/create if necessary. The output is a list of (mms, message_id)
*/
internal object ExportMmsMessagesUseCase {
private val TAG = Log.tag(ExportMmsMessagesUseCase::class.java)
fun execute(
context: Context,
getOrCreateThreadOutput: GetOrCreateMmsThreadIdsUseCase.Output,
checkForExistence: Boolean
): Try<Output> {
try {
val (mms, threadId) = getOrCreateThreadOutput
val transactionId = "signal:T${mms.id}"
if (checkForExistence) {
Log.d(TAG, "Checking if the message is already in the database.")
val messageId = isMessageAlreadyInDatabase(context, transactionId)
if (messageId != -1L) {
Log.d(TAG, "Message exists in database. Returning its id.")
return Try.success(Output(mms, messageId))
}
}
val mmsContentValues = contentValuesOf(
Telephony.Mms.THREAD_ID to threadId,
Telephony.Mms.DATE to mms.dateReceived,
Telephony.Mms.DATE_SENT to mms.dateSent,
Telephony.Mms.MESSAGE_BOX to if (mms.isOutgoing) Telephony.Mms.MESSAGE_BOX_SENT else Telephony.Mms.MESSAGE_BOX_INBOX,
Telephony.Mms.READ to if (mms.isRead) 1 else 0,
Telephony.Mms.CONTENT_TYPE to "application/vnd.wap.multipart.related",
Telephony.Mms.MESSAGE_TYPE to PduHeaders.MESSAGE_TYPE_SEND_REQ,
Telephony.Mms.MMS_VERSION to PduHeaders.MMS_VERSION_1_3,
Telephony.Mms.MESSAGE_CLASS to "personal",
Telephony.Mms.PRIORITY to PduHeaders.PRIORITY_NORMAL,
Telephony.Mms.TRANSACTION_ID to transactionId,
Telephony.Mms.RESPONSE_STATUS to PduHeaders.RESPONSE_STATUS_OK
)
val uri = context.contentResolver.insert(Telephony.Mms.CONTENT_URI, mmsContentValues)
val newMessageId = ContentUris.parseId(uri!!)
return Try.success(Output(getOrCreateThreadOutput.mms, newMessageId))
} catch (e: Exception) {
return Try.failure(e)
}
}
private fun isMessageAlreadyInDatabase(context: Context, transactionId: String): Long {
return context.contentResolver.query(
Telephony.Mms.CONTENT_URI,
arrayOf("_id"),
"${Telephony.Mms.TRANSACTION_ID} == ?",
arrayOf(transactionId),
null
)?.use {
it.moveToFirst()
it.getLong(0)
} ?: -1L
}
data class Output(
val mms: ExportableMessage.Mms,
val messageId: Long
)
}

Wyświetl plik

@ -0,0 +1,63 @@
package org.signal.smsexporter.internal.mms
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.provider.Telephony
import androidx.core.content.contentValuesOf
import org.signal.core.util.Try
import org.signal.core.util.logging.Log
import org.signal.smsexporter.ExportableMessage
/**
* Inserts the part objects for the given list of mms message insertion outputs. Returns a list
* of attachments that can be enqueued for a disk write.
*/
internal object ExportMmsPartsUseCase {
private val TAG = Log.tag(ExportMmsPartsUseCase::class.java)
fun execute(context: Context, part: ExportableMessage.Mms.Part, output: ExportMmsMessagesUseCase.Output, checkForExistence: Boolean): Try<Output> {
try {
val (message, messageId) = output
val contentId = "<signal:${part.contentId}>"
val mmsPartUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(messageId.toString()).appendPath("part").build()
if (checkForExistence) {
Log.d(TAG, "Checking attachment that may already be present...")
val partId: Long? = context.contentResolver.query(mmsPartUri, arrayOf(Telephony.Mms.Part._ID), "${Telephony.Mms.Part.CONTENT_ID} = ?", arrayOf(contentId), null)?.use {
if (it.moveToFirst()) {
it.getLong(0)
} else {
null
}
}
if (partId != null) {
Log.d(TAG, "Found attachment part that already exists.")
return Try.success(
Output(
uri = ContentUris.withAppendedId(mmsPartUri, partId),
part = part,
message = message
)
)
}
}
val mmsPartContentValues = contentValuesOf(
Telephony.Mms.Part.MSG_ID to messageId,
Telephony.Mms.Part.CONTENT_TYPE to part.contentType,
Telephony.Mms.Part.CONTENT_ID to contentId,
Telephony.Mms.Part.TEXT to if (part is ExportableMessage.Mms.Part.Text) part.text else null
)
val attachmentUri = context.contentResolver.insert(mmsPartUri, mmsPartContentValues)!!
return Try.success(Output(attachmentUri, part, message))
} catch (e: Exception) {
return Try.failure(e)
}
}
data class Output(val uri: Uri, val part: ExportableMessage.Mms.Part, val message: ExportableMessage)
}

Wyświetl plik

@ -0,0 +1,48 @@
package org.signal.smsexporter.internal.mms
import android.content.Context
import android.provider.Telephony
import androidx.core.content.contentValuesOf
import com.google.android.mms.pdu_alt.CharacterSets
import com.google.android.mms.pdu_alt.PduHeaders
import org.signal.core.util.Try
import org.signal.core.util.logging.Log
import org.signal.smsexporter.internal.sms.ExportSmsMessagesUseCase
/**
* Inserts the recipients for each individual message in the insert mms output. Returns nothing.
*/
object ExportMmsRecipientsUseCase {
private val TAG = Log.tag(ExportSmsMessagesUseCase::class.java)
fun execute(context: Context, messageId: Long, recipient: String, sender: String, checkForExistence: Boolean): Try<Unit> {
try {
val addrUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(messageId.toString()).appendPath("addr").build()
if (checkForExistence) {
Log.d(TAG, "Checking for recipient that may have already been inserted...")
val exists = context.contentResolver.query(addrUri, arrayOf("_id"), "${Telephony.Mms.Addr.ADDRESS} == ?", arrayOf(recipient), null)?.use {
it.moveToFirst()
} ?: false
if (exists) {
Log.d(TAG, "Recipient was already inserted. Skipping.")
return Try.success(Unit)
}
}
val addrValues = contentValuesOf(
Telephony.Mms.Addr.ADDRESS to recipient,
Telephony.Mms.Addr.CHARSET to CharacterSets.DEFAULT_CHARSET,
Telephony.Mms.Addr.TYPE to if (recipient == sender) PduHeaders.FROM else PduHeaders.TO,
)
context.contentResolver.insert(addrUri, addrValues)
return Try.success(Unit)
} catch (e: Exception) {
return Try.failure(e)
}
}
}

Wyświetl plik

@ -0,0 +1,50 @@
package org.signal.smsexporter.internal.mms
import android.content.Context
import com.klinker.android.send_message.Utils
import org.signal.core.util.Try
import org.signal.smsexporter.ExportableMessage
/**
* Given a list of messages, gets or creates the threadIds for each different recipient set.
* Returns a list of outputs that tie a given message to a thread id.
*
* This method will also filter out messages that do not have addresses.
*/
internal object GetOrCreateMmsThreadIdsUseCase {
fun execute(
context: Context,
mms: ExportableMessage.Mms,
threadCache: MutableMap<Set<String>, Long>
): Try<Output> {
return try {
val recipients = getRecipientSet(mms)
val threadId = getOrCreateThreadId(context, recipients, threadCache)
Try.success(Output(mms, threadId))
} catch (e: Exception) {
Try.failure(e)
}
}
private fun getOrCreateThreadId(context: Context, recipients: Set<String>, cache: MutableMap<Set<String>, Long>): Long {
return if (cache.containsKey(recipients)) {
cache[recipients]!!
} else {
val threadId = Utils.getOrCreateThreadId(context, recipients)
cache[recipients] = threadId
threadId
}
}
private fun getRecipientSet(mms: ExportableMessage.Mms): Set<String> {
val recipients = mms.addresses
if (recipients.isEmpty()) {
error("Expected non-empty recipient count.")
}
return HashSet(recipients.map { it.toString() })
}
data class Output(val mms: ExportableMessage.Mms, val threadId: Long)
}

Wyświetl plik

@ -0,0 +1,49 @@
package org.signal.smsexporter.internal.sms
import android.content.Context
import android.provider.Telephony
import androidx.core.content.contentValuesOf
import org.signal.core.util.Try
import org.signal.smsexporter.ExportableMessage
import java.lang.Exception
/**
* Given a list of Sms messages, export each one to the system SMS database
* Returns nothing.
*/
internal object ExportSmsMessagesUseCase {
fun execute(context: Context, sms: ExportableMessage.Sms, checkForExistence: Boolean): Try<Unit> {
try {
if (checkForExistence) {
val exists = context.contentResolver.query(
Telephony.Sms.CONTENT_URI,
arrayOf("_id"),
"${Telephony.Sms.ADDRESS} = ? AND ${Telephony.Sms.DATE_SENT} = ?",
arrayOf(sms.address, sms.dateSent.toString()),
null
)?.use {
it.count > 0
} ?: false
if (exists) {
return Try.success(Unit)
}
}
val contentValues = contentValuesOf(
Telephony.Sms.ADDRESS to sms.address,
Telephony.Sms.BODY to sms.body,
Telephony.Sms.DATE to sms.dateReceived,
Telephony.Sms.DATE_SENT to sms.dateSent,
Telephony.Sms.READ to if (sms.isRead) 1 else 0,
Telephony.Sms.TYPE to if (sms.isOutgoing) Telephony.Sms.MESSAGE_TYPE_SENT else Telephony.Sms.MESSAGE_TYPE_INBOX
)
context.contentResolver.insert(Telephony.Sms.CONTENT_URI, contentValues)
return Try.success(Unit)
} catch (e: Exception) {
return Try.failure(e)
}
}
}