mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-06 21:35:46 +01:00
Move BlobProvider and supporting classes to core-util.
This commit is contained in:
@@ -39,6 +39,7 @@ import org.signal.core.util.MemoryTracker;
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.concurrent.AnrDetector;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.logging.AndroidLogger;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.util.logging.Scrubber;
|
||||
@@ -51,10 +52,10 @@ import org.signal.ringrtc.CallManager;
|
||||
import org.thoughtcrime.securesms.apkupdate.ApkUpdateRefreshListener;
|
||||
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.conversation.drafts.DraftBlobs;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||
import org.thoughtcrime.securesms.database.LogDatabase;
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
@@ -99,7 +100,6 @@ import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchRece
|
||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.registration.util.RegistrationUtil;
|
||||
@@ -175,7 +175,7 @@ public class ApplicationContext extends Application implements AppForegroundObse
|
||||
SqlCipherLibraryLoader.load();
|
||||
SignalDatabase.init(this,
|
||||
DatabaseSecretProvider.getOrCreateDatabaseSecret(this),
|
||||
AttachmentSecretProvider.getInstance(this).getOrCreateAttachmentSecret());
|
||||
AttachmentSecretProvider.getInstance(this, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret());
|
||||
Logger.setTarget(SqlCipherLogTarget.INSTANCE);
|
||||
})
|
||||
.addBlocking("signal-store", () -> SignalStore.init(this))
|
||||
@@ -578,7 +578,7 @@ public class ApplicationContext extends Application implements AppForegroundObse
|
||||
|
||||
@WorkerThread
|
||||
private void initializeBlobProvider() {
|
||||
BlobProvider.getInstance().initialize(this);
|
||||
AppDependencies.getBlobs().initialize(this, DraftBlobs.INSTANCE::deleteOrphanedDraftFiles);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
||||
@@ -11,10 +11,11 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.contentproviders.BlobProvider;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.notifications.v2.InChatNotificationSoundSuppressor;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -88,9 +89,9 @@ public class AudioRecorder {
|
||||
|
||||
ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe();
|
||||
|
||||
BlobProvider.BlobBuilder blobBuilder = BlobProvider.getInstance()
|
||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||
.withMimeType(MediaUtil.AUDIO_AAC);
|
||||
BlobProvider.BlobBuilder blobBuilder = AppDependencies.getBlobs()
|
||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||
.withMimeType(MediaUtil.AUDIO_AAC);
|
||||
|
||||
recordingUri = blobBuilder.buildUriForDraftAttachment();
|
||||
recordingUriFuture = blobBuilder.createForDraftAttachmentAsync(context);
|
||||
|
||||
@@ -11,9 +11,9 @@ import androidx.appcompat.content.res.AppCompatResources
|
||||
import com.airbnb.lottie.SimpleColorFilter
|
||||
import org.signal.core.models.media.Media
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -80,7 +80,7 @@ object AvatarRenderer {
|
||||
|
||||
private fun renderPhoto(context: Context, avatar: Avatar.Photo, onAvatarRendered: (Media) -> Unit) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val blob = BlobProvider.getInstance()
|
||||
val blob = AppDependencies.blobs
|
||||
.forData(AvatarPickerStorage.read(context, PartAuthority.getAvatarPickerFilename(avatar.uri)), avatar.size)
|
||||
.createForSingleSessionOnDisk(context)
|
||||
|
||||
@@ -124,7 +124,7 @@ object AvatarRenderer {
|
||||
|
||||
val bytes = outStream.toByteArray()
|
||||
val inStream = ByteArrayInputStream(bytes)
|
||||
val uri = BlobProvider.getInstance().forData(inStream, bytes.size.toLong()).createForSingleSessionOnDisk(context)
|
||||
val uri = AppDependencies.blobs.forData(inStream, bytes.size.toLong()).createForSingleSessionOnDisk(context)
|
||||
|
||||
onAvatarRendered(createMedia(uri, bytes.size.toLong()))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.avatar.AvatarBundler
|
||||
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
|
||||
|
||||
class PhotoEditorFragment : Fragment(R.layout.avatar_photo_editor_fragment), ImageEditorFragment.Controller {
|
||||
@@ -39,15 +39,15 @@ class PhotoEditorFragment : Fragment(R.layout.avatar_photo_editor_fragment), Ima
|
||||
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val editedImageUri = imageEditorFragment.renderToSingleUseBlob()
|
||||
val size = BlobProvider.getFileSize(editedImageUri) ?: 0
|
||||
val inputStream = BlobProvider.getInstance().getStream(applicationContext, editedImageUri)
|
||||
val size = AppDependencies.blobs.getFileSize(editedImageUri) ?: 0
|
||||
val inputStream = AppDependencies.blobs.getStream(applicationContext, editedImageUri)
|
||||
val onDiskUri = AvatarPickerStorage.save(applicationContext, inputStream)
|
||||
val photo = AvatarBundler.extractPhoto(args.photoAvatar)
|
||||
val database = SignalDatabase.avatarPicker
|
||||
val newPhoto = photo.copy(uri = onDiskUri, size = size)
|
||||
|
||||
database.update(newPhoto)
|
||||
BlobProvider.getInstance().delete(requireContext(), photo.uri)
|
||||
AppDependencies.blobs.delete(requireContext(), photo.uri)
|
||||
|
||||
ThreadUtil.runOnMain {
|
||||
setFragmentResult(REQUEST_KEY_EDIT, AvatarBundler.bundlePhoto(newPhoto))
|
||||
|
||||
+3
-3
@@ -16,9 +16,9 @@ import org.thoughtcrime.securesms.avatar.AvatarRenderer
|
||||
import org.thoughtcrime.securesms.avatar.Avatars
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.NameUtil
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails
|
||||
@@ -36,7 +36,7 @@ class AvatarPickerRepository(context: Context) {
|
||||
try {
|
||||
val bytes = StreamUtil.readFully(details.stream)
|
||||
Avatar.Photo(
|
||||
BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory(),
|
||||
AppDependencies.blobs.forData(bytes).createForSingleSessionInMemory(),
|
||||
details.length,
|
||||
Avatar.DatabaseId.DoNotPersist
|
||||
)
|
||||
@@ -56,7 +56,7 @@ class AvatarPickerRepository(context: Context) {
|
||||
try {
|
||||
val bytes = AvatarHelper.getAvatarBytes(applicationContext, recipient.id)
|
||||
Avatar.Photo(
|
||||
BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory(),
|
||||
AppDependencies.blobs.forData(bytes).createForSingleSessionInMemory(),
|
||||
AvatarHelper.getAvatarLength(applicationContext, recipient.id),
|
||||
Avatar.DatabaseId.DoNotPersist
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.KeyStoreHelper;
|
||||
import org.signal.core.util.crypto.KeyStoreHelper;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,9 +23,9 @@ import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.backup.proto.KeyValue;
|
||||
import org.thoughtcrime.securesms.backup.proto.SharedPreference;
|
||||
import org.thoughtcrime.securesms.backup.proto.SqlStatement;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.BackupMediaSnapshotTable;
|
||||
import org.thoughtcrime.securesms.database.EmojiSearchTable;
|
||||
|
||||
@@ -24,8 +24,8 @@ import org.thoughtcrime.securesms.backup.proto.KeyValue;
|
||||
import org.thoughtcrime.securesms.backup.proto.SharedPreference;
|
||||
import org.thoughtcrime.securesms.backup.proto.SqlStatement;
|
||||
import org.thoughtcrime.securesms.backup.proto.Sticker;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.EmojiSearchTable;
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.signal.core.util.bytes
|
||||
import org.signal.core.util.concurrent.LimitedWorker
|
||||
import org.signal.core.util.concurrent.SignalDispatchers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider
|
||||
import org.signal.core.util.decodeOrNull
|
||||
import org.signal.core.util.forceForeignKeyConstraintsEnabled
|
||||
import org.signal.core.util.fullWalCheckpoint
|
||||
@@ -93,7 +94,7 @@ import org.thoughtcrime.securesms.backup.v2.ui.BackupAlert
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.BackupMediaSnapshotTable.ArchiveMediaItem
|
||||
@@ -140,7 +141,6 @@ import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogRepository
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.BackupMediaRestoreService
|
||||
@@ -717,7 +717,7 @@ object BackupRepository {
|
||||
SignalDatabase(
|
||||
context = context,
|
||||
databaseSecret = DatabaseSecretProvider.getOrCreateDatabaseSecret(context),
|
||||
attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore).getOrCreateAttachmentSecret(),
|
||||
name = "$baseName.db"
|
||||
)
|
||||
}
|
||||
@@ -2235,7 +2235,7 @@ object BackupRepository {
|
||||
}
|
||||
|
||||
Log.i(TAG, "[remoteRestore] Downloading backup")
|
||||
val tempBackupFile = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
val tempBackupFile = AppDependencies.blobs.forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
when (val result = downloadBackupFile(tempBackupFile, progressListener)) {
|
||||
is NetworkResult.Success -> Log.i(TAG, "[remoteRestore] Download successful")
|
||||
else -> {
|
||||
@@ -2355,7 +2355,7 @@ object BackupRepository {
|
||||
}
|
||||
|
||||
Log.i(TAG, "[restoreLinkAndSyncBackup] Downloading backup")
|
||||
val tempBackupFile = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
val tempBackupFile = AppDependencies.blobs.forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
when (val result = AppDependencies.signalServiceMessageReceiver.retrieveLinkAndSyncBackup(response.cdn, response.key, tempBackupFile, progressListener)) {
|
||||
is NetworkResult.Success -> Log.i(TAG, "[restoreLinkAndSyncBackup] Download successful")
|
||||
else -> {
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.JsonUtils
|
||||
import org.signal.network.NetworkResult
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
|
||||
class ExportAccountDataRepository {
|
||||
|
||||
@@ -44,7 +44,7 @@ class ExportAccountDataRepository {
|
||||
tree["text"].asText()
|
||||
}
|
||||
|
||||
val uri = BlobProvider.getInstance()
|
||||
val uri = AppDependencies.blobs
|
||||
.forData(dataStr.encodeToByteArray())
|
||||
.withMimeType(mimeType)
|
||||
.withFileName(fileName)
|
||||
|
||||
+2
-3
@@ -57,7 +57,6 @@ import org.thoughtcrime.securesms.jobs.LocalBackupJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.protos.LocalBackupCreationProgress
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
@@ -136,7 +135,7 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
||||
|
||||
fun validateBackup() {
|
||||
_state.value = _state.value.copy(statusMessage = "Exporting to a temporary file...")
|
||||
val tempFile = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
val tempFile = AppDependencies.blobs.forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
|
||||
disposables += Single
|
||||
.fromCallable {
|
||||
@@ -207,7 +206,7 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
||||
|
||||
SignalExecutors.BOUNDED_IO.execute {
|
||||
Log.d(TAG, "Downloading file...")
|
||||
val tempBackupFile = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
val tempBackupFile = AppDependencies.blobs.forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
|
||||
when (val result = BackupRepository.downloadBackupFile(tempBackupFile)) {
|
||||
is NetworkResult.Success -> Log.i(TAG, "Download successful")
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@ import kotlinx.coroutines.withContext
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentReceiptRecord
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.Locale
|
||||
@@ -75,7 +75,7 @@ object ReceiptImageRenderer {
|
||||
val bitmap = view.drawToBitmap()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream)
|
||||
|
||||
BlobProvider.getInstance()
|
||||
AppDependencies.blobs
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType("image/png")
|
||||
.withFileName("Signal-Donation-Receipt.png")
|
||||
|
||||
+2
-2
@@ -75,7 +75,7 @@ import org.thoughtcrime.securesms.components.settings.app.usernamelinks.QrCodeDa
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.QrCodeState
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.UsernameLinkSettingsState.ActiveTab
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.UUID
|
||||
@@ -405,7 +405,7 @@ private fun shareQrBadge(activity: Activity, badge: Bitmap?) {
|
||||
badge.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
|
||||
byteArrayOutputStream.flush()
|
||||
val bytes = byteArrayOutputStream.toByteArray()
|
||||
val shareUri = BlobProvider.getInstance()
|
||||
val shareUri = AppDependencies.blobs
|
||||
.forData(bytes)
|
||||
.withMimeType("image/png")
|
||||
.withFileName("SignalUsernameQr.png")
|
||||
|
||||
+1
-2
@@ -39,7 +39,6 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
@@ -88,7 +87,7 @@ class InternalConversationSettingsFragment : ComposeFragment(), InternalConversa
|
||||
)
|
||||
val stream = BitmapUtil.toCompressedJpeg(bitmap)
|
||||
val bytes = stream.readBytes()
|
||||
val uri = BlobProvider.getInstance().forData(bytes).createForSingleSessionOnDisk(requireContext())
|
||||
val uri = AppDependencies.blobs.forData(bytes).createForSingleSessionOnDisk(requireContext())
|
||||
return UriAttachment(
|
||||
uri = uri,
|
||||
contentType = MediaUtil.IMAGE_JPEG,
|
||||
|
||||
+3
-3
@@ -18,8 +18,8 @@ import org.thoughtcrime.securesms.contactshare.Contact.Email;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact.Name;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact.Phone;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.SignalE164Util;
|
||||
|
||||
@@ -98,8 +98,8 @@ public class SharedContactRepository {
|
||||
Log.w(TAG, "Failed to parse the vcard.", e);
|
||||
}
|
||||
|
||||
if (BlobProvider.AUTHORITY.equals(uri.getAuthority())) {
|
||||
BlobProvider.getInstance().delete(context, uri);
|
||||
if (AppDependencies.getBlobs().getAuthority().equals(uri.getAuthority())) {
|
||||
AppDependencies.getBlobs().delete(context, uri);
|
||||
}
|
||||
|
||||
return contact;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.drafts
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.drafts
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import java.io.File
|
||||
|
||||
object DraftBlobs {
|
||||
|
||||
private val TAG = Log.tag(DraftBlobs::class)
|
||||
|
||||
fun deleteOrphanedDraftFiles(directory: File) {
|
||||
val files = directory.listFiles()
|
||||
|
||||
if (files == null || files.size == 0) {
|
||||
Log.d(TAG, "No attachment drafts exist. Skipping.")
|
||||
return
|
||||
}
|
||||
|
||||
val draftDatabase = drafts
|
||||
val voiceNoteDrafts = draftDatabase.getAllVoiceNoteDrafts()
|
||||
|
||||
val draftFileNames = voiceNoteDrafts
|
||||
.asSequence()
|
||||
.map { VoiceNoteDraft.fromDraft(it) }
|
||||
.map(VoiceNoteDraft::uri)
|
||||
.mapNotNull { AppDependencies.blobs.buildFileName(it) }
|
||||
.toList()
|
||||
|
||||
for (file in files) {
|
||||
if (!draftFileNames.contains(file.getName())) {
|
||||
if (file.delete()) {
|
||||
Log.d(TAG, "Deleted orphaned attachment draft: " + file.getName())
|
||||
} else {
|
||||
Log.d(TAG, "Failed to delete orphaned attachment draft: " + file.getName())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.mms.QuoteId
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.mms.SlideFactory
|
||||
import org.thoughtcrime.securesms.mms.StickerSlide
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor
|
||||
@@ -161,7 +160,7 @@ class DraftRepository(
|
||||
fun deleteVoiceNoteDraftData(draft: DraftTable.Draft?) {
|
||||
if (draft != null) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
BlobProvider.getInstance().delete(context, Uri.parse(draft.value).buildUpon().clearQuery().build())
|
||||
AppDependencies.blobs.delete(context, Uri.parse(draft.value).buildUpon().clearQuery().build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -326,7 +326,6 @@ import org.thoughtcrime.securesms.polls.PollOption
|
||||
import org.thoughtcrime.securesms.polls.PollRecord
|
||||
import org.thoughtcrime.securesms.profiles.manage.EditProfileActivity
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.ratelimit.RecaptchaRequiredEvent
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment
|
||||
@@ -1013,7 +1012,7 @@ class ConversationFragment :
|
||||
override fun onGifSelectSuccess(blobUri: Uri, width: Int, height: Int) {
|
||||
setMedia(
|
||||
uri = blobUri,
|
||||
mediaType = SlideFactory.MediaType.from(BlobProvider.getMimeType(blobUri))!!,
|
||||
mediaType = SlideFactory.MediaType.from(AppDependencies.blobs.getMimeType(blobUri))!!,
|
||||
width = width,
|
||||
height = height,
|
||||
videoGif = true
|
||||
|
||||
+3
-4
@@ -86,7 +86,6 @@ import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.polls.Poll
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewRecipient
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil
|
||||
@@ -681,7 +680,7 @@ class ConversationRepository(
|
||||
val thumbnailUri = thumbnailSlide.uri ?: return@fromCallable null
|
||||
|
||||
val inputStream = PartAuthority.getAttachmentStream(applicationContext, thumbnailUri)
|
||||
val tempUri = BlobProvider.getInstance().forData(inputStream, thumbnailSlide.fileSize)
|
||||
val tempUri = AppDependencies.blobs.forData(inputStream, thumbnailSlide.fileSize)
|
||||
.withMimeType(thumbnailSlide.contentType)
|
||||
.createForSingleSessionOnDisk(applicationContext)
|
||||
|
||||
@@ -826,9 +825,9 @@ class ConversationRepository(
|
||||
SignalExecutors.BOUNDED_IO.execute {
|
||||
slides
|
||||
.mapNotNull(Slide::getUri)
|
||||
.filter(BlobProvider::isAuthority)
|
||||
.filter { AppDependencies.blobs.isAuthority(it) }
|
||||
.forEach {
|
||||
BlobProvider.getInstance().delete(applicationContext, it)
|
||||
AppDependencies.blobs.delete(applicationContext, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.crypto
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.crypto.AttachmentSecretStore
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
object AppAttachmentSecretStore : AttachmentSecretStore {
|
||||
override fun getAttachmentUnencryptedSecret(context: Context): String? {
|
||||
return TextSecurePreferences.getAttachmentUnencryptedSecret(context)
|
||||
}
|
||||
|
||||
override fun getAttachmentEncryptedSecret(context: Context): String? {
|
||||
return TextSecurePreferences.getAttachmentEncryptedSecret(context)
|
||||
}
|
||||
|
||||
override fun setAttachmentEncryptedSecret(context: Context, secret: String) {
|
||||
TextSecurePreferences.setAttachmentEncryptedSecret(context, secret)
|
||||
}
|
||||
|
||||
override fun setAttachmentUnencryptedSecret(context: Context, secret: String?) {
|
||||
TextSecurePreferences.setAttachmentUnencryptedSecret(context, secret)
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import org.signal.core.util.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Encapsulates the key material used to encrypt attachments on disk.
|
||||
*
|
||||
* There are two logical pieces of material, a deprecated set of keys used to encrypt
|
||||
* legacy attachments, and a key that is used to encrypt attachments going forward.
|
||||
*/
|
||||
public class AttachmentSecret {
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] classicCipherKey;
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] classicMacKey;
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] modernKey;
|
||||
|
||||
public AttachmentSecret(byte[] classicCipherKey, byte[] classicMacKey, byte[] modernKey)
|
||||
{
|
||||
this.classicCipherKey = classicCipherKey;
|
||||
this.classicMacKey = classicMacKey;
|
||||
this.modernKey = modernKey;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public AttachmentSecret() {
|
||||
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
byte[] getClassicCipherKey() {
|
||||
return classicCipherKey;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
byte[] getClassicMacKey() {
|
||||
return classicMacKey;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public byte[] getModernKey() {
|
||||
return modernKey;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
void setClassicCipherKey(byte[] classicCipherKey) {
|
||||
this.classicCipherKey = classicCipherKey;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
void setClassicMacKey(byte[] classicMacKey) {
|
||||
this.classicMacKey = classicMacKey;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
try {
|
||||
return JsonUtils.toJson(this);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
static AttachmentSecret fromString(@NonNull String value) {
|
||||
try {
|
||||
return JsonUtils.fromJson(value, AttachmentSecret.class);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
|
||||
@Override
|
||||
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(Base64.encodeToString(value, Base64.NO_WRAP | Base64.NO_PADDING));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* A provider that is responsible for creating or retrieving the AttachmentSecret model.
|
||||
*
|
||||
* On modern Android, the serialized secrets are themselves encrypted using a key that lives
|
||||
* in the system KeyStore, for whatever that is worth.
|
||||
*/
|
||||
public class AttachmentSecretProvider {
|
||||
|
||||
private static AttachmentSecretProvider provider;
|
||||
|
||||
public static synchronized AttachmentSecretProvider getInstance(@NonNull Context context) {
|
||||
if (provider == null) provider = new AttachmentSecretProvider(context.getApplicationContext());
|
||||
return provider;
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
|
||||
private AttachmentSecret attachmentSecret;
|
||||
|
||||
private AttachmentSecretProvider(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
public synchronized AttachmentSecret getOrCreateAttachmentSecret() {
|
||||
if (attachmentSecret != null) return attachmentSecret;
|
||||
|
||||
String unencryptedSecret = TextSecurePreferences.getAttachmentUnencryptedSecret(context);
|
||||
String encryptedSecret = TextSecurePreferences.getAttachmentEncryptedSecret(context);
|
||||
|
||||
if (unencryptedSecret != null) attachmentSecret = getUnencryptedAttachmentSecret(context, unencryptedSecret);
|
||||
else if (encryptedSecret != null) attachmentSecret = getEncryptedAttachmentSecret(encryptedSecret);
|
||||
else attachmentSecret = createAndStoreAttachmentSecret(context);
|
||||
|
||||
return attachmentSecret;
|
||||
}
|
||||
|
||||
public synchronized AttachmentSecret setClassicKey(@NonNull Context context, @NonNull byte[] classicCipherKey, @NonNull byte[] classicMacKey) {
|
||||
AttachmentSecret currentSecret = getOrCreateAttachmentSecret();
|
||||
currentSecret.setClassicCipherKey(classicCipherKey);
|
||||
currentSecret.setClassicMacKey(classicMacKey);
|
||||
|
||||
storeAttachmentSecret(context, attachmentSecret);
|
||||
|
||||
return attachmentSecret;
|
||||
}
|
||||
|
||||
private AttachmentSecret getUnencryptedAttachmentSecret(@NonNull Context context, @NonNull String unencryptedSecret)
|
||||
{
|
||||
AttachmentSecret attachmentSecret = AttachmentSecret.fromString(unencryptedSecret);
|
||||
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes());
|
||||
|
||||
TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize());
|
||||
TextSecurePreferences.setAttachmentUnencryptedSecret(context, null);
|
||||
|
||||
return attachmentSecret;
|
||||
}
|
||||
|
||||
private AttachmentSecret getEncryptedAttachmentSecret(@NonNull String serializedEncryptedSecret) {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
|
||||
return AttachmentSecret.fromString(new String(KeyStoreHelper.unseal(encryptedSecret)));
|
||||
}
|
||||
|
||||
private AttachmentSecret createAndStoreAttachmentSecret(@NonNull Context context) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
|
||||
AttachmentSecret attachmentSecret = new AttachmentSecret(null, null, secret);
|
||||
storeAttachmentSecret(context, attachmentSecret);
|
||||
|
||||
return attachmentSecret;
|
||||
}
|
||||
|
||||
private void storeAttachmentSecret(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret) {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes());
|
||||
TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize());
|
||||
}
|
||||
}
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.util.stream.LimitedInputStream;
|
||||
import org.signal.core.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ClassicDecryptingPartInputStream {
|
||||
|
||||
private static final String TAG = Log.tag(ClassicDecryptingPartInputStream.class);
|
||||
|
||||
private static final int IV_LENGTH = 16;
|
||||
private static final int MAC_LENGTH = 20;
|
||||
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
if (file.length() <= IV_LENGTH + MAC_LENGTH) {
|
||||
throw new IOException("File too short");
|
||||
}
|
||||
|
||||
verifyMac(attachmentSecret, file);
|
||||
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
byte[] ivBytes = new byte[IV_LENGTH];
|
||||
readFully(fileStream, ivBytes);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(attachmentSecret.getClassicCipherKey(), "AES"), iv);
|
||||
|
||||
return new CipherInputStreamWrapper(new LimitedInputStream(fileStream, file.length() - MAC_LENGTH - IV_LENGTH), cipher);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyMac(AttachmentSecret attachmentSecret, File file) throws IOException {
|
||||
Mac mac = initializeMac(new SecretKeySpec(attachmentSecret.getClassicMacKey(), "HmacSHA1"));
|
||||
FileInputStream macStream = new FileInputStream(file);
|
||||
InputStream dataStream = new LimitedInputStream(new FileInputStream(file), file.length() - MAC_LENGTH);
|
||||
byte[] theirMac = new byte[MAC_LENGTH];
|
||||
|
||||
if (macStream.skip(file.length() - MAC_LENGTH) != file.length() - MAC_LENGTH) {
|
||||
throw new IOException("Unable to seek");
|
||||
}
|
||||
|
||||
readFully(macStream, theirMac);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
while ((read = dataStream.read(buffer)) != -1) {
|
||||
mac.update(buffer, 0, read);
|
||||
}
|
||||
|
||||
byte[] ourMac = mac.doFinal();
|
||||
|
||||
if (!MessageDigest.isEqual(ourMac, theirMac)) {
|
||||
throw new IOException("Bad MAC");
|
||||
}
|
||||
|
||||
macStream.close();
|
||||
dataStream.close();
|
||||
}
|
||||
|
||||
private static Mac initializeMac(SecretKeySpec key) {
|
||||
try {
|
||||
Mac hmac = Mac.getInstance("HmacSHA1");
|
||||
hmac.init(key);
|
||||
|
||||
return hmac;
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void readFully(InputStream in, byte[] buffer) throws IOException {
|
||||
int offset = 0;
|
||||
|
||||
for (;;) {
|
||||
int read = in.read(buffer, offset, buffer.length-offset);
|
||||
|
||||
if (read + offset < buffer.length) offset += read;
|
||||
else return;
|
||||
}
|
||||
}
|
||||
|
||||
// Note (4/3/17) -- Older versions of Android have a busted OpenSSL provider that
|
||||
// throws a RuntimeException on a BadPaddingException, so we have to catch
|
||||
// that here in case someone calls close() before reaching the end of the
|
||||
// stream (since close() implicitly calls doFinal())
|
||||
//
|
||||
// See Signal-Android Issue #6477
|
||||
// Android: https://android-review.googlesource.com/#/c/65321/
|
||||
private static class CipherInputStreamWrapper extends CipherInputStream {
|
||||
|
||||
CipherInputStreamWrapper(InputStream is, Cipher c) {
|
||||
super(is, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long skipAmount)
|
||||
throws IOException
|
||||
{
|
||||
long remaining = skipAmount;
|
||||
|
||||
if (skipAmount <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte[] skipBuffer = new byte[4092];
|
||||
|
||||
while (remaining > 0) {
|
||||
int read = super.read(skipBuffer, 0, Util.toIntExact(Math.min(skipBuffer.length, remaining)));
|
||||
|
||||
if (read < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
remaining -= read;
|
||||
}
|
||||
|
||||
return skipAmount - remaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.crypto.KeyStoreHelper;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.util.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.UnrecoverableEntryException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
public final class KeyStoreHelper {
|
||||
|
||||
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
|
||||
private static final String KEY_ALIAS = "SignalSecret";
|
||||
private static final Executor executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static SealedData seal(@NonNull byte[] input) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<SealedData> result = new AtomicReference<>();
|
||||
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
SecretKey secretKey = getOrCreateKeyStoreEntry();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
|
||||
byte[] iv = cipher.getIV();
|
||||
byte[] data = cipher.doFinal(input);
|
||||
|
||||
result.set(new SealedData(iv, data));
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
return result.get();
|
||||
}
|
||||
|
||||
public static byte[] unseal(@NonNull SealedData sealedData) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<byte[]> result = new AtomicReference<>();
|
||||
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
SecretKey secretKey = getKeyStoreEntry();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv));
|
||||
|
||||
result.set(cipher.doFinal(sealedData.data));
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
return result.get();
|
||||
}
|
||||
|
||||
@RequiresApi(23)
|
||||
private static SecretKey getOrCreateKeyStoreEntry() {
|
||||
if (hasKeyStoreEntry()) return getKeyStoreEntry();
|
||||
else return createKeyStoreEntry();
|
||||
}
|
||||
|
||||
@RequiresApi(23)
|
||||
private static SecretKey createKeyStoreEntry() {
|
||||
try {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
|
||||
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.build();
|
||||
|
||||
keyGenerator.init(keyGenParameterSpec);
|
||||
|
||||
return keyGenerator.generateKey();
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(23)
|
||||
private static SecretKey getKeyStoreEntry() {
|
||||
KeyStore keyStore = getKeyStore();
|
||||
|
||||
try {
|
||||
// Attempt 1
|
||||
return getSecretKey(keyStore);
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
try {
|
||||
// Attempt 2
|
||||
return getSecretKey(keyStore);
|
||||
} catch (UnrecoverableKeyException e2) {
|
||||
throw new AssertionError(e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SecretKey getSecretKey(KeyStore keyStore) throws UnrecoverableKeyException {
|
||||
try {
|
||||
KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
|
||||
return entry.getSecretKey();
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
throw e;
|
||||
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyStore getKeyStore() {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
|
||||
keyStore.load(null);
|
||||
return keyStore;
|
||||
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(23)
|
||||
private static boolean hasKeyStoreEntry() {
|
||||
try {
|
||||
KeyStore ks = KeyStore.getInstance(ANDROID_KEY_STORE);
|
||||
ks.load(null);
|
||||
|
||||
return ks.containsAlias(KEY_ALIAS) && ks.entryInstanceOf(KEY_ALIAS, KeyStore.SecretKeyEntry.class);
|
||||
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SealedData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(SealedData.class);
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] iv;
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] data;
|
||||
|
||||
SealedData(@NonNull byte[] iv, @NonNull byte[] data) {
|
||||
this.iv = iv;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public SealedData() {}
|
||||
|
||||
public String serialize() {
|
||||
try {
|
||||
return JsonUtils.toJson(this);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SealedData fromString(@NonNull String value) {
|
||||
try {
|
||||
return JsonUtils.fromJson(value, SealedData.class);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
|
||||
@Override
|
||||
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(Base64.encodeToString(value, Base64.NO_WRAP | Base64.NO_PADDING));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.Conversions;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ModernDecryptingPartInputStream {
|
||||
|
||||
public static final String PREMATURE_END_ERROR_MESSAGE = "Prematurely reached end of stream!";
|
||||
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull File file, long offset)
|
||||
throws IOException
|
||||
{
|
||||
return createFor(attachmentSecret, random, new FileInputStream(file), offset);
|
||||
}
|
||||
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file, long offset)
|
||||
throws IOException
|
||||
{
|
||||
FileInputStream inputStream = new FileInputStream(file);
|
||||
byte[] random = new byte[32];
|
||||
|
||||
readFully(inputStream, random);
|
||||
|
||||
return createFor(attachmentSecret, random, inputStream, offset);
|
||||
}
|
||||
|
||||
private static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull InputStream inputStream, long offset) throws IOException {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256"));
|
||||
|
||||
byte[] iv = new byte[16];
|
||||
int remainder = (int) (offset % 16);
|
||||
Conversions.longTo4ByteArray(iv, 12, offset / 16);
|
||||
|
||||
byte[] key = mac.doFinal(random);
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
|
||||
|
||||
long skipped = inputStream.skip(offset - remainder);
|
||||
|
||||
if (skipped != offset - remainder) {
|
||||
throw new IOException("Skip failed: " + skipped + " vs " + (offset - remainder));
|
||||
}
|
||||
|
||||
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
|
||||
byte[] remainderBuffer = new byte[remainder];
|
||||
|
||||
readFully(cipherInputStream, remainderBuffer);
|
||||
|
||||
return cipherInputStream;
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void readFully(InputStream in, byte[] buffer) throws IOException {
|
||||
int offset = 0;
|
||||
|
||||
for (;;) {
|
||||
int read = in.read(buffer, offset, buffer.length-offset);
|
||||
|
||||
if (read == -1) throw new IOException(PREMATURE_END_ERROR_MESSAGE);
|
||||
else if (read + offset < buffer.length) offset += read;
|
||||
else return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Constructs an OutputStream that encrypts data written to it with the AttachmentSecret provided.
|
||||
*
|
||||
* The on-disk format is very simple, and intentionally no longer includes authentication.
|
||||
*/
|
||||
public class ModernEncryptingPartOutputStream {
|
||||
|
||||
public static Pair<byte[], OutputStream> createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file, boolean inline)
|
||||
throws IOException
|
||||
{
|
||||
byte[] random = new byte[32];
|
||||
new SecureRandom().nextBytes(random);
|
||||
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256"));
|
||||
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
byte[] iv = new byte[16];
|
||||
byte[] key = mac.doFinal(random);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
|
||||
|
||||
if (inline) {
|
||||
fileOutputStream.write(random);
|
||||
}
|
||||
|
||||
return new Pair<>(random, new CipherOutputStream(fileOutputStream, cipher));
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static long getPlaintextLength(long cipherTextLength) {
|
||||
return cipherTextLength - 32;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,10 @@ import org.signal.core.util.UuidUtil
|
||||
import org.signal.core.util.bitmaps.BitmapDecodingException
|
||||
import org.signal.core.util.copyTo
|
||||
import org.signal.core.util.count
|
||||
import org.signal.core.util.crypto.AttachmentSecret
|
||||
import org.signal.core.util.crypto.ClassicDecryptingPartInputStream
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream
|
||||
import org.signal.core.util.crypto.ModernEncryptingPartOutputStream
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.deleteAll
|
||||
import org.signal.core.util.drain
|
||||
@@ -79,10 +83,6 @@ import org.thoughtcrime.securesms.attachments.WallpaperAttachment
|
||||
import org.thoughtcrime.securesms.audio.AudioHash
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchivedMediaObject
|
||||
import org.thoughtcrime.securesms.backup.v2.exporters.ChatItemArchiveExporter
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.Companion.DATA_FILE
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.Companion.DATA_HASH_END
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.Companion.PREUPLOAD_MESSAGE_ID
|
||||
|
||||
@@ -6,9 +6,9 @@ import androidx.annotation.VisibleForTesting
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import net.zetetic.database.sqlcipher.SQLiteOpenHelper
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.crypto.AttachmentSecret
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret
|
||||
import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations
|
||||
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase
|
||||
|
||||
@@ -5,6 +5,9 @@ import android.database.Cursor
|
||||
import androidx.core.content.contentValuesOf
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.crypto.AttachmentSecret
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream
|
||||
import org.signal.core.util.crypto.ModernEncryptingPartOutputStream
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.exists
|
||||
import org.signal.core.util.forEach
|
||||
@@ -25,9 +28,6 @@ import org.signal.core.util.toInt
|
||||
import org.signal.core.util.update
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.signal.glide.decryptableuri.DecryptableUri
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream
|
||||
import org.thoughtcrime.securesms.database.model.IncomingSticker
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackId
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.signal.core.util.CoreUtilDependencies
|
||||
import org.signal.core.util.billing.BillingApi
|
||||
import org.signal.core.util.concurrent.DeadlockDetector
|
||||
import org.signal.core.util.concurrent.LatestValueObservable
|
||||
import org.signal.core.util.contentproviders.BlobProvider
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.core.util.resettableLazy
|
||||
import org.signal.glide.SignalGlideDependencies
|
||||
@@ -261,6 +262,11 @@ object AppDependencies {
|
||||
provider.provideBillingApi()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val blobs: BlobProvider by lazy {
|
||||
provider.provideBlobs()
|
||||
}
|
||||
|
||||
private val _webSocketObserver: BehaviorSubject<WebSocketConnectionState> = BehaviorSubject.create()
|
||||
|
||||
/**
|
||||
@@ -511,5 +517,6 @@ object AppDependencies {
|
||||
fun provideDonationsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket): DonationsApi
|
||||
fun provideSvrBApi(libSignalNetwork: Network): SvrBApi
|
||||
fun provideKeyTransparencyApi(unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket): KeyTransparencyApi
|
||||
fun provideBlobs(): BlobProvider
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -21,6 +21,7 @@ import org.signal.core.util.UptimeSleepTimer;
|
||||
import org.signal.core.util.billing.BillingApi;
|
||||
import org.signal.core.util.concurrent.DeadlockDetector;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.contentproviders.BlobProvider;
|
||||
import org.signal.libsignal.net.Network;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.zkgroup.GenericServerPublicParams;
|
||||
@@ -46,6 +47,7 @@ import org.signal.network.service.MessageService;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalBaseIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalIdentityKeyStore;
|
||||
@@ -635,6 +637,10 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
|
||||
return new KeyTransparencyApi(unauthWebSocket);
|
||||
}
|
||||
|
||||
@Override public @NotNull BlobProvider provideBlobs() {
|
||||
return new BlobProvider(context, AppAttachmentSecretStore.INSTANCE);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class DynamicCredentialsProvider implements CredentialsProvider {
|
||||
|
||||
|
||||
+3
-3
@@ -9,6 +9,7 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.devicetransfer.NewDeviceRestoreStatus;
|
||||
import org.signal.devicetransfer.ServerTask;
|
||||
@@ -16,12 +17,11 @@ import org.thoughtcrime.securesms.AppInitialization;
|
||||
import org.thoughtcrime.securesms.backup.BackupEvent;
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupImporter;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.DataRestoreConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -52,7 +52,7 @@ public final class NewDeviceServerTask implements ServerTask {
|
||||
|
||||
BackupPassphrase.set(context, passphrase);
|
||||
FullBackupImporter.importFile(context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret(),
|
||||
database,
|
||||
inputStream,
|
||||
passphrase,
|
||||
|
||||
+3
-3
@@ -7,15 +7,15 @@ import androidx.annotation.NonNull;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.devicetransfer.ClientTask;
|
||||
import org.thoughtcrime.securesms.backup.BackupEvent;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupExporter;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -43,7 +43,7 @@ final class OldDeviceClientTask implements ClientTask {
|
||||
String passphrase = SignalStore.account().getAccountEntropyPool().getValue();
|
||||
|
||||
FullBackupExporter.transfer(context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret(),
|
||||
SignalDatabase.getBackupDatabase(),
|
||||
outputStream,
|
||||
passphrase);
|
||||
|
||||
@@ -12,10 +12,11 @@ import okio.HashingSink
|
||||
import okio.blackholeSink
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream
|
||||
import org.signal.core.util.crypto.ModernEncryptingPartOutputStream
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
@@ -60,12 +61,12 @@ private fun Context.getJumboFile(versionUuid: UUID): File = File(File(getEmojiDi
|
||||
private fun getFilesUri(name: String, format: String): Uri = PartAuthority.getEmojiUri(name)
|
||||
|
||||
private fun getOutputStream(context: Context, outputFile: File): OutputStream {
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context).orCreateAttachmentSecret
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore).orCreateAttachmentSecret
|
||||
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second
|
||||
}
|
||||
|
||||
private fun getInputStream(context: Context, inputFile: File): InputStream {
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context).orCreateAttachmentSecret
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore).orCreateAttachmentSecret
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, inputFile, 0)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.giph.model.GiphyImage;
|
||||
import org.thoughtcrime.securesms.net.ContentProxySelector;
|
||||
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
@@ -66,10 +65,10 @@ final class GiphyMp4Repository {
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (response.code() >= 200 && response.code() < 300) {
|
||||
return BlobProvider.getInstance()
|
||||
.forData(response.body().byteStream(), response.body().contentLength())
|
||||
.withMimeType(mime)
|
||||
.createForSingleSessionOnDisk(AppDependencies.getApplication());
|
||||
return AppDependencies.getBlobs()
|
||||
.forData(response.body().byteStream(), response.body().contentLength())
|
||||
.withMimeType(mime)
|
||||
.createForSingleSessionOnDisk(AppDependencies.getApplication());
|
||||
} else {
|
||||
throw new IOException("Unexpected response code: " + response.code());
|
||||
}
|
||||
|
||||
@@ -12,28 +12,27 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.conversation.MessageSendType;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Fragment;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4SaveResult;
|
||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ViewModel;
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView;
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity;
|
||||
import org.thoughtcrime.securesms.mms.SlideFactory;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class GiphyActivity extends PassphraseRequiredActivity implements KeyboardPageSearchView.Callbacks {
|
||||
|
||||
@@ -120,7 +119,7 @@ public class GiphyActivity extends PassphraseRequiredActivity implements Keyboar
|
||||
}
|
||||
|
||||
private void handleGiphyMp4SuccessfulResult(@NonNull GiphyMp4SaveResult.Success success) {
|
||||
SlideFactory.MediaType mediaType = Objects.requireNonNull(SlideFactory.MediaType.from(BlobProvider.getMimeType(success.getBlobUri())));
|
||||
SlideFactory.MediaType mediaType = Objects.requireNonNull(SlideFactory.MediaType.from(AppDependencies.getBlobs().getMimeType(success.getBlobUri())));
|
||||
String mimeType = MediaUtil.getMimeType(this, success.getBlobUri());
|
||||
if (mimeType == null) {
|
||||
mimeType = mediaType.toFallbackMimeType();
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import org.signal.core.util.logging.Log;
|
||||
import org.signal.glide.common.io.GlideStreamConfig;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.signal.core.util.contentproviders.BlobProvider;
|
||||
import org.signal.core.util.bitmaps.BitmapDecodingException;
|
||||
import org.signal.core.util.bitmaps.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
@@ -18,23 +18,26 @@ import com.bumptech.glide.load.resource.gif.StreamGifDecoder;
|
||||
|
||||
import org.signal.apng.ApngDecoder;
|
||||
import org.signal.blurhash.BlurHash;
|
||||
import org.signal.glide.load.resource.apng.decode.APNGDecoder;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.glide.blurhash.BlurHashModelLoader;
|
||||
import org.signal.glide.blurhash.BlurHashResourceDecoder;
|
||||
import org.signal.glide.common.io.InputStreamFactory;
|
||||
import org.signal.glide.decryptableuri.DecryptableUri;
|
||||
import org.signal.glide.decryptableuri.DecryptableUriStreamLoader;
|
||||
import org.signal.glide.load.resource.apng.decode.APNGDecoder;
|
||||
import org.thoughtcrime.securesms.badges.load.BadgeLoader;
|
||||
import org.thoughtcrime.securesms.badges.load.GiftBadgeModel;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoLoader;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngDrawableTranscoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngFrameDrawableTranscoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngInputStreamFactoryResourceDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedApngCacheDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ByteBufferApngDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedApngCacheDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedApngCacheEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedApngResourceEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedBitmapResourceEncoder;
|
||||
@@ -47,15 +50,13 @@ import org.thoughtcrime.securesms.glide.cache.StreamBitmapDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.StreamFactoryApngDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.StreamFactoryGifDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.WebpSanDecoder;
|
||||
import org.signal.glide.decryptableuri.DecryptableUri;
|
||||
import org.signal.glide.decryptableuri.DecryptableUriStreamLoader;
|
||||
import org.thoughtcrime.securesms.mms.RegisterGlideComponents;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
|
||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||
import org.thoughtcrime.securesms.util.ConversationShortcutPhoto;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@@ -69,7 +70,7 @@ public class SignalGlideComponents implements RegisterGlideComponents {
|
||||
|
||||
@Override
|
||||
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret();
|
||||
byte[] secret = attachmentSecret.getModernKey();
|
||||
|
||||
registry.prepend(File.class, File.class, UnitModelLoader.Factory.getInstance());
|
||||
|
||||
@@ -10,16 +10,22 @@ import androidx.annotation.WorkerThread;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.signal.core.models.media.TransformProperties;
|
||||
import org.signal.core.util.MemoryFileDescriptor.MemoryFileException;
|
||||
import org.signal.core.util.bitmaps.BitmapDecodingException;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream;
|
||||
import org.signal.core.util.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.glide.decryptableuri.DecryptableUri;
|
||||
import org.signal.mediasend.MediaConstraints;
|
||||
import org.signal.mediasend.SentMediaQuality;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.signal.core.models.media.TransformProperties;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
@@ -27,18 +33,13 @@ import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec;
|
||||
import org.signal.glide.decryptableuri.DecryptableUri;
|
||||
import org.signal.mediasend.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.MediaStream;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.signal.mediasend.SentMediaQuality;
|
||||
import org.thoughtcrime.securesms.mms.PushMediaConstraints;
|
||||
import org.thoughtcrime.securesms.service.AttachmentProgressService;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.signal.core.util.bitmaps.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.ImageCompressionUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.signal.core.util.MemoryFileDescriptor.MemoryFileException;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.video.StreamingTranscoder;
|
||||
import org.thoughtcrime.securesms.video.TranscoderOptions;
|
||||
@@ -263,7 +264,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
||||
|
||||
if (transcoder.isTranscodeRequired()) {
|
||||
Log.i(TAG, "Compressing with streaming muxer");
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret();
|
||||
|
||||
File file = AttachmentTable.newDataFile(context);
|
||||
file.deleteOnExit();
|
||||
|
||||
@@ -48,7 +48,6 @@ import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
@@ -463,9 +462,9 @@ class BackupMessagesJob private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
BlobProvider.getInstance().clearTemporaryBackupsDirectory(AppDependencies.application)
|
||||
AppDependencies.blobs.clearTemporaryBackupsDirectory(AppDependencies.application)
|
||||
|
||||
val tempBackupFile = BlobProvider.getInstance().forTemporaryBackup(AppDependencies.application)
|
||||
val tempBackupFile = AppDependencies.blobs.forTemporaryBackup(AppDependencies.application)
|
||||
|
||||
val outputStream = FileOutputStream(tempBackupFile)
|
||||
val backupKey = SignalStore.backup.messageBackupKey
|
||||
|
||||
@@ -7,11 +7,11 @@ import org.thoughtcrime.securesms.avatar.Avatar
|
||||
import org.thoughtcrime.securesms.avatar.AvatarRenderer
|
||||
import org.thoughtcrime.securesms.avatar.Avatars
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
@@ -92,7 +92,7 @@ class CreateReleaseChannelJob private constructor(parameters: Parameters) : Base
|
||||
Avatars.ColorPair(ContextCompat.getColor(context, R.color.notification_background_ultramarine), ContextCompat.getColor(context, R.color.core_white), "")
|
||||
),
|
||||
onAvatarRendered = { media ->
|
||||
AvatarHelper.setAvatar(context, id, BlobProvider.getInstance().getStream(context, media.uri))
|
||||
AvatarHelper.setAvatar(context, id, AppDependencies.blobs.getStream(context, media.uri))
|
||||
SignalDatabase.recipients.setProfileAvatar(id, "local")
|
||||
latch.countDown()
|
||||
},
|
||||
|
||||
@@ -3,10 +3,10 @@ package org.thoughtcrime.securesms.jobs
|
||||
import okhttp3.ResponseBody
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.AutoDownloadEmojiConstraint
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.s3.S3
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException
|
||||
import java.io.IOException
|
||||
@@ -44,7 +44,7 @@ class FetchRemoteMegaphoneImageJob(parameters: Parameters, private val uuid: Str
|
||||
S3.getObject(imageUrl).use { response ->
|
||||
val body: ResponseBody? = response.body
|
||||
if (body != null) {
|
||||
val uri = BlobProvider.getInstance()
|
||||
val uri = AppDependencies.blobs
|
||||
.forData(body.byteStream(), body.contentLength())
|
||||
.createForMultipleSessionsOnDisk(context)
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ import androidx.annotation.Nullable;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.signal.core.ui.util.StorageUtil;
|
||||
import org.signal.core.util.NoExternalStorageException;
|
||||
import org.signal.core.util.Stopwatch;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.backup.BackupEvent;
|
||||
@@ -17,18 +21,15 @@ import org.thoughtcrime.securesms.backup.BackupFileIOError;
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase;
|
||||
import org.thoughtcrime.securesms.backup.BackupVerifier;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupExporter;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
import org.thoughtcrime.securesms.service.NotificationController;
|
||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||
import org.signal.core.ui.util.StorageUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -143,7 +144,7 @@ public final class LocalBackupJob extends BaseJob {
|
||||
try {
|
||||
Stopwatch stopwatch = new Stopwatch("backup-export");
|
||||
BackupEvent finishedEvent = FullBackupExporter.export(context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret(),
|
||||
SignalDatabase.getBackupDatabase(),
|
||||
tempFile,
|
||||
backupPassword,
|
||||
|
||||
@@ -12,16 +12,17 @@ import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.Stopwatch;
|
||||
import org.signal.core.util.androidx.DocumentFileUtil;
|
||||
import org.signal.core.util.androidx.DocumentFileUtil.OperationResult;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.backup.BackupEvent;
|
||||
import org.thoughtcrime.securesms.backup.BackupFileIOError;
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase;
|
||||
import org.thoughtcrime.securesms.backup.BackupVerifier;
|
||||
import org.signal.core.util.androidx.DocumentFileUtil;
|
||||
import org.signal.core.util.androidx.DocumentFileUtil.OperationResult;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupExporter;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -130,7 +131,7 @@ public final class LocalBackupJobApi29 extends BaseJob {
|
||||
try {
|
||||
Stopwatch stopwatch = new Stopwatch("backup-export");
|
||||
BackupEvent finishedEvent = FullBackupExporter.export(context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret(),
|
||||
SignalDatabase.getBackupDatabase(),
|
||||
temporaryFile,
|
||||
backupPassword,
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream.IntegrityCheck
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
||||
@@ -58,7 +57,7 @@ class MultiDeviceContactSyncJob(parameters: Parameters, private val attachmentPo
|
||||
val contactAttachment: SignalServiceAttachmentPointer = AttachmentPointerUtil.createSignalAttachmentPointer(attachmentPointer)
|
||||
|
||||
try {
|
||||
val contactsFile: File = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(context)
|
||||
val contactsFile: File = AppDependencies.blobs.forNonAutoEncryptingSingleSessionOnDisk(context)
|
||||
AppDependencies.signalServiceMessageReceiver
|
||||
.retrieveAttachment(contactAttachment, contactsFile, MAX_ATTACHMENT_SIZE, IntegrityCheck.forEncryptedDigest(contactAttachment.digest.get()))
|
||||
.use(this::processContactFile)
|
||||
|
||||
+12
-16
@@ -10,15 +10,15 @@ import android.provider.ContactsContract;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.signal.core.util.AppForegroundObserver;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.signal.network.exceptions.PushNetworkException;
|
||||
import org.signal.network.service.CdnService;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.signal.network.service.CdnService;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
@@ -26,17 +26,13 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.SealedSenderConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.signal.core.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
|
||||
@@ -46,9 +42,9 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsO
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.signal.network.exceptions.PushNetworkException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -177,10 +173,10 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||
out.close();
|
||||
updateUri = writeDetails.getUri();
|
||||
|
||||
long length = BlobProvider.getInstance().calculateFileSize(context, updateUri);
|
||||
long length = AppDependencies.getBlobs().calculateFileSize(context, updateUri);
|
||||
|
||||
sendUpdate(AppDependencies.getSignalServiceMessageSender(),
|
||||
BlobProvider.getInstance().getStream(context, updateUri),
|
||||
AppDependencies.getBlobs().getStream(context, updateUri),
|
||||
length,
|
||||
false);
|
||||
|
||||
@@ -188,7 +184,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||
Log.w(TAG, e);
|
||||
} finally {
|
||||
if (updateUri != null) {
|
||||
BlobProvider.getInstance().delete(context, updateUri);
|
||||
AppDependencies.getBlobs().delete(context, updateUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,17 +249,17 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||
|
||||
updateUri = writeDetails.getUri();
|
||||
|
||||
long length = BlobProvider.getInstance().calculateFileSize(context, updateUri);
|
||||
long length = AppDependencies.getBlobs().calculateFileSize(context, updateUri);
|
||||
|
||||
sendUpdate(AppDependencies.getSignalServiceMessageSender(),
|
||||
BlobProvider.getInstance().getStream(context, updateUri),
|
||||
AppDependencies.getBlobs().getStream(context, updateUri),
|
||||
length,
|
||||
true);
|
||||
} catch(InterruptedException e) {
|
||||
Log.w(TAG, e);
|
||||
} finally {
|
||||
if (updateUri != null) {
|
||||
BlobProvider.getInstance().delete(context, updateUri);
|
||||
AppDependencies.getBlobs().delete(context, updateUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,7 +373,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||
private @NonNull WriteDetails createTempFile() throws IOException {
|
||||
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]);
|
||||
Future<Uri> futureUri = BlobProvider.getInstance()
|
||||
Future<Uri> futureUri = AppDependencies.getBlobs()
|
||||
.forData(inputStream, 0)
|
||||
.withFileName("multidevice-contact-update")
|
||||
.createForSingleSessionOnDiskAsync(context);
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
|
||||
import org.thoughtcrime.securesms.s3.S3
|
||||
@@ -361,7 +360,7 @@ class RetrieveRemoteAnnouncementsJob private constructor(private val force: Bool
|
||||
for ((uuid, megaphone) in megaphonesToDelete) {
|
||||
SignalDatabase.remoteMegaphones.clear(uuid)
|
||||
if (megaphone.imageUri != null) {
|
||||
BlobProvider.getInstance().delete(context, megaphone.imageUri)
|
||||
AppDependencies.blobs.delete(context, megaphone.imageUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.createAndUploadArchive
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher
|
||||
import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse
|
||||
import org.whispersystems.signalservice.api.link.TransferArchiveError
|
||||
@@ -281,7 +280,7 @@ object LinkDeviceRepository {
|
||||
fun createAndUploadArchive(ephemeralMessageBackupKey: MessageBackupKey, deviceId: Int, deviceRegistrationId: Int, cancellationSignal: () -> Boolean): LinkUploadArchiveResult {
|
||||
Log.d(TAG, "[createAndUploadArchive] Beginning process.")
|
||||
val stopwatch = Stopwatch("link-archive")
|
||||
val tempBackupFile = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
val tempBackupFile = AppDependencies.blobs.forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
val outputStream = FileOutputStream(tempBackupFile)
|
||||
|
||||
try {
|
||||
|
||||
@@ -12,8 +12,10 @@ import androidx.core.util.Consumer;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.signal.core.util.ByteUnit;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.core.util.Result;
|
||||
import org.signal.core.util.bitmaps.BitmapDecodingException;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
@@ -42,15 +44,12 @@ import org.thoughtcrime.securesms.net.LinkPreviewRedirectValidationInterceptor;
|
||||
import org.thoughtcrime.securesms.net.RequestController;
|
||||
import org.thoughtcrime.securesms.net.UserAgentInterceptor;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials;
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.ReadCallLinkResult;
|
||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
|
||||
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||
import org.signal.core.util.bitmaps.BitmapDecodingException;
|
||||
import org.signal.core.util.ByteUnit;
|
||||
import org.thoughtcrime.securesms.util.ImageCompressionUtil;
|
||||
import org.thoughtcrime.securesms.util.LinkUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
@@ -68,11 +67,10 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import kotlin.Pair;
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
@@ -467,7 +465,7 @@ public class LinkPreviewRepository {
|
||||
int height,
|
||||
@NonNull String contentType) {
|
||||
|
||||
Uri uri = BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory();
|
||||
Uri uri = AppDependencies.getBlobs().forData(bytes).createForSingleSessionInMemory();
|
||||
|
||||
return new UriAttachment(uri,
|
||||
contentType,
|
||||
|
||||
+16
-17
@@ -12,6 +12,7 @@ import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.signal.core.util.Stopwatch;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -21,10 +22,8 @@ import org.signal.debuglogsviewer.DebugLogsViewer;
|
||||
import org.thoughtcrime.securesms.database.LogDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.signal.core.util.Stopwatch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -39,8 +38,8 @@ import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
@@ -190,10 +189,10 @@ public class SubmitDebugLogRepository {
|
||||
try {
|
||||
|
||||
ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
|
||||
Future<Uri> futureUri = BlobProvider.getInstance()
|
||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||
.withMimeType("application/gzip")
|
||||
.createForSingleSessionOnDiskAsync(context);
|
||||
Future<Uri> futureUri = AppDependencies.getBlobs()
|
||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||
.withMimeType("application/gzip")
|
||||
.createForSingleSessionOnDiskAsync(context);
|
||||
|
||||
OutputStream gzipOutput = new GZIPOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
||||
|
||||
@@ -224,12 +223,12 @@ public class SubmitDebugLogRepository {
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return BlobProvider.getInstance().calculateFileSize(context, gzipUri);
|
||||
return AppDependencies.getBlobs().calculateFileSize(context, gzipUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(@NonNull BufferedSink sink) throws IOException {
|
||||
Source source = Okio.source(BlobProvider.getInstance().getStream(context, gzipUri));
|
||||
Source source = Okio.source(AppDependencies.getBlobs().getStream(context, gzipUri));
|
||||
sink.writeAll(source);
|
||||
}
|
||||
});
|
||||
@@ -237,7 +236,7 @@ public class SubmitDebugLogRepository {
|
||||
stopwatch.split("upload");
|
||||
stopwatch.stop(TAG);
|
||||
|
||||
BlobProvider.getInstance().delete(context, gzipUri);
|
||||
AppDependencies.getBlobs().delete(context, gzipUri);
|
||||
|
||||
return Optional.of(logUrl);
|
||||
} catch (IOException | RuntimeException | ExecutionException | InterruptedException e) {
|
||||
@@ -264,10 +263,10 @@ public class SubmitDebugLogRepository {
|
||||
Stopwatch stopwatch = new Stopwatch("log-upload");
|
||||
|
||||
ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
|
||||
Future<Uri> futureUri = BlobProvider.getInstance()
|
||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||
.withMimeType("application/gzip")
|
||||
.createForSingleSessionOnDiskAsync(context);
|
||||
Future<Uri> futureUri = AppDependencies.getBlobs()
|
||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||
.withMimeType("application/gzip")
|
||||
.createForSingleSessionOnDiskAsync(context);
|
||||
|
||||
OutputStream gzipOutput = new GZIPOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
||||
|
||||
@@ -297,12 +296,12 @@ public class SubmitDebugLogRepository {
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return BlobProvider.getInstance().calculateFileSize(context, gzipUri);
|
||||
return AppDependencies.getBlobs().calculateFileSize(context, gzipUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(@NonNull BufferedSink sink) throws IOException {
|
||||
Source source = Okio.source(BlobProvider.getInstance().getStream(context, gzipUri));
|
||||
Source source = Okio.source(AppDependencies.getBlobs().getStream(context, gzipUri));
|
||||
sink.writeAll(source);
|
||||
}
|
||||
});
|
||||
@@ -310,7 +309,7 @@ public class SubmitDebugLogRepository {
|
||||
stopwatch.split("upload");
|
||||
stopwatch.stop(TAG);
|
||||
|
||||
BlobProvider.getInstance().delete(context, gzipUri);
|
||||
AppDependencies.getBlobs().delete(context, gzipUri);
|
||||
|
||||
return Optional.of(logUrl);
|
||||
} catch (IOException | RuntimeException | ExecutionException | InterruptedException e) {
|
||||
|
||||
@@ -31,12 +31,12 @@ import com.google.android.gms.maps.SupportMapFragment;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.MapStyleOptions;
|
||||
|
||||
import org.signal.core.util.bitmaps.BitmapUtil;
|
||||
import org.signal.core.util.concurrent.ListenableFuture;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.location.SignalMapView;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.signal.core.util.bitmaps.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
@@ -196,7 +196,7 @@ public final class PlacePickerActivity extends AppCompatActivity {
|
||||
public void onSuccess(Bitmap result) {
|
||||
dismissibleDialog.dismiss();
|
||||
byte[] blob = BitmapUtil.toByteArray(result);
|
||||
Uri uri = BlobProvider.getInstance()
|
||||
Uri uri = AppDependencies.getBlobs()
|
||||
.forData(blob)
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionInMemory();
|
||||
|
||||
@@ -4,9 +4,10 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.attachments
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.mms.PartUriParser
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.video.MediaDataSourceProvider
|
||||
import org.thoughtcrime.securesms.video.interfaces.MediaInput
|
||||
import org.thoughtcrime.securesms.video.interfaces.MediaInputFactory
|
||||
import org.thoughtcrime.securesms.video.videoconverter.mediadatasource.MediaDataSourceMediaInput
|
||||
@@ -19,8 +20,8 @@ import java.io.IOException
|
||||
object DecryptableUriMediaInput : MediaInputFactory {
|
||||
@Throws(IOException::class)
|
||||
override fun createForUri(context: Context, uri: Uri): MediaInput {
|
||||
if (BlobProvider.isAuthority(uri)) {
|
||||
return MediaDataSourceMediaInput(BlobProvider.getInstance().getMediaDataSource(context, uri))
|
||||
if (AppDependencies.blobs.isAuthority(uri)) {
|
||||
return MediaDataSourceMediaInput(MediaDataSourceProvider.getMediaDataSource(context, uri))
|
||||
}
|
||||
return if (PartAuthority.isLocalUri(uri)) {
|
||||
createForAttachmentUri(uri)
|
||||
|
||||
@@ -20,10 +20,11 @@ import org.signal.mediasend.MediaConstraints;
|
||||
import org.signal.mediasend.CameraFragment;
|
||||
import org.signal.mediasend.capture.CameraXFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.gallery.MediaGalleryFragment;
|
||||
import org.thoughtcrime.securesms.mms.PushMediaConstraints;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.signal.core.util.contentproviders.BlobProvider;
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
@@ -79,10 +80,10 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera
|
||||
|
||||
@Override
|
||||
public void onImageCaptured(@NonNull byte[] data, int width, int height) {
|
||||
Uri blobUri = BlobProvider.getInstance()
|
||||
.forData(data)
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionInMemory();
|
||||
Uri blobUri = AppDependencies.getBlobs()
|
||||
.forData(data)
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionInMemory();
|
||||
|
||||
onMediaSelected(new Media(blobUri,
|
||||
MediaUtil.IMAGE_JPEG,
|
||||
|
||||
+6
-5
@@ -13,8 +13,9 @@ import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.signal.imageeditor.core.model.EditorModel;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.fonts.FontTypefaceProvider;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.signal.core.util.contentproviders.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -45,10 +46,10 @@ public final class ImageEditorModelRenderMediaTransform implements MediaTransfor
|
||||
try {
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
|
||||
|
||||
Uri uri = BlobProvider.getInstance()
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionOnDisk(context);
|
||||
Uri uri = AppDependencies.getBlobs()
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionOnDisk(context);
|
||||
|
||||
return new Media(uri, MediaUtil.IMAGE_JPEG, media.getDate(), bitmap.getWidth(), bitmap.getHeight(), outputStream.size(), 0, false, false, media.getBucketId(), media.getCaption(), null, null);
|
||||
} catch (IOException e) {
|
||||
|
||||
+2
-3
@@ -39,7 +39,6 @@ import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
|
||||
@@ -237,8 +236,8 @@ class MediaSelectionRepository(context: Context) {
|
||||
fun deleteBlobs(media: List<Media>) {
|
||||
media
|
||||
.map(Media::uri)
|
||||
.filter(BlobProvider::isAuthority)
|
||||
.forEach { BlobProvider.getInstance().delete(context, it) }
|
||||
.filter { AppDependencies.blobs.isAuthority(it) }
|
||||
.forEach { AppDependencies.blobs.delete(context, it) }
|
||||
}
|
||||
|
||||
fun cleanUp(selectedMedia: List<Media>) {
|
||||
|
||||
+4
-3
@@ -21,6 +21,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import org.signal.core.models.media.Media
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.core.util.contentproviders.BlobProvider
|
||||
import org.signal.core.util.getParcelableArrayListCompat
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.core.util.logging.Log
|
||||
@@ -31,9 +32,9 @@ import org.thoughtcrime.securesms.components.mention.MentionAnnotation
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.conversation.MessageSendType
|
||||
import org.thoughtcrime.securesms.conversation.MessageStyler
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
||||
import org.thoughtcrime.securesms.mms.PushMediaConstraints
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
@@ -472,7 +473,7 @@ class MediaSelectionViewModel(
|
||||
editorStates.forEach { it.writeToParcel(parcel, 0) }
|
||||
val serializedEditorState: ByteArray = parcel.marshall()
|
||||
parcel.recycle()
|
||||
val blobUri = BlobProvider.getInstance().forData(serializedEditorState).createForSingleUseInMemory()
|
||||
val blobUri = AppDependencies.blobs.forData(serializedEditorState).createForSingleUseInMemory()
|
||||
outState.putParcelable(STATE_EDITORS, blobUri)
|
||||
}
|
||||
}
|
||||
@@ -496,7 +497,7 @@ class MediaSelectionViewModel(
|
||||
val cameraFirstCapture: Media? = savedInstanceState.getParcelableCompat(STATE_CAMERA_FIRST_CAPTURE, Media::class.java)
|
||||
val editorCount: Int = savedInstanceState.getInt(STATE_EDITOR_COUNT, 0)
|
||||
val blobUri: Uri? = savedInstanceState.getParcelableCompat(STATE_EDITORS, Uri::class.java)
|
||||
val blobProvider: BlobProvider = BlobProvider.getInstance()
|
||||
val blobProvider: BlobProvider = AppDependencies.blobs
|
||||
val editorStates: List<Bundle> = if (editorCount > 0 && blobUri != null && blobProvider.hasStream(context, blobUri)) {
|
||||
val accumulator: MutableList<Bundle> = mutableListOf()
|
||||
val blob: ByteArray = ByteStreams.toByteArray(blobProvider.getStream(context, blobUri))
|
||||
|
||||
+3
-2
@@ -10,8 +10,9 @@ import androidx.annotation.WorkerThread
|
||||
import org.signal.core.models.media.Media
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.contentproviders.BlobProvider
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.mediasend.MediaRepository
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.video.videoconverter.utils.VideoConstants
|
||||
import java.io.FileDescriptor
|
||||
@@ -72,7 +73,7 @@ class MediaCaptureRepository(context: Context) {
|
||||
return try {
|
||||
val data: T = dataSupplier()
|
||||
val length: Long = getLength(data)
|
||||
val uri: Uri = createBlobBuilder(BlobProvider.getInstance(), data, length)
|
||||
val uri: Uri = createBlobBuilder(AppDependencies.blobs, data, length)
|
||||
.withMimeType(mimeType)
|
||||
.createForSingleSessionOnDisk(context)
|
||||
|
||||
|
||||
+2
-2
@@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.fonts.TextFont
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.StorySend
|
||||
@@ -18,7 +19,6 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationState
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -32,7 +32,7 @@ class TextStoryPostSendRepository {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
bitmap.recycle()
|
||||
BlobProvider.getInstance().forData(outputStream.toByteArray()).createForSingleSessionInMemory()
|
||||
AppDependencies.blobs.forData(outputStream.toByteArray()).createForSingleSessionInMemory()
|
||||
}.subscribeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionRepository
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaValidator
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.mms.PushMediaConstraints
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
|
||||
@@ -96,8 +95,8 @@ object MediaSendV3Repository : MediaSendRepository {
|
||||
override suspend fun deleteBlobs(media: List<Media>) {
|
||||
media
|
||||
.map(Media::uri)
|
||||
.filter(BlobProvider::isAuthority)
|
||||
.forEach { BlobProvider.getInstance().delete(appContext, it) }
|
||||
.filter { AppDependencies.blobs.isAuthority(it) }
|
||||
.forEach { AppDependencies.blobs.delete(appContext, it) }
|
||||
}
|
||||
|
||||
override suspend fun send(request: SendRequest): SendResult = withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord
|
||||
import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord.ActionId
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.LocaleRemoteConfig
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
@@ -50,7 +49,7 @@ object RemoteMegaphoneRepository {
|
||||
SignalExecutors.BOUNDED_IO.execute {
|
||||
db.markFinished(remote.uuid)
|
||||
if (remote.imageUri != null) {
|
||||
BlobProvider.getInstance().delete(context, remote.imageUri)
|
||||
AppDependencies.blobs.delete(context, remote.imageUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* We moved files stored by {@link org.thoughtcrime.securesms.providers.BlobProvider} from the cache
|
||||
* We moved files stored by {@link org.signal.core.util.contentproviders.BlobProvider} from the cache
|
||||
* into internal storage, so we gotta move any existing multi-session files.
|
||||
*/
|
||||
public class BlobStorageLocationMigrationJob extends MigrationJob {
|
||||
|
||||
@@ -37,13 +37,17 @@ import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.signal.core.ui.view.Stub;
|
||||
import org.signal.core.util.ExpiringProfileCredentialUtil;
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.bitmaps.BitmapUtil;
|
||||
import org.signal.core.util.concurrent.ListenableFuture;
|
||||
import org.signal.core.util.concurrent.ListenableFuture.Listener;
|
||||
import org.signal.core.util.concurrent.SettableFuture;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.ui.view.Stub;
|
||||
import org.signal.core.util.permissions.PermissionCompat;
|
||||
import org.signal.mediasend.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
@@ -55,6 +59,7 @@ import org.thoughtcrime.securesms.components.location.SignalPlace;
|
||||
import org.thoughtcrime.securesms.conversation.MessageSendType;
|
||||
import org.thoughtcrime.securesms.database.MediaTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
||||
@@ -67,19 +72,14 @@ import org.thoughtcrime.securesms.payments.create.CreatePaymentFragmentArgs;
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
|
||||
import org.thoughtcrime.securesms.payments.preferences.RecipientHasNotEnabledPaymentsDialog;
|
||||
import org.thoughtcrime.securesms.payments.preferences.model.PayeeParcelable;
|
||||
import org.signal.core.util.permissions.PermissionCompat;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.signal.core.util.bitmaps.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.signal.core.util.ExpiringProfileCredentialUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
@@ -174,14 +174,14 @@ public class AttachmentManager {
|
||||
}
|
||||
|
||||
private void cleanup(final @Nullable Uri uri) {
|
||||
if (uri != null && BlobProvider.isAuthority(uri)) {
|
||||
if (uri != null && AppDependencies.getBlobs().isAuthority(uri)) {
|
||||
Log.d(TAG, "cleaning up " + uri);
|
||||
BlobProvider.getInstance().delete(context, uri);
|
||||
AppDependencies.getBlobs().delete(context, uri);
|
||||
}
|
||||
}
|
||||
|
||||
private void markGarbage(@Nullable Uri uri) {
|
||||
if (uri != null && BlobProvider.isAuthority(uri)) {
|
||||
if (uri != null && AppDependencies.getBlobs().isAuthority(uri)) {
|
||||
Log.d(TAG, "Marking garbage that needs cleaning: " + uri);
|
||||
garbage.add(uri);
|
||||
}
|
||||
@@ -215,10 +215,10 @@ public class AttachmentManager {
|
||||
@Override
|
||||
public void onSuccess(@NonNull Bitmap result) {
|
||||
byte[] blob = BitmapUtil.toByteArray(result);
|
||||
Uri uri = BlobProvider.getInstance()
|
||||
.forData(blob)
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionInMemory();
|
||||
Uri uri = AppDependencies.getBlobs()
|
||||
.forData(blob)
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionInMemory();
|
||||
LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place);
|
||||
|
||||
ThreadUtil.runOnMain(() -> {
|
||||
@@ -241,7 +241,7 @@ public class AttachmentManager {
|
||||
|
||||
attachmentViewStub.get().setVisibility(View.VISIBLE);
|
||||
removableMediaView.display(mapView, false);
|
||||
LocationSlide locationSlide = new LocationSlide(context, thumbnailUri, BlobProvider.getFileSize(thumbnailUri), place);
|
||||
LocationSlide locationSlide = new LocationSlide(context, thumbnailUri, AppDependencies.getBlobs().getFileSize(thumbnailUri), place);
|
||||
setSlide(locationSlide);
|
||||
attachmentListener.onAttachmentChanged();
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.provider.DocumentsContractCompat;
|
||||
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.signal.core.models.media.TransformProperties;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
|
||||
import org.signal.core.models.media.TransformProperties;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiFiles;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -58,7 +58,7 @@ public class PartAuthority {
|
||||
uriMatcher.addURI(AUTHORITY, "wallpaper/*", WALLPAPER_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "emoji/*", EMOJI_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "avatar_picker/*", AVATAR_PICKER_ROW);
|
||||
uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW);
|
||||
uriMatcher.addURI(AppDependencies.getBlobs().getAuthority(), AppDependencies.getBlobs().PATH, BLOB_ROW);
|
||||
}
|
||||
|
||||
public static InputStream getAttachmentThumbnailStream(@NonNull Context context, @NonNull Uri uri)
|
||||
@@ -75,7 +75,7 @@ public class PartAuthority {
|
||||
switch (match) {
|
||||
case PART_ROW: return SignalDatabase.attachments().getAttachmentStream(new PartUriParser(uri).getPartId(), 0);
|
||||
case STICKER_ROW: return SignalDatabase.stickers().getStickerStream(ContentUris.parseId(uri));
|
||||
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
||||
case BLOB_ROW: return AppDependencies.getBlobs().getStream(context, uri);
|
||||
case EMOJI_ROW: return EmojiFiles.openForReading(context, getEmojiFilename(uri));
|
||||
case AVATAR_PICKER_ROW: return AvatarPickerStorage.read(context, getAvatarPickerFilename(uri));
|
||||
case THUMBNAIL_ROW: return SignalDatabase.attachments().getAttachmentThumbnailStream(new PartUriParser(uri).getPartId(), 0);
|
||||
@@ -96,7 +96,7 @@ public class PartAuthority {
|
||||
if (attachment != null) return attachment.fileName;
|
||||
else return null;
|
||||
case BLOB_ROW:
|
||||
return BlobProvider.getFileName(uri);
|
||||
return AppDependencies.getBlobs().getFileName(uri);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ public class PartAuthority {
|
||||
if (attachment != null) return attachment.size;
|
||||
else return null;
|
||||
case BLOB_ROW:
|
||||
return BlobProvider.getFileSize(uri);
|
||||
return AppDependencies.getBlobs().getFileSize(uri);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ public class PartAuthority {
|
||||
if (attachment != null) return attachment.contentType;
|
||||
else return null;
|
||||
case BLOB_ROW:
|
||||
return BlobProvider.getMimeType(uri);
|
||||
return AppDependencies.getBlobs().getMimeType(uri);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
+2
-4
@@ -11,7 +11,6 @@ import org.signal.glide.decryptableuri.DecryptableUri
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.util.ImageCompressionUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.kb
|
||||
@@ -127,8 +126,7 @@ object NotificationThumbnails {
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
val thumbnailUri = BlobProvider
|
||||
.getInstance()
|
||||
val thumbnailUri = AppDependencies.blobs
|
||||
.forData(result.data)
|
||||
.withMimeType(result.mimeType)
|
||||
.withFileName(result.hashCode().toString())
|
||||
@@ -137,7 +135,7 @@ object NotificationThumbnails {
|
||||
synchronized(thumbnailCache) {
|
||||
if (thumbnailCache.size >= MAX_CACHE_SIZE) {
|
||||
thumbnailCache.remove(thumbnailCache.keys.first())?.uri?.let {
|
||||
BlobProvider.getInstance().delete(context, it)
|
||||
AppDependencies.blobs.delete(context, it)
|
||||
}
|
||||
}
|
||||
thumbnailCache[messageId] = CachedThumbnail(thumbnailUri, result.mimeType)
|
||||
|
||||
@@ -6,17 +6,18 @@ import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.ByteUnit;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream;
|
||||
import org.signal.core.util.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.database.model.ProfileAvatarFileDetails;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.signal.core.util.ByteUnit;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
@@ -117,7 +118,7 @@ public class AvatarHelper {
|
||||
* IOException. It is recommended to call {@link #hasAvatar(Context, RecipientId)} first.
|
||||
*/
|
||||
public static @NonNull InputStream getAvatar(@NonNull Context context, @NonNull RecipientId recipientId) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret();
|
||||
File avatarFile = getAvatarFile(context, recipientId);
|
||||
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, avatarFile, 0);
|
||||
@@ -166,7 +167,7 @@ public class AvatarHelper {
|
||||
* recipient. Only intended to be used for backup. Otherwise, use {@link #setAvatar(Context, RecipientId, InputStream)}.
|
||||
*/
|
||||
public static @NonNull OutputStream getOutputStream(@NonNull Context context, @NonNull RecipientId recipientId, boolean isSyncAvatar) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret();
|
||||
File targetFile = getAvatarFile(context, recipientId, isSyncAvatar);
|
||||
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, targetFile, true).getSecond();
|
||||
}
|
||||
@@ -215,7 +216,7 @@ public class AvatarHelper {
|
||||
private static void setAvatarInternal(@NonNull Context context, @NonNull RecipientId recipientId, @NonNull InputStream inputStream, boolean isSyncAvatar)
|
||||
throws IOException
|
||||
{
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret();
|
||||
File targetFile = getAvatarFile(context, recipientId, isSyncAvatar);
|
||||
File tempFile = new File(targetFile.getParent(), targetFile.getName() + ".tmp");
|
||||
|
||||
|
||||
+4
-6
@@ -22,23 +22,22 @@ import com.airbnb.lottie.SimpleColorFilter;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.signal.core.ui.compose.SignalIcons;
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.signal.core.ui.logging.LoggingFragment;
|
||||
import org.signal.core.util.EditTextUtil;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.ui.logging.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.avatar.Avatars;
|
||||
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
|
||||
import org.thoughtcrime.securesms.databinding.CreateProfileFragmentBinding;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.thoughtcrime.securesms.profiles.edit.pnp.WhoCanFindMeByPhoneNumberFragment;
|
||||
import org.thoughtcrime.securesms.profiles.manage.EditProfileNameFragment;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@@ -47,7 +46,6 @@ import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity.EXCLUDE_SYSTEM;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity.GROUP_ID;
|
||||
@@ -119,7 +117,7 @@ public class CreateProfileFragment extends LoggingFragment {
|
||||
private void handleMediaFromResult(@NonNull Media media) {
|
||||
SimpleTask.run(() -> {
|
||||
try {
|
||||
InputStream stream = BlobProvider.getInstance().getStream(requireContext(), media.getUri());
|
||||
InputStream stream = AppDependencies.getBlobs().getStream(requireContext(), media.getUri());
|
||||
|
||||
return StreamUtil.readFully(stream);
|
||||
} catch (IOException ioException) {
|
||||
|
||||
+2
-3
@@ -10,6 +10,7 @@ import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -17,10 +18,8 @@ import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
@@ -135,7 +134,7 @@ class EditProfileViewModel extends ViewModel {
|
||||
} else {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
try {
|
||||
InputStream stream = BlobProvider.getInstance().getStream(context, media.getUri());
|
||||
InputStream stream = AppDependencies.getBlobs().getStream(context, media.getUri());
|
||||
byte[] data = StreamUtil.readFully(stream);
|
||||
|
||||
internalAvatarState.postValue(InternalAvatarState.loading(data));
|
||||
|
||||
@@ -15,11 +15,13 @@ import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.contentproviders.BaseContentProvider
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider
|
||||
import org.signal.core.util.logging.AndroidLogger
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader
|
||||
@@ -73,7 +75,7 @@ class AvatarProvider : BaseContentProvider() {
|
||||
SignalDatabase.init(
|
||||
application,
|
||||
DatabaseSecretProvider.getOrCreateDatabaseSecret(application),
|
||||
AttachmentSecretProvider.getInstance(application).getOrCreateAttachmentSecret()
|
||||
AttachmentSecretProvider.getInstance(application, AppAttachmentSecretStore).getOrCreateAttachmentSecret()
|
||||
)
|
||||
|
||||
SignalStore.init(application)
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package org.thoughtcrime.securesms.providers;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.provider.OpenableColumns;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class BaseContentProvider extends ContentProvider {
|
||||
|
||||
private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
||||
|
||||
/**
|
||||
* Sanity checks the security like FileProvider does.
|
||||
*/
|
||||
@Override
|
||||
public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
|
||||
super.attachInfo(context, info);
|
||||
|
||||
if (info.exported) {
|
||||
throw new SecurityException("Provider must not be exported");
|
||||
}
|
||||
if (!info.grantUriPermissions) {
|
||||
throw new SecurityException("Provider must grant uri permissions");
|
||||
}
|
||||
}
|
||||
|
||||
protected static Cursor createCursor(@Nullable String[] projection, @NonNull String fileName, long fileSize) {
|
||||
if (projection == null || projection.length == 0) {
|
||||
projection = COLUMNS;
|
||||
}
|
||||
|
||||
ArrayList<String> cols = new ArrayList<>(projection.length);
|
||||
ArrayList<Object> values = new ArrayList<>(projection.length);
|
||||
|
||||
for (String col : projection) {
|
||||
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
|
||||
cols.add(OpenableColumns.DISPLAY_NAME);
|
||||
values.add(fileName);
|
||||
} else if (OpenableColumns.SIZE.equals(col)) {
|
||||
cols.add(OpenableColumns.SIZE);
|
||||
values.add(fileSize);
|
||||
}
|
||||
}
|
||||
|
||||
MatrixCursor cursor = new MatrixCursor(cols.toArray(new String[0]), 1);
|
||||
|
||||
cursor.addRow(values.toArray(new Object[0]));
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
protected static String createFileNameForMimeType(String mimeType) {
|
||||
return mimeType.replace('/', '.');
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.providers;
|
||||
|
||||
import android.content.ContentValues;
|
||||
@@ -9,11 +14,13 @@ import android.os.ParcelFileDescriptor;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.CoreUtilDependencies;
|
||||
import org.signal.core.util.MemoryFileUtil;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.contentproviders.BaseContentProvider;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.util.MemoryFileUtil;
|
||||
import org.signal.core.util.Util;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -35,8 +42,8 @@ public final class BlobContentProvider extends BaseContentProvider {
|
||||
Log.i(TAG, "openFile() called: " + uri);
|
||||
|
||||
try {
|
||||
try (InputStream stream = BlobProvider.getInstance().getStream(AppDependencies.getApplication(), uri)) {
|
||||
Long fileSize = BlobProvider.getFileSize(uri);
|
||||
try (InputStream stream = AppDependencies.getBlobs().getStream(CoreUtilDependencies.getApplication(), uri)) {
|
||||
Long fileSize = AppDependencies.getBlobs().getFileSize(uri);
|
||||
if (fileSize == null) {
|
||||
Log.w(TAG, "No file size available");
|
||||
throw new FileNotFoundException();
|
||||
@@ -64,9 +71,9 @@ public final class BlobContentProvider extends BaseContentProvider {
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
|
||||
Log.i(TAG, "query() called: " + uri);
|
||||
|
||||
String mimeType = BlobProvider.getMimeType(uri);
|
||||
String fileName = BlobProvider.getFileName(uri);
|
||||
Long fileSize = BlobProvider.getFileSize(uri);
|
||||
String mimeType = AppDependencies.getBlobs().getMimeType(uri);
|
||||
String fileName = AppDependencies.getBlobs().getFileName(uri);
|
||||
Long fileSize = AppDependencies.getBlobs().getFileSize(uri);
|
||||
|
||||
if (fileSize == null) {
|
||||
Log.w(TAG, "No file size");
|
||||
@@ -79,16 +86,16 @@ public final class BlobContentProvider extends BaseContentProvider {
|
||||
}
|
||||
|
||||
if (fileName == null) {
|
||||
fileName = createFileNameForMimeType(mimeType);
|
||||
fileName = BaseContentProvider.createFileNameForMimeType(mimeType);
|
||||
}
|
||||
|
||||
return createCursor(projection, fileName, fileSize);
|
||||
return BaseContentProvider.createCursor(projection, fileName, fileSize);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return BlobProvider.getMimeType(uri);
|
||||
return AppDependencies.getBlobs().getMimeType(uri);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -1,704 +0,0 @@
|
||||
package org.thoughtcrime.securesms.providers;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.media.MediaDataSource;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.database.DraftTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.util.IOFunction;
|
||||
import org.signal.core.util.Util;
|
||||
import org.thoughtcrime.securesms.video.ByteArrayMediaDataSource;
|
||||
import org.thoughtcrime.securesms.video.EncryptedMediaDataSource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Allows for the creation and retrieval of blobs.
|
||||
*/
|
||||
public class BlobProvider {
|
||||
|
||||
private static final String TAG = Log.tag(BlobProvider.class);
|
||||
|
||||
private static final String DRAFT_ATTACHMENTS_DIRECTORY = "draft_blobs";
|
||||
private static final String MULTI_SESSION_DIRECTORY = "multi_session_blobs";
|
||||
private static final String SINGLE_SESSION_DIRECTORY = "single_session_blobs";
|
||||
private static final String TEMP_BACKUPS_DIRECTORY = "temp_backups";
|
||||
|
||||
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".blob";
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/blob");
|
||||
public static final String PATH = "blob/*/*/*/*/*";
|
||||
|
||||
private static final int STORAGE_TYPE_PATH_SEGMENT = 1;
|
||||
private static final int MIMETYPE_PATH_SEGMENT = 2;
|
||||
private static final int FILENAME_PATH_SEGMENT = 3;
|
||||
private static final int FILESIZE_PATH_SEGMENT = 4;
|
||||
private static final int ID_PATH_SEGMENT = 5;
|
||||
|
||||
private static final int MATCH = 1;
|
||||
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH) {{
|
||||
addURI(AUTHORITY, PATH, MATCH);
|
||||
}};
|
||||
|
||||
private static final BlobProvider INSTANCE = new BlobProvider();
|
||||
|
||||
private final Map<Uri, byte[]> memoryBlobs = new HashMap<>();
|
||||
|
||||
private volatile boolean initialized = false;
|
||||
|
||||
|
||||
public static BlobProvider getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin building a blob for the provided data. Allows for the creation of in-memory blobs.
|
||||
*/
|
||||
public MemoryBlobBuilder forData(@NonNull byte[] data) {
|
||||
return new MemoryBlobBuilder(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin building a blob for the provided input stream.
|
||||
*/
|
||||
public BlobBuilder forData(@NonNull InputStream data, long fileSize) {
|
||||
return new BlobBuilder(data, fileSize);
|
||||
}
|
||||
|
||||
public static boolean isSingleUseMemoryBlob(Uri uri) throws IOException {
|
||||
return StorageType.decode(uri.getPathSegments().get(STORAGE_TYPE_PATH_SEGMENT)) == StorageType.SINGLE_USE_MEMORY;
|
||||
}
|
||||
|
||||
public synchronized boolean hasStream(@NonNull Context context, @NonNull Uri uri) {
|
||||
waitUntilInitialized();
|
||||
try {
|
||||
if (isAuthority(uri)) {
|
||||
StorageType storageType = StorageType.decode(uri.getPathSegments().get(STORAGE_TYPE_PATH_SEGMENT));
|
||||
|
||||
if (storageType.isMemory()) {
|
||||
byte[] data = memoryBlobs.get(uri);
|
||||
|
||||
return data != null;
|
||||
} else {
|
||||
String id = uri.getPathSegments().get(ID_PATH_SEGMENT);
|
||||
String directory = getDirectory(storageType);
|
||||
File file = new File(getOrCreateDirectory(context, directory), buildFileName(id));
|
||||
|
||||
return file.exists();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Intentionally left blank
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a stream for the content with the specified URI.
|
||||
*
|
||||
* @throws IOException If the stream fails to open or the spec of the URI doesn't match.
|
||||
*/
|
||||
public synchronized @NonNull InputStream getStream(@NonNull Context context, @NonNull Uri uri) throws IOException {
|
||||
waitUntilInitialized();
|
||||
return getStream(context, uri, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a stream for the content with the specified URI starting from the specified position.
|
||||
*
|
||||
* @throws IOException If the stream fails to open or the spec of the URI doesn't match.
|
||||
*/
|
||||
public synchronized @NonNull InputStream getStream(@NonNull Context context, @NonNull Uri uri, long position) throws IOException {
|
||||
waitUntilInitialized();
|
||||
return getBlobRepresentation(context,
|
||||
uri,
|
||||
bytes -> {
|
||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
|
||||
if (byteArrayInputStream.skip(position) != position) {
|
||||
throw new IOException("Failed to skip to position " + position + " for: " + uri);
|
||||
}
|
||||
return byteArrayInputStream;
|
||||
},
|
||||
file -> ModernDecryptingPartInputStream.createFor(getAttachmentSecret(context),
|
||||
file,
|
||||
position));
|
||||
}
|
||||
|
||||
public synchronized @NonNull MediaDataSource getMediaDataSource(@NonNull Context context, @NonNull Uri uri) throws IOException {
|
||||
waitUntilInitialized();
|
||||
return getBlobRepresentation(context,
|
||||
uri,
|
||||
ByteArrayMediaDataSource::new,
|
||||
file -> EncryptedMediaDataSource.createForDiskBlob(getAttachmentSecret(context), file));
|
||||
}
|
||||
|
||||
private synchronized @NonNull <T> T getBlobRepresentation(@NonNull Context context,
|
||||
@NonNull Uri uri,
|
||||
@NonNull IOFunction<byte[], T> getByteRepresentation,
|
||||
@NonNull IOFunction<File, T> getFileRepresentation)
|
||||
throws IOException
|
||||
{
|
||||
if (isAuthority(uri)) {
|
||||
StorageType storageType = StorageType.decode(uri.getPathSegments().get(STORAGE_TYPE_PATH_SEGMENT));
|
||||
|
||||
if (storageType.isMemory()) {
|
||||
byte[] data = memoryBlobs.get(uri);
|
||||
|
||||
if (data != null) {
|
||||
if (storageType == StorageType.SINGLE_USE_MEMORY) {
|
||||
memoryBlobs.remove(uri);
|
||||
}
|
||||
return getByteRepresentation.apply(data);
|
||||
} else {
|
||||
throw new IOException("Failed to find in-memory blob for: " + uri);
|
||||
}
|
||||
} else {
|
||||
String id = uri.getPathSegments().get(ID_PATH_SEGMENT);
|
||||
String directory = getDirectory(storageType);
|
||||
File file = new File(getOrCreateDirectory(context, directory), buildFileName(id));
|
||||
|
||||
return getFileRepresentation.apply(file);
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Provided URI does not match this spec. Uri: " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized AttachmentSecret getAttachmentSecret(@NonNull Context context) {
|
||||
return AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the content with the specified URI.
|
||||
*/
|
||||
public synchronized void delete(@NonNull Context context, @NonNull Uri uri) {
|
||||
waitUntilInitialized();
|
||||
|
||||
if (!isAuthority(uri)) {
|
||||
Log.d(TAG, "Can't delete. Not the authority for uri: " + uri);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Deleting " + getId(uri));
|
||||
|
||||
try {
|
||||
StorageType storageType = StorageType.decode(uri.getPathSegments().get(STORAGE_TYPE_PATH_SEGMENT));
|
||||
|
||||
if (storageType.isMemory()) {
|
||||
memoryBlobs.remove(uri);
|
||||
} else {
|
||||
String id = uri.getPathSegments().get(ID_PATH_SEGMENT);
|
||||
String directory = getDirectory(storageType);
|
||||
File file = new File(getOrCreateDirectory(context, directory), buildFileName(id));
|
||||
|
||||
if (file.delete()) {
|
||||
Log.d(TAG, "Successfully deleted " + getId(uri));
|
||||
} else {
|
||||
throw new IOException("File wasn't deleted.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to delete uri: " + getId(uri), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the class to be initialized. Part of this initialization is deleting any leftover
|
||||
* single-session blobs from the previous session. However, this class defers that work to a
|
||||
* background thread, so callers don't have to worry about it.
|
||||
*/
|
||||
@AnyThread
|
||||
public synchronized void initialize(@NonNull Context context) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
synchronized (this) {
|
||||
File directory = getOrCreateDirectory(context, SINGLE_SESSION_DIRECTORY);
|
||||
File[] files = directory.listFiles();
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.delete()) {
|
||||
Log.d(TAG, "Deleted single-session file: " + file.getName());
|
||||
} else {
|
||||
Log.w(TAG, "Failed to delete single-session file! " + file.getName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Null directory listing!");
|
||||
}
|
||||
|
||||
deleteOrphanedDraftFiles(context);
|
||||
|
||||
Log.i(TAG, "Initialized.");
|
||||
initialized = true;
|
||||
notifyAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public synchronized void clearTemporaryBackupsDirectory(@NonNull Context context) {
|
||||
File directory = getOrCreateDirectory(context, TEMP_BACKUPS_DIRECTORY);
|
||||
File[] files = directory.listFiles();
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.delete()) {
|
||||
Log.d(TAG, "Deleted temporary backup file: " + file.getName());
|
||||
} else {
|
||||
Log.w(TAG, "Failed to delete temporary backup file: " + file.getName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Null directory listing!");
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public synchronized byte[] getMemoryBlob(@NonNull Uri uri) {
|
||||
return memoryBlobs.get(uri);
|
||||
}
|
||||
|
||||
private static void deleteOrphanedDraftFiles(@NonNull Context context) {
|
||||
File directory = getOrCreateDirectory(context, DRAFT_ATTACHMENTS_DIRECTORY);
|
||||
File[] files = directory.listFiles();
|
||||
|
||||
if (files == null || files.length == 0) {
|
||||
Log.d(TAG, "No attachment drafts exist. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
DraftTable draftDatabase = SignalDatabase.drafts();
|
||||
DraftTable.Drafts voiceNoteDrafts = draftDatabase.getAllVoiceNoteDrafts();
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
List<String> draftFileNames = voiceNoteDrafts.stream()
|
||||
.map(VoiceNoteDraft::fromDraft)
|
||||
.map(VoiceNoteDraft::getUri)
|
||||
.map(BlobProvider::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.map(BlobProvider::buildFileName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (final File file : files) {
|
||||
if (!draftFileNames.contains(file.getName())) {
|
||||
if (file.delete()) {
|
||||
Log.d(TAG, "Deleted orphaned attachment draft: " + file.getName());
|
||||
} else {
|
||||
Log.d(TAG, "Failed to delete orphaned attachment draft: " + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable String getMimeType(@NonNull Uri uri) {
|
||||
if (isAuthority(uri)) {
|
||||
return uri.getPathSegments().get(MIMETYPE_PATH_SEGMENT);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable String getFileName(@NonNull Uri uri) {
|
||||
if (isAuthority(uri)) {
|
||||
return uri.getPathSegments().get(FILENAME_PATH_SEGMENT);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable Long getFileSize(@NonNull Uri uri) {
|
||||
if (isAuthority(uri)) {
|
||||
try {
|
||||
return Long.parseLong(uri.getPathSegments().get(FILESIZE_PATH_SEGMENT));
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable String getId(@NonNull Uri uri) {
|
||||
if (isAuthority(uri)) {
|
||||
return Uri.encode(uri.getPathSegments().get(ID_PATH_SEGMENT));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public long calculateFileSize(@NonNull Context context, @NonNull Uri uri) {
|
||||
if (!isAuthority(uri)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try (InputStream stream = getStream(context, uri)) {
|
||||
return StreamUtil.getStreamLength(stream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isAuthority(@NonNull Uri uri) {
|
||||
return URI_MATCHER.match(uri) == MATCH;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private synchronized @NonNull Uri writeBlobSpecToDisk(@NonNull Context context, @NonNull BlobSpec blobSpec)
|
||||
throws IOException
|
||||
{
|
||||
waitUntilInitialized();
|
||||
|
||||
Future<Uri> uriFuture = writeBlobSpecToDiskAsync(context, blobSpec);
|
||||
try {
|
||||
return uriFuture.get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Error writing blob spec to disk", e);
|
||||
if (e.getCause() instanceof IOException) {
|
||||
throw (IOException) e.getCause();
|
||||
} else {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@WorkerThread
|
||||
private synchronized @NonNull Future<Uri> writeBlobSpecToDiskAsync(@NonNull Context context, @NonNull BlobSpec blobSpec)
|
||||
throws IOException
|
||||
{
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
String directory = getDirectory(blobSpec.getStorageType());
|
||||
File outputFile = new File(getOrCreateDirectory(context, directory), buildFileName(blobSpec.id));
|
||||
OutputStream outputStream = ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).getSecond();
|
||||
|
||||
final Uri uri = buildUri(blobSpec);
|
||||
|
||||
return SignalExecutors.BOUNDED.submit(() -> {
|
||||
try {
|
||||
StreamUtil.copy(blobSpec.getData(), outputStream);
|
||||
return uri;
|
||||
} catch (IOException e) {
|
||||
delete(context, uri);
|
||||
Log.w(TAG, "Error during write!", e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized @NonNull Uri writeBlobSpecToMemory(@NonNull BlobSpec blobSpec, @NonNull byte[] data) {
|
||||
Uri uri = buildUri(blobSpec);
|
||||
memoryBlobs.put(uri, data);
|
||||
return uri;
|
||||
}
|
||||
|
||||
private static @NonNull String buildFileName(@NonNull String id) {
|
||||
return Uri.encode(id) + ".blob";
|
||||
}
|
||||
|
||||
private static @NonNull String getDirectory(@NonNull StorageType storageType) {
|
||||
switch (storageType) {
|
||||
case SINGLE_USE_MEMORY:
|
||||
case SINGLE_SESSION_MEMORY:
|
||||
throw new IllegalArgumentException("In-Memory Blobs do not have directories.");
|
||||
case SINGLE_SESSION_DISK:
|
||||
return SINGLE_SESSION_DIRECTORY;
|
||||
case MULTI_SESSION_DISK:
|
||||
return MULTI_SESSION_DIRECTORY;
|
||||
case ATTACHMENT_DRAFT:
|
||||
return DRAFT_ATTACHMENTS_DIRECTORY;
|
||||
case TEMP_BACKUPS:
|
||||
return TEMP_BACKUPS_DIRECTORY;
|
||||
}
|
||||
return storageType == StorageType.MULTI_SESSION_DISK ? MULTI_SESSION_DIRECTORY : SINGLE_SESSION_DIRECTORY;
|
||||
}
|
||||
|
||||
private static @NonNull Uri buildUri(@NonNull BlobSpec blobSpec) {
|
||||
return CONTENT_URI.buildUpon()
|
||||
.appendPath(blobSpec.getStorageType().encode())
|
||||
.appendPath(blobSpec.getMimeType())
|
||||
.appendPath(blobSpec.getFileName())
|
||||
.appendEncodedPath(String.valueOf(blobSpec.getFileSize()))
|
||||
.appendPath(blobSpec.getId())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static File getOrCreateDirectory(@NonNull Context context, @NonNull String directory) {
|
||||
return context.getDir(directory, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link File} within the appropriate directory to be cleaned up as part of
|
||||
* normal operations. Unlike other blobs, this is just a file reference and no
|
||||
* automatic encryption occurs when reading or writing and must be done by the caller.
|
||||
*
|
||||
* @return file located in the appropriate directory to be delete on app session restarts
|
||||
*/
|
||||
public File forNonAutoEncryptingSingleSessionOnDisk(@NonNull Context context) {
|
||||
String directory = getDirectory(StorageType.SINGLE_SESSION_DISK);
|
||||
String id = UUID.randomUUID().toString();
|
||||
return new File(getOrCreateDirectory(context, directory), buildFileName(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link File} within the appropriate directory to persist between multiple
|
||||
* process lifetimes. Unlike other blobs, this is just a file reference and no
|
||||
* automatic encryption occurs when reading or writing and must be done by the caller.
|
||||
*
|
||||
* @return file located in the appropriate directory. The directory is periodically cleared.
|
||||
*/
|
||||
public File forTemporaryBackup(@NonNull Context context) {
|
||||
String directory = getDirectory(StorageType.TEMP_BACKUPS);
|
||||
String id = UUID.randomUUID().toString();
|
||||
return new File(getOrCreateDirectory(context, directory), buildFileName(id));
|
||||
}
|
||||
|
||||
public class BlobBuilder {
|
||||
|
||||
private InputStream data;
|
||||
private String id;
|
||||
private String mimeType;
|
||||
private String fileName;
|
||||
private long fileSize;
|
||||
|
||||
private BlobBuilder(@NonNull InputStream data, long fileSize) {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.data = data;
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public BlobBuilder withMimeType(@NonNull String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlobBuilder withFileName(@Nullable String fileName) {
|
||||
this.fileName = fileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected BlobSpec buildBlobSpec(@NonNull StorageType storageType) {
|
||||
return new BlobSpec(data, id, storageType, mimeType, fileName, fileSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a blob that will exist for a single app session. An app session is defined as the
|
||||
* period from one {@link Application#onCreate()} to the next.
|
||||
*/
|
||||
@WorkerThread
|
||||
public Uri createForSingleSessionOnDisk(@NonNull Context context) throws IOException {
|
||||
return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.SINGLE_SESSION_DISK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an async blob that will exist for a single app session. An app session is defined as the
|
||||
* period from one {@link Application#onCreate()} to the next. The file will be created on disk
|
||||
* synchronously, but the data will copied asynchronously. This is helpful when the copy is
|
||||
* long-running, such as in the case of recording a voice note.
|
||||
*/
|
||||
@WorkerThread
|
||||
public Future<Uri> createForSingleSessionOnDiskAsync(@NonNull Context context) throws IOException {
|
||||
return writeBlobSpecToDiskAsync(context, buildBlobSpec(StorageType.SINGLE_SESSION_DISK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a blob that will exist for multiple app sessions. It is the caller's responsibility to
|
||||
* eventually call {@link BlobProvider#delete(Context, Uri)} when the blob is no longer in use.
|
||||
*/
|
||||
@WorkerThread
|
||||
public Uri createForMultipleSessionsOnDisk(@NonNull Context context) throws IOException {
|
||||
return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.MULTI_SESSION_DISK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a blob that will exist for multiple app sessions. The file will be created on disk
|
||||
* synchronously, but the data will copied asynchronously. This is helpful when the copy is
|
||||
* long-running, such as in the case of recording a voice note.
|
||||
* <p>
|
||||
* It is the caller's responsibility to eventually call {@link BlobProvider#delete(Context, Uri)}
|
||||
* when the blob is no longer in use.
|
||||
*/
|
||||
@WorkerThread
|
||||
public Future<Uri> createForDraftAttachmentAsync(@NonNull Context context)
|
||||
throws IOException
|
||||
{
|
||||
return writeBlobSpecToDiskAsync(context, buildBlobSpec(StorageType.ATTACHMENT_DRAFT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the URI for a draft attachment without waiting for the data to be written.
|
||||
* This is useful for getting a URI reference while data is still being written asynchronously.
|
||||
* The URI can be used to check file size and save periodic snapshots.
|
||||
* <p>
|
||||
* It is the caller's responsibility to eventually call {@link BlobProvider#delete(Context, Uri)}
|
||||
* when the blob is no longer in use.
|
||||
*/
|
||||
@WorkerThread
|
||||
public Uri buildUriForDraftAttachment() {
|
||||
return buildUri(buildBlobSpec(StorageType.ATTACHMENT_DRAFT));
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void waitUntilInitialized() {
|
||||
if (!initialized) {
|
||||
Log.i(TAG, "Waiting for initialization...");
|
||||
synchronized (this) {
|
||||
while (!initialized) {
|
||||
Util.wait(this, 0);
|
||||
}
|
||||
Log.i(TAG, "Initialization complete.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryBlobBuilder extends BlobBuilder {
|
||||
|
||||
private byte[] data;
|
||||
|
||||
private MemoryBlobBuilder(@NonNull byte[] data) {
|
||||
super(new ByteArrayInputStream(data), data.length);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryBlobBuilder withMimeType(@NonNull String mimeType) {
|
||||
super.withMimeType(mimeType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryBlobBuilder withFileName(@NonNull String fileName) {
|
||||
super.withFileName(fileName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a blob that is stored in memory and can only be read a single time. After a single
|
||||
* read, it will be removed from storage. Useful for when a Uri is needed to read transient data.
|
||||
*/
|
||||
public Uri createForSingleUseInMemory() {
|
||||
return writeBlobSpecToMemory(buildBlobSpec(StorageType.SINGLE_USE_MEMORY), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a blob that is stored in memory. Will persist for a single app session. You should
|
||||
* always try to call {@link BlobProvider#delete(Context, Uri)} after you're done with the blob
|
||||
* to free up memory.
|
||||
*/
|
||||
public Uri createForSingleSessionInMemory() {
|
||||
return writeBlobSpecToMemory(buildBlobSpec(StorageType.SINGLE_SESSION_MEMORY), data);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BlobSpec {
|
||||
|
||||
private final InputStream data;
|
||||
private final String id;
|
||||
private final StorageType storageType;
|
||||
private final String mimeType;
|
||||
private final String fileName;
|
||||
private final long fileSize;
|
||||
|
||||
private BlobSpec(@NonNull InputStream data,
|
||||
@NonNull String id,
|
||||
@NonNull StorageType storageType,
|
||||
@NonNull String mimeType,
|
||||
@Nullable String fileName,
|
||||
@IntRange(from = 0) long fileSize)
|
||||
{
|
||||
this.data = data;
|
||||
this.id = id;
|
||||
this.storageType = storageType;
|
||||
this.mimeType = mimeType;
|
||||
this.fileName = fileName;
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
private @NonNull InputStream getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
private @NonNull String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private @NonNull StorageType getStorageType() {
|
||||
return storageType;
|
||||
}
|
||||
|
||||
private @NonNull String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
private @Nullable String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private long getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
private enum StorageType {
|
||||
|
||||
SINGLE_USE_MEMORY("single-use-memory", true),
|
||||
SINGLE_SESSION_MEMORY("single-session-memory", true),
|
||||
SINGLE_SESSION_DISK("single-session-disk", false),
|
||||
MULTI_SESSION_DISK("multi-session-disk", false),
|
||||
ATTACHMENT_DRAFT("attachment-draft", false),
|
||||
TEMP_BACKUPS("temporary-backups", false);
|
||||
|
||||
private final String encoded;
|
||||
private final boolean inMemory;
|
||||
|
||||
StorageType(String encoded, boolean inMemory) {
|
||||
this.encoded = encoded;
|
||||
this.inMemory = inMemory;
|
||||
}
|
||||
|
||||
private String encode() {
|
||||
return encoded;
|
||||
}
|
||||
|
||||
private boolean isMemory() {
|
||||
return inMemory;
|
||||
}
|
||||
|
||||
private static StorageType decode(@NonNull String encoded) throws IOException {
|
||||
for (StorageType storageType : StorageType.values()) {
|
||||
if (storageType.encoded.equals(encoded)) {
|
||||
return storageType;
|
||||
}
|
||||
}
|
||||
throw new IOException("Failed to decode lifespan.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import androidx.annotation.RequiresApi;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.contentproviders.BaseContentProvider;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
@@ -46,7 +47,7 @@ import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.mms.PartUriParser;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.MemoryFileUtil;
|
||||
import org.signal.core.util.MemoryFileUtil;
|
||||
import org.signal.core.util.Util;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
+8
-8
@@ -18,14 +18,14 @@ import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.ui.BottomSheetUtil;
|
||||
import org.signal.core.ui.util.ThemeUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.qr.QrView;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.qr.QrCodeUtil;
|
||||
import org.signal.core.ui.BottomSheetUtil;
|
||||
import org.signal.core.ui.util.ThemeUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -131,11 +131,11 @@ public class GroupLinkShareQrDialogFragment extends DialogFragment {
|
||||
|
||||
byte[] bytes = byteArrayOutputStream.toByteArray();
|
||||
|
||||
return BlobProvider.getInstance()
|
||||
.forData(bytes)
|
||||
.withMimeType("image/png")
|
||||
.withFileName("SignalGroupQr.png")
|
||||
.createForSingleSessionInMemory();
|
||||
return AppDependencies.getBlobs()
|
||||
.forData(bytes)
|
||||
.withMimeType("image/png")
|
||||
.withFileName("SignalGroupQr.png")
|
||||
.createForSingleSessionInMemory();
|
||||
} finally {
|
||||
qrBitmap.recycle();
|
||||
}
|
||||
|
||||
+3
-2
@@ -24,6 +24,7 @@ import org.signal.archive.LocalBackupRestoreProgress
|
||||
import org.signal.core.models.AccountEntropyPool
|
||||
import org.signal.core.models.MasterKey
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.registration.PreExistingRegistrationData
|
||||
import org.signal.registration.StorageController
|
||||
@@ -38,7 +39,7 @@ import org.thoughtcrime.securesms.backup.v2.RemoteRestoreResult
|
||||
import org.thoughtcrime.securesms.backup.v2.RestoreV2Event
|
||||
import org.thoughtcrime.securesms.backup.v2.local.LocalArchiver
|
||||
import org.thoughtcrime.securesms.backup.v2.local.SnapshotFileSystem
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
|
||||
@@ -278,7 +279,7 @@ class AppRegistrationStorageController(private val context: Context) : StorageCo
|
||||
val database = SignalDatabase.backupDatabase
|
||||
FullBackupImporter.importFile(
|
||||
context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore).getOrCreateAttachmentSecret(),
|
||||
database,
|
||||
uri,
|
||||
passphrase,
|
||||
|
||||
@@ -9,11 +9,12 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.AppInitialization
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase
|
||||
import org.thoughtcrime.securesms.backup.FullBackupImporter
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.DataRestoreConstraint
|
||||
@@ -59,7 +60,7 @@ object RestoreRepository {
|
||||
|
||||
FullBackupImporter.importFile(
|
||||
context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore).getOrCreateAttachmentSecret(),
|
||||
database,
|
||||
backupFileUri,
|
||||
passphrase,
|
||||
|
||||
+2
-3
@@ -20,13 +20,12 @@ import com.bumptech.glide.Glide;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.glide.decryptableuri.DecryptableUri;
|
||||
import org.signal.video.VideoPlayer;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.signal.video.VideoPlayer;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -88,7 +87,7 @@ public class ViewOnceMessageActivity extends PassphraseRequiredActivity implemen
|
||||
super.onStop();
|
||||
cancelDurationUpdate();
|
||||
video.cleanup();
|
||||
BlobProvider.getInstance().delete(this, uri);
|
||||
AppDependencies.getBlobs().delete(this, uri);
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,10 @@ import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.signal.core.util.FontUtil;
|
||||
import org.signal.core.util.ParcelUtil;
|
||||
import org.signal.core.util.ThrottledDebouncer;
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -46,6 +49,8 @@ import org.signal.imageeditor.core.renderers.BezierDrawingRenderer;
|
||||
import org.signal.imageeditor.core.renderers.FaceBlurRenderer;
|
||||
import org.signal.imageeditor.core.renderers.MultiLineTextRenderer;
|
||||
import org.signal.imageeditor.core.renderers.UriGlideRenderer;
|
||||
import org.signal.mediasend.MediaConstraints;
|
||||
import org.signal.mediasend.SentMediaQuality;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.ResizeAnimation;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentSaver;
|
||||
@@ -54,20 +59,14 @@ import org.thoughtcrime.securesms.fonts.FontTypefaceProvider;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendPageFragment;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaAnimations;
|
||||
import org.signal.mediasend.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.PushMediaConstraints;
|
||||
import org.signal.mediasend.SentMediaQuality;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.scribbles.stickers.AnalogClockStickerRenderer;
|
||||
import org.thoughtcrime.securesms.scribbles.stickers.DigitalClockStickerRenderer;
|
||||
import org.thoughtcrime.securesms.scribbles.stickers.FeatureSticker;
|
||||
import org.thoughtcrime.securesms.scribbles.stickers.TappableRenderer;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.signal.core.util.ParcelUtil;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.signal.core.util.ThrottledDebouncer;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
@@ -76,10 +75,9 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import kotlin.Pair;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
@@ -813,10 +811,10 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
||||
image.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
|
||||
image.recycle();
|
||||
|
||||
return BlobProvider.getInstance()
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleUseInMemory();
|
||||
return AppDependencies.getBlobs()
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleUseInMemory();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -827,10 +825,10 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
||||
image.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
|
||||
image.recycle();
|
||||
|
||||
return BlobProvider.getInstance()
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionInMemory();
|
||||
return AppDependencies.getBlobs()
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionInMemory();
|
||||
}
|
||||
|
||||
private void onDrawingChanged(boolean stillTouching, boolean isUserEdit) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.models.media.Media
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.UriUtil
|
||||
@@ -49,7 +49,7 @@ class ShareRepository(context: Context) {
|
||||
} ?: return ResolvedShareData.Failure
|
||||
|
||||
val blobUri: Uri = try {
|
||||
BlobProvider.getInstance()
|
||||
AppDependencies.blobs
|
||||
.forData(stream, size)
|
||||
.withMimeType(mimeType)
|
||||
.withFileName(name)
|
||||
@@ -94,7 +94,7 @@ class ShareRepository(context: Context) {
|
||||
val dimens: Pair<Int, Int> = MediaUtil.getDimensions(appContext, mimeType, uri)
|
||||
val duration = 0L
|
||||
val blobUri = try {
|
||||
BlobProvider.getInstance()
|
||||
AppDependencies.blobs
|
||||
.forData(stream, size)
|
||||
.withMimeType(mimeType)
|
||||
.createForSingleSessionOnDisk(appContext)
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.sharing.v2.ShareActivity
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel
|
||||
import org.thoughtcrime.securesms.stories.landing.StoriesLandingItem
|
||||
@@ -88,7 +88,7 @@ object StoryContextMenu {
|
||||
bitmap.recycle()
|
||||
|
||||
SaveAttachmentUtil.SaveAttachment(
|
||||
uri = BlobProvider.getInstance().forData(jpeg.readBytes()).createForSingleUseInMemory(),
|
||||
uri = AppDependencies.blobs.forData(jpeg.readBytes()).createForSingleUseInMemory(),
|
||||
contentType = MediaUtil.IMAGE_JPEG,
|
||||
date = messageRecord.dateSent,
|
||||
fileName = null
|
||||
|
||||
@@ -2,9 +2,10 @@ package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream
|
||||
import org.signal.core.util.crypto.ModernEncryptingPartOutputStream
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
@@ -15,13 +16,13 @@ import java.io.OutputStream
|
||||
object EncryptedStreamUtils {
|
||||
@WorkerThread
|
||||
fun getOutputStream(context: Context, outputFile: File): OutputStream {
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context).orCreateAttachmentSecret
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore).orCreateAttachmentSecret
|
||||
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun getInputStream(context: Context, inputFile: File): InputStream {
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context).orCreateAttachmentSecret
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore).orCreateAttachmentSecret
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, inputFile, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A function which takes 1 input and returns 1 output, and is capable of throwing an IO Exception.
|
||||
*/
|
||||
public interface IOFunction<I, O> {
|
||||
O apply(I input) throws IOException;
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import android.media.ThumbnailUtils;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import kotlin.Pair;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -26,16 +25,17 @@ import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
||||
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.signal.core.util.ContentTypeUtil;
|
||||
import org.signal.core.util.bitmaps.BitmapDecodingException;
|
||||
import org.signal.core.util.bitmaps.BitmapUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.signal.core.models.database.AttachmentId;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.signal.glide.decryptableuri.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
import org.thoughtcrime.securesms.mms.GifSlide;
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
@@ -46,7 +46,7 @@ import org.thoughtcrime.securesms.mms.StickerSlide;
|
||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.mms.ViewOnceSlide;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.video.MediaDataSourceProvider;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -55,6 +55,8 @@ import java.net.URLConnection;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
public class MediaUtil {
|
||||
|
||||
private static final String TAG = Log.tag(MediaUtil.class);
|
||||
@@ -293,8 +295,8 @@ public class MediaUtil {
|
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
|
||||
try {
|
||||
if (BlobProvider.isAuthority(uri) && MediaUtil.isVideo(BlobProvider.getMimeType(uri))) {
|
||||
MediaDataSource source = BlobProvider.getInstance().getMediaDataSource(context, uri);
|
||||
if (AppDependencies.getBlobs().isAuthority(uri) && MediaUtil.isVideo(AppDependencies.getBlobs().getMimeType(uri))) {
|
||||
MediaDataSource source = MediaDataSourceProvider.getMediaDataSource(context, uri);
|
||||
retriever.setDataSource(source);
|
||||
} else if (PartAuthority.isAttachmentUri(uri)) {
|
||||
MediaDataSource source = SignalDatabase.attachments().mediaDataSourceFor(PartAuthority.requireAttachmentId(uri), false);
|
||||
@@ -471,7 +473,7 @@ public class MediaUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BlobProvider.isAuthority(uri) && MediaUtil.isVideo(BlobProvider.getMimeType(uri))) {
|
||||
if (AppDependencies.getBlobs().isAuthority(uri) && MediaUtil.isVideo(AppDependencies.getBlobs().getMimeType(uri))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -516,11 +518,11 @@ public class MediaUtil {
|
||||
MediaUtil.isVideo(URLConnection.guessContentTypeFromName(uri.toString()))) {
|
||||
return ThumbnailUtils.createVideoThumbnail(uri.toString().replace("file://", ""),
|
||||
MediaStore.Video.Thumbnails.MINI_KIND);
|
||||
} else if (BlobProvider.isAuthority(uri) &&
|
||||
MediaUtil.isVideo(BlobProvider.getMimeType(uri)))
|
||||
} else if (AppDependencies.getBlobs().isAuthority(uri) &&
|
||||
MediaUtil.isVideo(AppDependencies.getBlobs().getMimeType(uri)))
|
||||
{
|
||||
try {
|
||||
MediaDataSource source = BlobProvider.getInstance().getMediaDataSource(context, uri);
|
||||
MediaDataSource source = MediaDataSourceProvider.getMediaDataSource(context, uri);
|
||||
return extractFrame(source, timeUs);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to extract frame for URI: " + uri, e);
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.MemoryFile;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.ProxyFileDescriptorCallback;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiresApi(api = 26)
|
||||
final class MemoryFileDescriptorProxy {
|
||||
|
||||
private static final String TAG = Log.tag(MemoryFileDescriptorProxy.class);
|
||||
|
||||
public static ParcelFileDescriptor create(@NonNull Context context,
|
||||
@NonNull MemoryFile file)
|
||||
throws IOException
|
||||
{
|
||||
StorageManager storageManager = Objects.requireNonNull(context.getSystemService(StorageManager.class));
|
||||
HandlerThread thread = new HandlerThread("MemoryFile");
|
||||
|
||||
thread.start();
|
||||
Log.i(TAG, "Thread started");
|
||||
|
||||
Handler handler = new Handler(thread.getLooper());
|
||||
ProxyCallback proxyCallback = new ProxyCallback(file, () -> {
|
||||
if (thread.quitSafely()) {
|
||||
Log.i(TAG, "Thread quitSafely true");
|
||||
} else {
|
||||
Log.w(TAG, "Thread quitSafely false");
|
||||
}
|
||||
});
|
||||
|
||||
ParcelFileDescriptor parcelFileDescriptor = storageManager.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_ONLY,
|
||||
proxyCallback,
|
||||
handler);
|
||||
|
||||
Log.i(TAG, "Created");
|
||||
return parcelFileDescriptor;
|
||||
}
|
||||
|
||||
private static final class ProxyCallback extends ProxyFileDescriptorCallback {
|
||||
|
||||
private final MemoryFile memoryFile;
|
||||
private final Runnable onClose;
|
||||
|
||||
ProxyCallback(@NonNull MemoryFile memoryFile, Runnable onClose) {
|
||||
this.memoryFile = memoryFile;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long onGetSize() {
|
||||
return memoryFile.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onRead(long offset, int size, byte[] data) throws ErrnoException {
|
||||
try {
|
||||
InputStream inputStream = memoryFile.getInputStream();
|
||||
if(inputStream.skip(offset) != offset){
|
||||
if (offset > memoryFile.length()) {
|
||||
throw new ErrnoException("onRead", OsConstants.EIO);
|
||||
}
|
||||
|
||||
throw new AssertionError();
|
||||
}
|
||||
return inputStream.read(data, 0, size);
|
||||
} catch (IOException e) {
|
||||
throw new ErrnoException("onRead", OsConstants.EBADF);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease() {
|
||||
Log.i(TAG, "onRelease()");
|
||||
memoryFile.close();
|
||||
onClose.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.os.MemoryFile;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class MemoryFileUtil {
|
||||
|
||||
private MemoryFileUtil() {}
|
||||
|
||||
public static ParcelFileDescriptor getParcelFileDescriptor(@NonNull MemoryFile file)
|
||||
throws IOException
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return MemoryFileDescriptorProxy.create(AppDependencies.getApplication(), file);
|
||||
} else {
|
||||
return getParcelFileDescriptorLegacy(file);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavaReflectionMemberAccess")
|
||||
@SuppressLint("PrivateApi")
|
||||
public static ParcelFileDescriptor getParcelFileDescriptorLegacy(@NonNull MemoryFile file)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
|
||||
FileDescriptor fileDescriptor = (FileDescriptor) method.invoke(file);
|
||||
|
||||
Field field = fileDescriptor.getClass().getDeclaredField("descriptor");
|
||||
field.setAccessible(true);
|
||||
|
||||
int fd = field.getInt(fileDescriptor);
|
||||
|
||||
return ParcelFileDescriptor.adoptFd(fd);
|
||||
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | NoSuchFieldException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.util
|
||||
import android.content.Context
|
||||
import org.signal.core.util.kibiBytes
|
||||
import org.signal.core.util.splitByByteLength
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.mms.TextSlide
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@@ -31,7 +31,7 @@ object MessageUtil {
|
||||
val textData = rawText.toByteArray()
|
||||
val timestamp = SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(Date())
|
||||
val filename = String.format("signal-%s.txt", timestamp)
|
||||
val textUri = BlobProvider.getInstance()
|
||||
val textUri = AppDependencies.blobs
|
||||
.forData(textData)
|
||||
.withMimeType(MediaUtil.LONG_TEXT)
|
||||
.withFileName(filename)
|
||||
|
||||
@@ -6,10 +6,11 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider;
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream;
|
||||
import org.signal.core.util.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -93,12 +94,12 @@ public final class FileStorage {
|
||||
}
|
||||
|
||||
private static @NonNull OutputStream getOutputStream(@NonNull Context context, File outputFile) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret();
|
||||
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).getSecond();
|
||||
}
|
||||
|
||||
private static @NonNull InputStream getInputStream(@NonNull Context context, File inputFile) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore.INSTANCE).getOrCreateAttachmentSecret();
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, inputFile, 0);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ import android.media.MediaDataSource;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.signal.core.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.media.MediaDataSource;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.video
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaDataSource
|
||||
import android.net.Uri
|
||||
import com.squareup.wire.internal.JvmStatic
|
||||
import org.signal.core.util.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Utilizes blob provider to load a blob as a media data source.
|
||||
*/
|
||||
object MediaDataSourceProvider {
|
||||
@JvmStatic
|
||||
@Suppress("MoveLambdaOutsideParentheses")
|
||||
@Throws(IOException::class)
|
||||
fun getMediaDataSource(context: Context, uri: Uri): MediaDataSource {
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context, AppAttachmentSecretStore).getOrCreateAttachmentSecret()
|
||||
return AppDependencies.blobs.getBlobRepresentation(
|
||||
context,
|
||||
uri,
|
||||
::ByteArrayMediaDataSource,
|
||||
{ EncryptedMediaDataSource.createForDiskBlob(attachmentSecret, it!!) }
|
||||
)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,8 +5,8 @@ import android.media.MediaDataSource;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.signal.core.util.crypto.AttachmentSecret;
|
||||
import org.signal.core.util.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.video.videoconverter.mediadatasource.InputStreamMediaDataSource;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -8,13 +8,11 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
@@ -44,15 +42,15 @@ class BlobDataSource implements DataSource {
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
this.dataSpec = dataSpec;
|
||||
this.inputStream = BlobProvider.getInstance().getStream(context, dataSpec.uri, dataSpec.position);
|
||||
this.inputStream = AppDependencies.getBlobs().getStream(context, dataSpec.uri, dataSpec.position);
|
||||
|
||||
if (listener != null) {
|
||||
listener.onTransferStart(this, dataSpec, false);
|
||||
}
|
||||
|
||||
long size = unwrapLong(BlobProvider.getFileSize(dataSpec.uri));
|
||||
long size = unwrapLong(AppDependencies.getBlobs().getFileSize(dataSpec.uri));
|
||||
if (size == 0) {
|
||||
size = BlobProvider.getInstance().calculateFileSize(context, dataSpec.uri);
|
||||
size = AppDependencies.getBlobs().calculateFileSize(context, dataSpec.uri);
|
||||
}
|
||||
|
||||
if (size - dataSpec.position <= 0) throw new EOFException("No more data");
|
||||
|
||||
@@ -7,17 +7,14 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DefaultDataSource;
|
||||
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.DefaultDataSource;
|
||||
import androidx.media3.datasource.DefaultDataSourceFactory;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
@@ -26,7 +23,7 @@ import java.util.Map;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Go-to {@link DataSource} that handles all of our various types of video sources.
|
||||
* Will defer to other {@link DataSource}s depending on the URI.
|
||||
*/
|
||||
@@ -57,7 +54,7 @@ import okhttp3.OkHttpClient;
|
||||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
if (BlobProvider.isAuthority(dataSpec.uri)) {
|
||||
if (AppDependencies.getBlobs().isAuthority(dataSpec.uri)) {
|
||||
dataSource = blobDataSource;
|
||||
} else if (PartAuthority.isLocalUri(dataSpec.uri)) {
|
||||
dataSource = partDataSource;
|
||||
|
||||
Reference in New Issue
Block a user