From a7b4a5d93d83d073ed69c603936a9e6da36c9c6f Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 17 Jun 2026 15:28:25 -0300 Subject: [PATCH] Rip out Camera1. --- .../securesms/database/AttachmentTableTest.kt | 2 +- .../database/AttachmentTableTest_deduping.kt | 2 +- .../jobs/AttachmentCompressionJobTest.kt | 2 +- .../thoughtcrime/securesms/MainActivity.kt | 23 +- .../avatar/picker/AvatarPickerFragment.kt | 22 +- .../processor/AccountDataArchiveProcessor.kt | 17 +- .../components/ZoomingImageView.java | 2 +- .../data/DataAndStorageSettingsFragment.kt | 2 +- .../app/data/DataAndStorageSettingsState.kt | 2 +- .../data/DataAndStorageSettingsViewModel.kt | 2 +- .../ConversationSettingsFragment.kt | 19 +- .../InternalConversationSettingsFragment.kt | 2 +- .../securesms/contactshare/ContactUtil.java | 2 +- .../v2/ConversationActivityResultContracts.kt | 21 +- .../conversation/v2/ConversationFragment.kt | 4 +- .../securesms/database/AttachmentTable.kt | 2 +- .../database/TransformPropertiesUtil.kt | 2 +- .../DecryptableStreamLocalUriFetcher.java | 4 +- .../details/AddGroupDetailsFragment.java | 2 +- .../jobs/AttachmentCompressionJob.java | 26 +- .../securesms/keyboard/KeyboardUtil.kt | 4 +- .../securesms/keyvalue/SettingsValues.java | 2 +- .../linkpreview/LinkPreviewRepository.java | 2 +- .../securesms/maps/PlacePickerActivity.java | 4 +- .../mediasend/AvatarSelectionActivity.java | 23 +- .../mediasend/Camera1Controller.java | 265 --------- .../securesms/mediasend/Camera1Fragment.java | 505 ------------------ .../securesms/mediasend/CameraFragment.java | 80 --- .../securesms/mediasend/CameraXFragment.kt | 12 +- .../mediasend/SentMediaQualityTransform.java | 2 +- .../mediasend/VideoEditorFragment.kt | 2 +- .../securesms/mediasend/VideoTrimTransform.kt | 2 +- .../mediasend/camerax/CameraXRemoteConfig.kt | 22 - .../mediasend/v2/MediaSelectionNavigator.kt | 24 - .../mediasend/v2/MediaSelectionRepository.kt | 4 +- .../mediasend/v2/MediaSelectionState.kt | 7 +- .../mediasend/v2/MediaSelectionViewModel.kt | 7 +- .../securesms/mediasend/v2/MediaValidator.kt | 2 +- .../v2/capture/MediaCaptureFragment.kt | 25 +- .../v2/capture/MediaCaptureRepository.kt | 17 - .../mediasend/v2/capture/MediaCaptureState.kt | 7 - .../v2/capture/MediaCaptureViewModel.kt | 16 - .../v2/gallery/MediaGalleryFragment.kt | 21 +- .../gallery/MediaSelectionGalleryFragment.kt | 5 +- .../v2/review/MediaReviewFragment.kt | 4 +- .../v2/review/QualitySelectorBottomSheet.kt | 2 +- .../mediasend/v3/MediaSendV3CameraSlot.kt | 28 - .../mediasend/v3/MediaSendV3Repository.kt | 23 +- .../securesms/mms/AttachmentManager.java | 3 +- .../securesms/mms/MediaConstraints.java | 130 ----- .../securesms/mms/PushMediaConstraints.java | 8 + .../securesms/mms/SlideFactory.java | 1 + .../v2/NotificationExtensions.kt | 2 +- .../v2/NotificationThumbnails.kt | 2 +- .../PaymentsTransferQrScanFragment.java | 7 +- .../profiles/ProfileMediaConstraints.java | 8 +- .../securesms/profiles/SystemProfileUtil.java | 26 +- .../scribbles/ImageEditorFragment.java | 4 +- .../securesms/sharing/MultiShareSender.java | 2 +- .../thoughtcrime/securesms/stories/Stories.kt | 4 +- .../stories/dialogs/StoryContextMenu.kt | 2 +- .../securesms/util/ImageCompressionUtil.java | 1 + .../securesms/util/MediaUtil.java | 2 + .../securesms/util/RemoteConfig.kt | 7 - .../securesms/verify/VerifyScanFragment.kt | 3 +- .../securesms/video/VideoUtil.java | 5 +- .../crop/WallpaperCropViewModel.java | 2 +- app/src/main/res/values/strings.xml | 7 - ...chmentDatabaseTransformPropertiesTest.java | 2 +- .../sms/UploadDependencyGraphTest.kt | 2 +- core/util/build.gradle.kts | 1 + .../bitmaps}/BitmapDecodingException.java | 7 +- .../signal/core/util/bitmaps}/BitmapUtil.java | 160 +----- .../signal/mediasend/MediaConstraints.java | 102 ++++ .../mediasend/MediaSendActivityContract.kt | 2 +- .../signal/mediasend/MediaSendRepository.kt | 10 +- .../org/signal/mediasend/MediaSendState.kt | 5 +- .../signal/mediasend/MediaSendViewModel.kt | 2 +- .../org/signal/mediasend}/SentMediaQuality.kt | 12 +- .../mediasend/capture/CameraFragment.java | 30 ++ .../src/main/res/values/strings.xml | 4 + .../main/java/org/signal/qr/QrScannerView.kt | 13 +- 82 files changed, 337 insertions(+), 1518 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Controller.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXRemoteConfig.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureState.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3CameraSlot.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mms/MediaConstraints.java rename {app/src/main/java/org/thoughtcrime/securesms/util => core/util/src/main/java/org/signal/core/util/bitmaps}/BitmapDecodingException.java (61%) rename {app/src/main/java/org/thoughtcrime/securesms/util => core/util/src/main/java/org/signal/core/util/bitmaps}/BitmapUtil.java (60%) create mode 100644 feature/media-send/src/main/java/org/signal/mediasend/MediaConstraints.java rename {app/src/main/java/org/thoughtcrime/securesms/mms => feature/media-send/src/main/java/org/signal/mediasend}/SentMediaQuality.kt (71%) create mode 100644 feature/media-send/src/main/java/org/signal/mediasend/capture/CameraFragment.java diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt index 70754b89eb..af6fb3d298 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt @@ -26,6 +26,7 @@ import org.signal.core.util.Base64 import org.signal.core.util.Base64.decodeBase64OrThrow import org.signal.core.util.copyTo import org.signal.core.util.stream.NullOutputStream +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.attachments.ArchivedAttachment import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.attachments.PointerAttachment @@ -34,7 +35,6 @@ import org.thoughtcrime.securesms.backup.v2.ArchivedMediaObject import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mms.IncomingMessage import org.thoughtcrime.securesms.mms.MediaStream -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.util.MediaUtil diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt index f4167f4a18..887349d0ef 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt @@ -19,13 +19,13 @@ import org.signal.core.util.Util import org.signal.core.util.readFully import org.signal.core.util.stream.LimitedInputStream import org.signal.core.util.update +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.attachments.Cdn import org.thoughtcrime.securesms.attachments.PointerAttachment import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mms.MediaStream import org.thoughtcrime.securesms.mms.OutgoingMessage import org.thoughtcrime.securesms.mms.QuoteModel -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.MediaUtil diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJobTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJobTest.kt index babb0c1e2f..2d5622f55e 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJobTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJobTest.kt @@ -15,13 +15,13 @@ import org.junit.Test import org.junit.runner.RunWith import org.signal.core.models.media.TransformProperties import org.signal.core.util.StreamUtil +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.attachments.UriAttachment import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.UriAttachmentBuilder import org.thoughtcrime.securesms.database.transformPropertiesForSentMediaQuality import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.jobmanager.Job -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.util.MediaUtil diff --git a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt index f800655f0a..854157e77f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt @@ -5,7 +5,6 @@ package org.thoughtcrime.securesms -import android.Manifest import android.annotation.SuppressLint import android.app.Activity import android.content.Context @@ -15,7 +14,6 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver -import android.widget.Toast import androidx.activity.SystemBarStyle import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent @@ -162,7 +160,6 @@ import org.thoughtcrime.securesms.main.navigateToDetailLocation import org.thoughtcrime.securesms.main.rememberDetailNavHostController import org.thoughtcrime.securesms.main.rememberFocusRequester import org.thoughtcrime.securesms.main.storiesNavGraphBuilder -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity import org.thoughtcrime.securesms.mediasend.v3.mediaSendLauncher import org.thoughtcrime.securesms.megaphone.Megaphone @@ -197,7 +194,6 @@ import org.thoughtcrime.securesms.window.NavigationType import org.thoughtcrime.securesms.window.rememberThreePaneScaffoldNavigatorDelegate import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState import kotlin.time.Duration.Companion.minutes -import org.signal.core.ui.R as CoreUiR class MainActivity : PassphraseRequiredActivity(), @@ -1176,24 +1172,7 @@ class MainActivity : } } - if (CameraXRemoteConfig.isSupported()) { - onGranted() - } else { - Permissions.with(this@MainActivity) - .request(Manifest.permission.CAMERA) - .ifNecessary() - .withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_capture_photos_and_video_allow_camera), CoreUiR.drawable.symbol_camera_24) - .withPermanentDenialDialog( - getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), - null, - R.string.CameraXFragment_allow_access_camera, - R.string.CameraXFragment_to_capture_photos_videos, - supportFragmentManager - ) - .onAllGranted(onGranted) - .onAnyDenied { Toast.makeText(this@MainActivity, R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show() } - .execute() - } + onGranted() } inner class ToolbarCallback : MainToolbarCallback { diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/picker/AvatarPickerFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/picker/AvatarPickerFragment.kt index f501569829..21573390e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/picker/AvatarPickerFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/picker/AvatarPickerFragment.kt @@ -1,13 +1,11 @@ package org.thoughtcrime.securesms.avatar.picker -import android.Manifest import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.Gravity import android.view.View import android.widget.PopupMenu -import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment @@ -30,12 +28,10 @@ import org.thoughtcrime.securesms.avatar.vector.VectorAvatarCreationFragment import org.thoughtcrime.securesms.components.ButtonStripItemView import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.util.visible -import org.signal.core.ui.R as CoreUiR /** * Primary Avatar picker fragment, displays current user avatar and a list of recently used avatars and defaults. @@ -223,22 +219,8 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) { @Suppress("DEPRECATION") private fun openCameraCapture() { - if (CameraXRemoteConfig.isSupported()) { - val intent = AvatarSelectionActivity.getIntentForCameraCapture(requireContext()) - startActivityForResult(intent, REQUEST_CODE_SELECT_IMAGE) - } else { - Permissions.with(this) - .request(Manifest.permission.CAMERA) - .ifNecessary() - .onAllGranted { - val intent = AvatarSelectionActivity.getIntentForCameraCapture(requireContext()) - startActivityForResult(intent, REQUEST_CODE_SELECT_IMAGE) - } - .withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_capture_photos_allow_camera), CoreUiR.drawable.symbol_camera_24) - .withPermanentDenialDialog(getString(R.string.AvatarSelectionBottomSheetDialogFragment__taking_a_photo_requires_the_camera_permission), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos, getParentFragmentManager()) - .onAnyDenied { Toast.makeText(requireContext(), R.string.AvatarSelectionBottomSheetDialogFragment__taking_a_photo_requires_the_camera_permission, Toast.LENGTH_SHORT).show() } - .execute() - } + val intent = AvatarSelectionActivity.getIntentForCameraCapture(requireContext()) + startActivityForResult(intent, REQUEST_CODE_SELECT_IMAGE) } @Suppress("DEPRECATION") diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataArchiveProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataArchiveProcessor.kt index 315f494945..e8d2fcd25e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataArchiveProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataArchiveProcessor.kt @@ -17,6 +17,7 @@ import org.signal.core.util.UuidUtil import org.signal.core.util.logging.Log import org.signal.core.util.toByteArray import org.signal.libsignal.zkgroup.backups.BackupLevel +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.backup.v2.ExportState import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.MessageBackupTier @@ -471,19 +472,19 @@ object AccountDataArchiveProcessor { } } - private fun org.thoughtcrime.securesms.mms.SentMediaQuality.toRemoteSentMediaQuality(): AccountData.SentMediaQuality { + private fun SentMediaQuality.toRemoteSentMediaQuality(): AccountData.SentMediaQuality { return when (this) { - org.thoughtcrime.securesms.mms.SentMediaQuality.STANDARD -> AccountData.SentMediaQuality.STANDARD - org.thoughtcrime.securesms.mms.SentMediaQuality.HIGH -> AccountData.SentMediaQuality.HIGH + SentMediaQuality.STANDARD -> AccountData.SentMediaQuality.STANDARD + SentMediaQuality.HIGH -> AccountData.SentMediaQuality.HIGH } } - private fun AccountData.SentMediaQuality?.toLocalSentMediaQuality(): org.thoughtcrime.securesms.mms.SentMediaQuality { + private fun AccountData.SentMediaQuality?.toLocalSentMediaQuality(): SentMediaQuality { return when (this) { - AccountData.SentMediaQuality.HIGH -> org.thoughtcrime.securesms.mms.SentMediaQuality.HIGH - AccountData.SentMediaQuality.STANDARD -> org.thoughtcrime.securesms.mms.SentMediaQuality.STANDARD - AccountData.SentMediaQuality.UNKNOWN_QUALITY -> org.thoughtcrime.securesms.mms.SentMediaQuality.STANDARD - null -> org.thoughtcrime.securesms.mms.SentMediaQuality.STANDARD + AccountData.SentMediaQuality.HIGH -> SentMediaQuality.HIGH + AccountData.SentMediaQuality.STANDARD -> SentMediaQuality.STANDARD + AccountData.SentMediaQuality.UNKNOWN_QUALITY -> SentMediaQuality.STANDARD + null -> SentMediaQuality.STANDARD } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java index 8cd9a99e74..d9fa516b9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ZoomingImageView.java @@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder import org.signal.glide.decryptableuri.DecryptableUri; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.ActionRequestListener; -import org.thoughtcrime.securesms.util.BitmapUtil; +import org.signal.core.util.bitmaps.BitmapUtil; import org.thoughtcrime.securesms.util.MediaUtil; import java.io.IOException; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt index 34113a9c11..d7dd7f6483 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsFragment.kt @@ -25,9 +25,9 @@ import org.signal.core.ui.compose.Scaffolds import org.signal.core.ui.compose.SignalIcons import org.signal.core.ui.compose.Texts import org.signal.core.util.bytes +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.compose.rememberStatusBarColorNestedScrollModifier -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.util.AttachmentUtil import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.webrtc.CallDataMode diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt index 57acd3dd8f..087139a2fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsState.kt @@ -1,6 +1,6 @@ package org.thoughtcrime.securesms.components.settings.app.data -import org.thoughtcrime.securesms.mms.SentMediaQuality +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.webrtc.CallDataMode data class DataAndStorageSettingsState( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt index ad54f97069..26af1639e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsViewModel.kt @@ -6,11 +6,11 @@ import androidx.lifecycle.ViewModelProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.keyvalue.SettingsValues.ForceWebsocketMode import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.messages.IncomingMessageObserver -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.util.PlayServicesUtil import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.webrtc.CallDataMode diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt index 855e73c631..bf4d4f9f38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.components.settings.conversation -import android.Manifest import android.app.ActivityOptions import android.content.ActivityNotFoundException import android.content.Context @@ -96,7 +95,6 @@ import org.thoughtcrime.securesms.main.MainNavigationChatDetailRouter import org.thoughtcrime.securesms.main.MainNavigationDetailLocation import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository import org.thoughtcrime.securesms.nicknames.NicknameActivity import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity @@ -481,23 +479,8 @@ class ConversationSettingsFragment : .setMessage(R.string.ConversationSettingsFragment__only_admins_of_this_group_can_add_to_its_story) .setPositiveButton(android.R.string.ok) { d, _ -> d.dismiss() } .show() - } else if (CameraXRemoteConfig.isSupported()) { - addToGroupStoryDelegate.addToStory(state.recipient.id) } else { - Permissions.with(this@ConversationSettingsFragment) - .request(Manifest.permission.CAMERA) - .ifNecessary() - .withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_capture_photos_and_video_allow_camera), CoreUiR.drawable.symbol_camera_24) - .withPermanentDenialDialog( - getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), - null, - R.string.CameraXFragment_allow_access_camera, - R.string.CameraXFragment_to_capture_photos_videos, - getParentFragmentManager() - ) - .onAllGranted { addToGroupStoryDelegate.addToStory(state.recipient.id) } - .onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show() } - .execute() + addToGroupStoryDelegate.addToStory(state.recipient.id) } }, onVideoClick = { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt index fcb402ef46..371088db49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.signal.core.ui.compose.ComposeFragment import org.signal.core.util.Util +import org.signal.core.util.bitmaps.BitmapUtil import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.isAbsent import org.signal.core.util.logging.Log @@ -42,7 +43,6 @@ import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientForeverObserver import org.thoughtcrime.securesms.recipients.RecipientId -import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.MediaUtil import java.util.Objects import kotlin.random.Random diff --git a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactUtil.java b/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactUtil.java index 0ae5bc9415..1911c2005a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactUtil.java @@ -28,7 +28,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.BitmapDecodingException; +import org.signal.core.util.bitmaps.BitmapDecodingException; import org.thoughtcrime.securesms.util.ImageCompressionUtil; import org.thoughtcrime.securesms.util.SignalE164Util; import org.thoughtcrime.securesms.util.SpanUtil; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityResultContracts.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityResultContracts.kt index fda768417e..f81c0c4b23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityResultContracts.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityResultContracts.kt @@ -25,14 +25,11 @@ import org.thoughtcrime.securesms.contactshare.Contact import org.thoughtcrime.securesms.contactshare.ContactShareEditActivity import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.conversation.colors.ChatColors -import org.thoughtcrime.securesms.conversation.v2.ConversationActivityResultContracts.Callbacks import org.thoughtcrime.securesms.giph.ui.GiphyActivity import org.thoughtcrime.securesms.maps.PlacePickerActivity import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity import org.thoughtcrime.securesms.recipients.RecipientId -import org.signal.core.ui.R as CoreUiR /** * This encapsulates the logic for interacting with other activities used throughout a conversation. The gist @@ -76,22 +73,8 @@ class ConversationActivityResultContracts(private val fragment: Fragment, privat } fun launchCamera(recipientId: RecipientId, isReply: Boolean) { - if (CameraXRemoteConfig.isSupported()) { - cameraLauncher.launch(MediaSelectionInput(emptyList(), recipientId, null, isReply)) - fragment.requireActivity().overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary) - } else { - Permissions.with(fragment) - .request(Manifest.permission.CAMERA) - .ifNecessary() - .withRationaleDialog(fragment.getString(R.string.CameraXFragment_allow_access_camera), fragment.getString(R.string.CameraXFragment_to_capture_photos_and_video_allow_camera), CoreUiR.drawable.symbol_camera_24) - .withPermanentDenialDialog(fragment.getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos_videos, fragment.parentFragmentManager) - .onAllGranted { - cameraLauncher.launch(MediaSelectionInput(emptyList(), recipientId, null, isReply)) - fragment.requireActivity().overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary) - } - .onAnyDenied { Toast.makeText(fragment.requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show() } - .execute() - } + cameraLauncher.launch(MediaSelectionInput(emptyList(), recipientId, null, isReply)) + fragment.requireActivity().overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary) } fun launchMediaEditor(mediaList: List, recipientId: RecipientId, text: CharSequence?) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 9ac2b1b10e..78d3e59609 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -310,7 +310,7 @@ import org.thoughtcrime.securesms.mms.AudioSlide import org.thoughtcrime.securesms.mms.DocumentSlide import org.thoughtcrime.securesms.mms.GifSlide import org.thoughtcrime.securesms.mms.ImageSlide -import org.thoughtcrime.securesms.mms.MediaConstraints +import org.thoughtcrime.securesms.mms.PushMediaConstraints import org.thoughtcrime.securesms.mms.QuoteModel import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.mms.SlideDeck @@ -2169,7 +2169,7 @@ class ConversationFragment : inputPanel.clickOnComposeInput() } - is ShareOrDraftData.SetLocation -> attachmentManager.setLocation(data.location, MediaConstraints.getPushMediaConstraints()) + is ShareOrDraftData.SetLocation -> attachmentManager.setLocation(data.location, PushMediaConstraints(null)) is ShareOrDraftData.SetEditMessage -> { composeText.setDraftText(data.draftText) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt index be78011c3b..1bc176d0e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt @@ -37,6 +37,7 @@ import org.signal.core.util.SqlUtil import org.signal.core.util.ThreadUtil import org.signal.core.util.Util 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.delete @@ -102,7 +103,6 @@ import org.thoughtcrime.securesms.mms.MediaStream import org.thoughtcrime.securesms.mms.MmsException import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.stickers.StickerLocator -import org.thoughtcrime.securesms.util.BitmapDecodingException import org.thoughtcrime.securesms.util.FileUtils import org.thoughtcrime.securesms.util.ImageCompressionUtil import org.thoughtcrime.securesms.util.MediaUtil diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/TransformPropertiesUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/database/TransformPropertiesUtil.kt index 2dfc29bd2e..a3c82da95f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/TransformPropertiesUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/TransformPropertiesUtil.kt @@ -8,8 +8,8 @@ package org.thoughtcrime.securesms.database import kotlinx.serialization.json.Json import org.signal.core.models.media.TransformProperties import org.signal.core.util.logging.Log +import org.signal.mediasend.SentMediaQuality import org.signal.network.util.JsonUtil -import org.thoughtcrime.securesms.mms.SentMediaQuality import java.io.IOException import java.util.Optional diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/DecryptableStreamLocalUriFetcher.java b/app/src/main/java/org/thoughtcrime/securesms/glide/DecryptableStreamLocalUriFetcher.java index 02afad6830..f662818d49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/DecryptableStreamLocalUriFetcher.java +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/DecryptableStreamLocalUriFetcher.java @@ -18,8 +18,8 @@ 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.thoughtcrime.securesms.util.BitmapDecodingException; -import org.thoughtcrime.securesms.util.BitmapUtil; +import org.signal.core.util.bitmaps.BitmapDecodingException; +import org.signal.core.util.bitmaps.BitmapUtil; import org.thoughtcrime.securesms.util.MediaUtil; import java.io.ByteArrayInputStream; diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsFragment.java index 4267cea2c1..1c15b812a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/details/AddGroupDetailsFragment.java @@ -48,7 +48,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity; -import org.thoughtcrime.securesms.util.BitmapUtil; +import org.signal.core.util.bitmaps.BitmapUtil; import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.ViewUtil; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java index dd97b106ef..5ede7b36ed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java @@ -28,13 +28,14 @@ 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.thoughtcrime.securesms.mms.MediaConstraints; +import org.signal.mediasend.MediaConstraints; import org.thoughtcrime.securesms.mms.MediaStream; import org.thoughtcrime.securesms.mms.MmsException; -import org.thoughtcrime.securesms.mms.SentMediaQuality; +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.thoughtcrime.securesms.util.BitmapDecodingException; +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; @@ -163,7 +164,7 @@ public final class AttachmentCompressionJob extends BaseJob { return; } - MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(SentMediaQuality.fromCode(transformProperties.sentMediaQuality)); + MediaConstraints mediaConstraints = new PushMediaConstraints(SentMediaQuality.fromCode(transformProperties.sentMediaQuality)); compress(database, mediaConstraints, databaseAttachment); } @@ -196,16 +197,16 @@ public final class AttachmentCompressionJob extends BaseJob { } else if (MediaUtil.isVideo(attachment)) { Log.i(TAG, "Compressing video."); attachment = transcodeVideoIfNeededToDatabase(context, attachmentDatabase, attachment, constraints, EventBus.getDefault(), this::isCanceled); - if (!constraints.isSatisfied(context, attachment)) { + if (!isConstraintsSatisfied(context, attachment, constraints)) { throw new UndeliverableMessageException("Size constraints could not be met on video!"); } - } else if (constraints.canResize(attachment)) { + } else if (constraints.canResize(attachment.contentType)) { Log.i(TAG, "Compressing image."); try (MediaStream converted = compressImage(context, attachment, constraints)) { attachmentDatabase.updateAttachmentData(attachment, converted); } attachmentDatabase.markAttachmentAsTransformed(attachmentId, false); - } else if (constraints.isSatisfied(context, attachment)) { + } else if (isConstraintsSatisfied(context, attachment, constraints)) { Log.i(TAG, "Not compressing."); attachmentDatabase.markAttachmentAsTransformed(attachmentId, false); } else { @@ -376,6 +377,17 @@ public final class AttachmentCompressionJob extends BaseJob { return attachment; } + private static boolean isConstraintsSatisfied(@NonNull Context context, + @NonNull Attachment attachment, + @NonNull MediaConstraints mediaConstraints) + { + if (attachment.getUri() == null || attachment.contentType == null) { + return false; + } + + return mediaConstraints.isSatisfied(context, attachment.getUri(), attachment.contentType, attachment.size); + } + /** * Compresses the images. Given that we compress every image, this has the fun side effect of * stripping all EXIF data. diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/KeyboardUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/KeyboardUtil.kt index 52b25bf3d9..b6498b4c6d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyboard/KeyboardUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/KeyboardUtil.kt @@ -7,10 +7,8 @@ package org.thoughtcrime.securesms.keyboard import android.net.Uri import androidx.annotation.WorkerThread -import androidx.core.util.component1 -import androidx.core.util.component2 +import org.signal.core.util.bitmaps.BitmapUtil import org.thoughtcrime.securesms.dependencies.AppDependencies -import org.thoughtcrime.securesms.util.BitmapUtil import java.util.concurrent.ExecutionException import java.util.concurrent.TimeoutException diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java index e5338de961..d7d265b8d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -12,7 +12,7 @@ import androidx.lifecycle.LiveData; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.AppDependencies; -import org.thoughtcrime.securesms.mms.SentMediaQuality; +import org.signal.mediasend.SentMediaQuality; import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; import org.thoughtcrime.securesms.util.Environment; import org.thoughtcrime.securesms.util.SingleLiveEvent; diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 4f0d355f0d..ce151b9e4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -49,7 +49,7 @@ 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.thoughtcrime.securesms.util.BitmapDecodingException; +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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/maps/PlacePickerActivity.java b/app/src/main/java/org/thoughtcrime/securesms/maps/PlacePickerActivity.java index 8bcce8ef57..0e81800a7a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/maps/PlacePickerActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/maps/PlacePickerActivity.java @@ -12,7 +12,6 @@ import android.location.Address; import android.location.Geocoder; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.view.View; import android.view.animation.OvershootInterpolator; @@ -25,7 +24,6 @@ import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; -import com.google.accompanist.permissions.PermissionsUtilKt; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapView; @@ -38,7 +36,7 @@ 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.thoughtcrime.securesms.util.BitmapUtil; +import org.signal.core.util.bitmaps.BitmapUtil; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.MediaUtil; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionActivity.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionActivity.java index bc72a95ccc..cce1086261 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionActivity.java @@ -6,7 +6,6 @@ import android.graphics.Point; import android.net.Uri; import android.os.Bundle; import android.view.WindowManager; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -17,9 +16,11 @@ import androidx.fragment.app.FragmentTransaction; import org.signal.core.models.media.Media; import org.signal.imageeditor.core.model.EditorModel; +import org.signal.mediasend.MediaConstraints; +import org.signal.mediasend.capture.CameraFragment; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mediasend.v2.gallery.MediaGalleryFragment; -import org.thoughtcrime.securesms.mms.MediaConstraints; +import org.thoughtcrime.securesms.mms.PushMediaConstraints; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.scribbles.ImageEditorFragment; @@ -27,9 +28,6 @@ import org.thoughtcrime.securesms.util.MediaUtil; import java.io.FileDescriptor; import java.util.Collections; -import java.util.Optional; - -import io.reactivex.rxjava3.core.Flowable; public class AvatarSelectionActivity extends AppCompatActivity implements CameraFragment.Controller, ImageEditorFragment.Controller, MediaGalleryFragment.Callbacks { @@ -78,12 +76,6 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera } } - @Override - public void onCameraError() { - Toast.makeText(this, androidx.biometric.R.string.default_error_msg, Toast.LENGTH_SHORT).show(); - finish(); - } - @Override public void onImageCaptured(@NonNull byte[] data, int width, int height) { Uri blobUri = BlobProvider.getInstance() @@ -138,14 +130,9 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera throw new UnsupportedOperationException("Cannot select more than one photo"); } - @Override - public @NonNull Flowable> getMostRecentMediaItem() { - return Flowable.just(Optional.empty()); - } - @Override public @NonNull MediaConstraints getMediaConstraints() { - return MediaConstraints.getPushMediaConstraints(); + return new PushMediaConstraints(null); } @Override @@ -265,7 +252,7 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera return; } - Fragment fragment = CameraFragment.newInstanceForAvatarCapture(); + Fragment fragment = CameraXFragment.newInstanceForAvatarCapture(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction() .replace(R.id.fragment_container, fragment, IMAGE_CAPTURE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Controller.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Controller.java deleted file mode 100644 index 3f54a684a8..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Controller.java +++ /dev/null @@ -1,265 +0,0 @@ -package org.thoughtcrime.securesms.mediasend; - -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.view.Surface; - -import androidx.annotation.NonNull; - -import org.signal.core.util.logging.Log; - -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -class Camera1Controller { - - private static final String TAG = Log.tag(Camera1Controller.class); - - private final int screenWidth; - private final int screenHeight; - private final OrderEnforcer enforcer; - private final EventListener eventListener; - - private Camera camera; - private int cameraId; - private SurfaceTexture previewSurface; - private int screenRotation; - - Camera1Controller(int preferredDirection, int screenWidth, int screenHeight, @NonNull EventListener eventListener) { - this.eventListener = eventListener; - this.enforcer = new OrderEnforcer<>(Stage.INITIALIZED, Stage.PREVIEW_STARTED); - this.cameraId = Camera.getNumberOfCameras() > 1 ? preferredDirection : Camera.CameraInfo.CAMERA_FACING_BACK; - this.screenWidth = screenWidth; - this.screenHeight = screenHeight; - } - - void initialize() { - Log.d(TAG, "initialize()"); - - if (Camera.getNumberOfCameras() <= 0) { - Log.w(TAG, "Device doesn't have any cameras."); - onCameraUnavailable(); - return; - } - - try { - camera = Camera.open(cameraId); - } catch (Exception e) { - Log.w(TAG, "Failed to open camera.", e); - onCameraUnavailable(); - return; - } - - if (camera == null) { - Log.w(TAG, "Null camera instance."); - onCameraUnavailable(); - return; - } - - Camera.Parameters params = camera.getParameters(); - Camera.Size previewSize = getClosestSize(camera.getParameters().getSupportedPreviewSizes(), screenWidth, screenHeight); - Camera.Size pictureSize = getClosestSize(camera.getParameters().getSupportedPictureSizes(), screenWidth, screenHeight); - final List focusModes = params.getSupportedFocusModes(); - - Log.d(TAG, "Preview size: " + previewSize.width + "x" + previewSize.height + " Picture size: " + pictureSize.width + "x" + pictureSize.height); - - params.setPreviewSize(previewSize.width, previewSize.height); - params.setPictureSize(pictureSize.width, pictureSize.height); - params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); - params.setColorEffect(Camera.Parameters.EFFECT_NONE); - params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO); - - if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { - params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { - params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); - } - - - camera.setParameters(params); - - enforcer.markCompleted(Stage.INITIALIZED); - - eventListener.onPropertiesAvailable(getProperties()); - } - - void release() { - Log.d(TAG, "release() called"); - enforcer.run(Stage.INITIALIZED, () -> { - Log.d(TAG, "release() executing"); - previewSurface = null; - camera.stopPreview(); - camera.release(); - enforcer.reset(); - }); - } - - void linkSurface(@NonNull SurfaceTexture surfaceTexture) { - Log.d(TAG, "linkSurface() called"); - enforcer.run(Stage.INITIALIZED, () -> { - try { - Log.d(TAG, "linkSurface() executing"); - previewSurface = surfaceTexture; - - camera.setPreviewTexture(surfaceTexture); - camera.startPreview(); - enforcer.markCompleted(Stage.PREVIEW_STARTED); - } catch (Exception e) { - Log.w(TAG, "Failed to start preview.", e); - eventListener.onCameraUnavailable(); - } - }); - } - - void capture(@NonNull CaptureCallback callback) { - enforcer.run(Stage.PREVIEW_STARTED, () -> { - camera.takePicture(null, null, null, (data, camera) -> { - callback.onCaptureAvailable(data, cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT); - }); - }); - } - - boolean isCameraFacingFront() { - return cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT; - } - - int flip() { - Log.d(TAG, "flip()"); - SurfaceTexture surfaceTexture = previewSurface; - cameraId = (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK; - - release(); - initialize(); - linkSurface(surfaceTexture); - setScreenRotation(screenRotation); - - return cameraId; - } - - void setScreenRotation(int screenRotation) { - Log.d(TAG, "setScreenRotation(" + screenRotation + ") called"); - enforcer.run(Stage.PREVIEW_STARTED, () -> { - Log.d(TAG, "setScreenRotation(" + screenRotation + ") executing"); - this.screenRotation = screenRotation; - - int previewRotation = getPreviewRotation(screenRotation); - int outputRotation = getOutputRotation(screenRotation); - - Log.d(TAG, "Preview rotation: " + previewRotation + " Output rotation: " + outputRotation); - - camera.setDisplayOrientation(previewRotation); - - Camera.Parameters params = camera.getParameters(); - params.setRotation(outputRotation); - camera.setParameters(params); - }); - } - - private void onCameraUnavailable() { - enforcer.reset(); - eventListener.onCameraUnavailable(); - } - - private Properties getProperties() { - Camera.Size previewSize = camera.getParameters().getPreviewSize(); - return new Properties(Camera.getNumberOfCameras(), previewSize.width, previewSize.height); - } - - private Camera.Size getClosestSize(List sizes, int width, int height) { - Collections.sort(sizes, ASC_SIZE_COMPARATOR); - - int i = 0; - while (i < sizes.size() && (sizes.get(i).width * sizes.get(i).height) < (width * height)) { - i++; - } - i++; - - return sizes.get(Math.min(i, sizes.size() - 1)); - } - - private int getOutputRotation(int displayRotationCode) { - int degrees = convertRotationToDegrees(displayRotationCode); - - Camera.CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(cameraId, info); - - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - return (info.orientation + degrees) % 360; - } else { - return (info.orientation - degrees + 360) % 360; - } - } - - private int getPreviewRotation(int displayRotationCode) { - int degrees = convertRotationToDegrees(displayRotationCode); - - Camera.CameraInfo info = new Camera.CameraInfo(); - Camera.getCameraInfo(cameraId, info); - - int result; - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - result = (info.orientation + degrees) % 360; - result = (360 - result) % 360; - } else { - result = (info.orientation - degrees + 360) % 360; - } - - return result; - } - - private int convertRotationToDegrees(int screenRotation) { - switch (screenRotation) { - case Surface.ROTATION_0: return 0; - case Surface.ROTATION_90: return 90; - case Surface.ROTATION_180: return 180; - case Surface.ROTATION_270: return 270; - } - return 0; - } - - private final Comparator ASC_SIZE_COMPARATOR = (o1, o2) -> Integer.compare(o1.width * o1.height, o2.width * o2.height); - - private enum Stage { - INITIALIZED, PREVIEW_STARTED - } - - class Properties { - - private final int cameraCount; - private final int previewWidth; - private final int previewHeight; - - Properties(int cameraCount, int previewWidth, int previewHeight) { - this.cameraCount = cameraCount; - this.previewWidth = previewWidth; - this.previewHeight = previewHeight; - } - - int getCameraCount() { - return cameraCount; - } - - int getPreviewWidth() { - return previewWidth; - } - - int getPreviewHeight() { - return previewHeight; - } - - @Override - public @NonNull String toString() { - return "cameraCount: " + cameraCount + " previewWidth: " + previewWidth + " previewHeight: " + previewHeight; - } - } - - interface EventListener { - void onPropertiesAvailable(@NonNull Properties properties); - void onCameraUnavailable(); - } - - interface CaptureCallback { - void onCaptureAvailable(@NonNull byte[] jpegData, boolean frontFacing); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java deleted file mode 100644 index e18f0563a4..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java +++ /dev/null @@ -1,505 +0,0 @@ -package org.thoughtcrime.securesms.mediasend; - -import android.animation.Animator; -import android.annotation.SuppressLint; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.SurfaceTexture; -import android.graphics.drawable.Drawable; -import android.hardware.Camera; -import android.os.Bundle; -import android.view.Display; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.TextureView; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.Animation; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.RotateAnimation; -import android.widget.ImageButton; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintSet; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.MultiTransformation; -import com.bumptech.glide.load.Transformation; -import com.bumptech.glide.load.resource.bitmap.CenterCrop; -import com.bumptech.glide.request.target.SimpleTarget; -import com.bumptech.glide.request.transition.Transition; -import com.google.android.material.card.MaterialCardView; - -import org.signal.core.util.Stopwatch; -import org.signal.core.util.logging.Log; -import org.signal.core.models.media.Media; -import org.signal.core.ui.logging.LoggingFragment; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.animation.AnimationCompleteListener; -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig; -import org.thoughtcrime.securesms.mediasend.v2.MediaAnimations; -import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton; -import org.signal.glide.decryptableuri.DecryptableUri; -import org.signal.core.util.ServiceUtil; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.util.ViewUtil; - -import java.io.ByteArrayOutputStream; - -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.disposables.Disposable; - -/** - * Camera capture implemented with the legacy camera API's. Should only be used if a device is on the {@link CameraXRemoteConfig}. - */ -public class Camera1Fragment extends LoggingFragment implements CameraFragment, - TextureView.SurfaceTextureListener, - Camera1Controller.EventListener -{ - - private static final String TAG = Log.tag(Camera1Fragment.class); - - private TextureView cameraPreview; - private ViewGroup controlsContainer; - private ImageButton flipButton; - private View captureButton; - private Camera1Controller camera; - private Controller controller; - private OrderEnforcer orderEnforcer; - private Camera1Controller.Properties properties; - private RotationListener rotationListener; - private Disposable rotationListenerDisposable; - private Disposable mostRecentItemDisposable = Disposable.disposed(); - private CameraScreenBrightnessController cameraScreenBrightnessController; - private boolean isMediaSelected; - - public static Camera1Fragment newInstance() { - return new Camera1Fragment(); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (getActivity() instanceof Controller) { - this.controller = (Controller) getActivity(); - } else if (getParentFragment() instanceof Controller) { - this.controller = (Controller) getParentFragment(); - } - - if (controller == null) { - throw new IllegalStateException("Parent must implement controller interface."); - } - - WindowManager windowManager = ServiceUtil.getWindowManager(getActivity()); - Display display = windowManager.getDefaultDisplay(); - Point displaySize = new Point(); - - display.getSize(displaySize); - - camera = new Camera1Controller(SignalStore.misc().isCameraFacingFront() ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK, displaySize.x, displaySize.y, this); - orderEnforcer = new OrderEnforcer<>(Stage.SURFACE_AVAILABLE, Stage.CAMERA_PROPERTIES_AVAILABLE); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.camera_fragment, container, false); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); - cameraScreenBrightnessController = new CameraScreenBrightnessController(requireActivity().getWindow(), new CameraStateProvider(camera)); - getViewLifecycleOwner().getLifecycle().addObserver(cameraScreenBrightnessController); - - rotationListener = new RotationListener(requireContext()); - cameraPreview = view.findViewById(R.id.camera_preview); - controlsContainer = view.findViewById(R.id.camera_controls_container); - - View cameraParent = view.findViewById(R.id.camera_preview_parent); - - onOrientationChanged(); - - cameraPreview.setSurfaceTextureListener(this); - - GestureDetector gestureDetector = new GestureDetector(flipGestureListener); - cameraPreview.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event)); - - view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - // Let's assume portrait for now, so 9:16 - float aspectRatio = CameraFragment.getAspectRatioForOrientation(Configuration.ORIENTATION_PORTRAIT); - float width = right - left; - float height = Math.min((1f / aspectRatio) * width, bottom - top); - - ViewGroup.LayoutParams params = cameraParent.getLayoutParams(); - - // If there's a mismatch... - if (params.height != (int) height) { - params.width = (int) width; - params.height = (int) height; - - cameraParent.setLayoutParams(params); - } - }); - } - - @Override - public void onResume() { - super.onResume(); - camera.initialize(); - - if (cameraPreview.isAvailable()) { - orderEnforcer.markCompleted(Stage.SURFACE_AVAILABLE); - } - - if (properties != null) { - orderEnforcer.markCompleted(Stage.CAMERA_PROPERTIES_AVAILABLE); - } - - orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> { - camera.linkSurface(cameraPreview.getSurfaceTexture()); - }); - - rotationListenerDisposable = rotationListener.getObservable() - .distinctUntilChanged() - .filter(rotation -> rotation != RotationListener.Rotation.ROTATION_180) - .subscribe(rotation -> { - orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> { - camera.setScreenRotation(rotation.getSurfaceRotation()); - }); - }); - - orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); - requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); - } - - @Override - public void onPause() { - super.onPause(); - rotationListenerDisposable.dispose(); - camera.release(); - orderEnforcer.reset(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - mostRecentItemDisposable.dispose(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - } - - @Override - public void fadeOutControls(@NonNull Runnable onEndAction) { - controlsContainer.setEnabled(false); - controlsContainer.animate() - .setInterpolator(MediaAnimations.getInterpolator()) - .setDuration(250) - .alpha(0f) - .setListener(new AnimationCompleteListener() { - @Override - public void onAnimationEnd(Animator animation) { - controlsContainer.setEnabled(true); - onEndAction.run(); - } - }); - } - - @Override - public void fadeInControls() { - controlsContainer.setEnabled(false); - controlsContainer.animate() - .setInterpolator(MediaAnimations.getInterpolator()) - .setDuration(250) - .alpha(1f) - .setListener(new AnimationCompleteListener() { - @Override - public void onAnimationEnd(Animator animation) { - controlsContainer.setEnabled(true); - } - }); - } - - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - Log.d(TAG, "onSurfaceTextureAvailable"); - orderEnforcer.markCompleted(Stage.SURFACE_AVAILABLE); - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - return false; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - } - - @Override - public void onPropertiesAvailable(@NonNull Camera1Controller.Properties properties) { - Log.d(TAG, "Got camera properties: " + properties); - this.properties = properties; - orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); - orderEnforcer.markCompleted(Stage.CAMERA_PROPERTIES_AVAILABLE); - } - - @Override - public void onCameraUnavailable() { - controller.onCameraError(); - } - - private void presentRecentItemThumbnail(@Nullable Media media) { - View thumbBackground = controlsContainer.findViewById(R.id.camera_gallery_button_background); - ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button); - - if (media != null) { - thumbBackground.setBackgroundResource(R.drawable.circle_tintable); - thumbnail.clearColorFilter(); - thumbnail.setScaleType(ImageView.ScaleType.FIT_CENTER); - Glide.with(this) - .load(new DecryptableUri(media.getUri())) - .centerCrop() - .into(thumbnail); - } else { - thumbBackground.setBackgroundResource(R.drawable.media_selection_camera_switch_background); - thumbnail.setImageResource(R.drawable.symbol_album_tilt_24); - thumbnail.setColorFilter(Color.WHITE); - thumbnail.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - } - } - - @Override - public void presentHud(int selectedMediaCount) { - MediaCountIndicatorButton countButton = controlsContainer.findViewById(R.id.camera_review_button); - View cameraGalleryContainer = controlsContainer.findViewById(R.id.camera_gallery_button_background); - - if (selectedMediaCount > 0) { - countButton.setVisibility(View.VISIBLE); - countButton.setCount(selectedMediaCount); - cameraGalleryContainer.setVisibility(View.GONE); - } else { - countButton.setVisibility(View.GONE); - cameraGalleryContainer.setVisibility(View.VISIBLE); - } - - isMediaSelected = selectedMediaCount > 0; - updateGalleryVisibility(); - } - - private void updateGalleryVisibility() { - View cameraGalleryContainer = controlsContainer.findViewById(R.id.camera_gallery_button_background); - - if (isMediaSelected) { - cameraGalleryContainer.setVisibility(View.GONE); - } else { - cameraGalleryContainer.setVisibility(View.VISIBLE); - } - } - - private void initControls() { - flipButton = requireView().findViewById(R.id.camera_flip_button); - captureButton = requireView().findViewById(R.id.camera_capture_button); - - View galleryButton = requireView().findViewById(R.id.camera_gallery_button); - View countButton = requireView().findViewById(R.id.camera_review_button); - - mostRecentItemDisposable.dispose(); - mostRecentItemDisposable = controller.getMostRecentMediaItem() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(item -> presentRecentItemThumbnail(item.orElse(null))); - - initializeViewFinderAndControlsPositioning(); - - captureButton.setOnClickListener(v -> { - captureButton.setEnabled(false); - onCaptureClicked(); - }); - - captureButton.setOnLongClickListener(unused -> { - CameraFragment.toastVideoRecordingNotAvailable(requireContext()); - return true; - }); - - orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, () -> { - if (properties.getCameraCount() > 1) { - flipButton.setVisibility(properties.getCameraCount() > 1 ? View.VISIBLE : View.GONE); - flipButton.setOnClickListener(v -> { - int newCameraId = camera.flip(); - SignalStore.misc().setCameraFacingFront(newCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT); - - Animation animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); - animation.setDuration(200); - animation.setInterpolator(new DecelerateInterpolator()); - flipButton.startAnimation(animation); - cameraScreenBrightnessController.onCameraDirectionChanged(newCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT); - }); - } else { - flipButton.setVisibility(View.GONE); - } - }); - - galleryButton.setOnClickListener(v -> controller.onGalleryClicked()); - countButton.setOnClickListener(v -> controller.onCameraCountButtonClicked()); - } - - private void initializeViewFinderAndControlsPositioning() { - MaterialCardView cameraCard = requireView().findViewById(R.id.camera_preview_parent); - View controls = requireView().findViewById(R.id.camera_controls_container); - CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity()); - - if (!cameraDisplay.getRoundViewFinderCorners()) { - cameraCard.setRadius(0f); - } - - ViewUtil.setBottomMargin(controls, cameraDisplay.getCameraCaptureMarginBottom(getResources())); - - if (cameraDisplay.getCameraViewportGravity() == CameraDisplay.CameraViewportGravity.CENTER) { - ConstraintSet constraintSet = new ConstraintSet(); - constraintSet.clone((ConstraintLayout) requireView()); - constraintSet.connect(R.id.camera_preview_parent, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); - constraintSet.applyTo((ConstraintLayout) requireView()); - } else { - ViewUtil.setBottomMargin(cameraCard, cameraDisplay.getCameraViewportMarginBottom()); - } - } - - private void onCaptureClicked() { - orderEnforcer.reset(); - - Stopwatch fastCaptureTimer = new Stopwatch("Capture"); - - camera.capture((jpegData, frontFacing) -> { - fastCaptureTimer.split("captured"); - - Transformation transformation = frontFacing ? new MultiTransformation<>(new CenterCrop(), new FlipTransformation()) - : new CenterCrop(); - - Glide.with(this) - .asBitmap() - .load(jpegData) - .transform(transformation) - .override(cameraPreview.getWidth(), cameraPreview.getHeight()) - .into(new SimpleTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition transition) { - fastCaptureTimer.split("transform"); - - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - resource.compress(Bitmap.CompressFormat.JPEG, 80, stream); - fastCaptureTimer.split("compressed"); - - byte[] data = stream.toByteArray(); - fastCaptureTimer.split("bytes"); - fastCaptureTimer.stop(TAG); - - controller.onImageCaptured(data, resource.getWidth(), resource.getHeight()); - } - - @Override - public void onLoadFailed(@Nullable Drawable errorDrawable) { - controller.onCameraError(); - } - }); - }); - } - - private PointF getScaleTransform(float viewWidth, float viewHeight, int cameraWidth, int cameraHeight) { - float camWidth = isPortrait() ? Math.min(cameraWidth, cameraHeight) : Math.max(cameraWidth, cameraHeight); - float camHeight = isPortrait() ? Math.max(cameraWidth, cameraHeight) : Math.min(cameraWidth, cameraHeight); - - float scaleX = 1; - float scaleY = 1; - - if ((camWidth / viewWidth) > (camHeight / viewHeight)) { - float targetWidth = viewHeight * (camWidth / camHeight); - scaleX = targetWidth / viewWidth; - } else { - float targetHeight = viewWidth * (camHeight / camWidth); - scaleY = targetHeight / viewHeight; - } - - return new PointF(scaleX, scaleY); - } - - private void onOrientationChanged() { - int layout = R.layout.camera_controls_portrait; - - controlsContainer.removeAllViews(); - controlsContainer.addView(LayoutInflater.from(getContext()).inflate(layout, controlsContainer, false)); - initControls(); - } - - private void updatePreviewScale() { - PointF scale = getScaleTransform(cameraPreview.getWidth(), cameraPreview.getHeight(), properties.getPreviewWidth(), properties.getPreviewHeight()); - Matrix matrix = new Matrix(); - - float camWidth = isPortrait() ? Math.min(cameraPreview.getWidth(), cameraPreview.getHeight()) : Math.max(cameraPreview.getWidth(), cameraPreview.getHeight()); - float camHeight = isPortrait() ? Math.max(cameraPreview.getWidth(), cameraPreview.getHeight()) : Math.min(cameraPreview.getWidth(), cameraPreview.getHeight()); - - matrix.setScale(scale.x, scale.y); - matrix.postTranslate((camWidth - (camWidth * scale.x)) / 2, (camHeight - (camHeight * scale.y)) / 2); - cameraPreview.setTransform(matrix); - } - - private boolean isPortrait() { - return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - } - - private final GestureDetector.OnGestureListener flipGestureListener = new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onDown(MotionEvent e) { - return true; - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - flipButton.performClick(); - return true; - } - }; - - private enum Stage { - SURFACE_AVAILABLE, CAMERA_PROPERTIES_AVAILABLE - } - - private static class CameraStateProvider implements CameraScreenBrightnessController.CameraStateProvider { - - private final Camera1Controller camera1Controller; - - private CameraStateProvider(Camera1Controller camera1Controller) { - this.camera1Controller = camera1Controller; - } - - @Override - public boolean isFrontFacingCameraSelected() { - return camera1Controller.isCameraFacingFront(); - } - - @Override - public boolean isFlashEnabled() { - return false; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java deleted file mode 100644 index 1a2c78cf19..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.thoughtcrime.securesms.mediasend; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Configuration; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; - -import org.signal.core.models.media.Media; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig; -import org.thoughtcrime.securesms.mms.MediaConstraints; - -import java.io.FileDescriptor; -import java.util.Optional; - -import io.reactivex.rxjava3.core.Flowable; - -public interface CameraFragment { - - float PORTRAIT_ASPECT_RATIO = 9 / 16f; - - @SuppressLint({ "RestrictedApi", "UnsafeOptInUsageError" }) - static Fragment newInstance(boolean qrScanEnabled) { - if (CameraXRemoteConfig.isSupported()) { - return CameraXFragment.newInstance(qrScanEnabled); - } else { - return Camera1Fragment.newInstance(); - } - } - - static Class getFragmentClass() { - if (CameraXRemoteConfig.isSupported()) { - return CameraXFragment.class; - } else { - return Camera1Fragment.class; - } - } - - @SuppressLint({ "RestrictedApi", "UnsafeOptInUsageError" }) - static Fragment newInstanceForAvatarCapture() { - if (CameraXRemoteConfig.isSupported()) { - return CameraXFragment.newInstanceForAvatarCapture(); - } else { - return Camera1Fragment.newInstance(); - } - } - - static float getAspectRatioForOrientation(int orientation) { - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - return PORTRAIT_ASPECT_RATIO; - } else { - return 1f / PORTRAIT_ASPECT_RATIO; - } - } - - static void toastVideoRecordingNotAvailable(@NonNull Context context) { - Toast.makeText(context, R.string.CameraFragment__video_recording_is_not_supported_on_your_device, Toast.LENGTH_SHORT) - .show(); - } - - void presentHud(int selectedMediaCount); - void fadeOutControls(@NonNull Runnable onEndAction); - void fadeInControls(); - - interface Controller { - void onCameraError(); - void onImageCaptured(@NonNull byte[] data, int width, int height); - void onVideoCaptured(@NonNull FileDescriptor fd); - void onVideoCaptureError(); - void onGalleryClicked(); - void onCameraCountButtonClicked(); - void onQrCodeFound(@NonNull String data); - @NonNull Flowable> getMostRecentMediaItem(); - @NonNull MediaConstraints getMediaConstraints(); - int getMaxVideoDuration(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt index 17eae16372..2699630810 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt @@ -9,6 +9,8 @@ import android.os.Build import android.os.Bundle import android.os.ParcelFileDescriptor import android.widget.Toast +import android.widget.Toast.LENGTH_SHORT +import android.widget.Toast.makeText import androidx.camera.core.CameraSelector import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween @@ -60,9 +62,11 @@ import org.signal.core.ui.permissions.Permissions import org.signal.core.util.MemoryFileDescriptor import org.signal.core.util.asListContains import org.signal.core.util.logging.Log +import org.signal.mediasend.MediaConstraints +import org.signal.mediasend.capture.CameraFragment import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.R.string.CameraFragment__video_recording_is_not_supported_on_your_device import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.video.VideoUtil @@ -543,10 +547,12 @@ private fun handleHudEvent( } ) } else { - CameraFragment.toastVideoRecordingNotAvailable(context) + makeText(context, CameraFragment__video_recording_is_not_supported_on_your_device, LENGTH_SHORT) + .show() } } else { - CameraFragment.toastVideoRecordingNotAvailable(context) + makeText(context, CameraFragment__video_recording_is_not_supported_on_your_device, LENGTH_SHORT) + .show() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/SentMediaQualityTransform.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/SentMediaQualityTransform.java index 2d7042a912..c6d4ad892c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/SentMediaQualityTransform.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/SentMediaQualityTransform.java @@ -7,7 +7,7 @@ import androidx.annotation.WorkerThread; import org.signal.core.models.media.Media; import org.signal.core.models.media.TransformProperties; -import org.thoughtcrime.securesms.mms.SentMediaQuality; +import org.signal.mediasend.SentMediaQuality; import java.util.Optional; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoEditorFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoEditorFragment.kt index 836f259955..cb279dba10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoEditorFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoEditorFragment.kt @@ -11,10 +11,10 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import org.signal.core.util.Throttler import org.signal.core.util.logging.Log +import org.signal.mediasend.MediaConstraints import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData -import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.mms.VideoSlide import org.thoughtcrime.securesms.scribbles.VideoEditorPlayButtonLayout import org.thoughtcrime.securesms.util.visible diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoTrimTransform.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoTrimTransform.kt index c5a32e5d02..23cf0d7102 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoTrimTransform.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/VideoTrimTransform.kt @@ -4,8 +4,8 @@ import android.content.Context import androidx.annotation.WorkerThread import org.signal.core.models.media.Media import org.signal.core.models.media.TransformProperties +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData -import org.thoughtcrime.securesms.mms.SentMediaQuality class VideoTrimTransform(private val data: VideoTrimData) : MediaTransform { @WorkerThread diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXRemoteConfig.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXRemoteConfig.kt deleted file mode 100644 index ba80b6393b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXRemoteConfig.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.thoughtcrime.securesms.mediasend.camerax - -import android.os.Build -import org.signal.core.util.asListContains -import org.thoughtcrime.securesms.util.RemoteConfig - -/** - * Some phones don't work well with CameraX. This class uses a remote config to decide - * which phones should fall back to the legacy camera. - */ -object CameraXRemoteConfig { - - @JvmStatic - fun isSupported(): Boolean { - return !isBlocklisted() - } - - @JvmStatic - fun isBlocklisted(): Boolean { - return RemoteConfig.cameraXModelBlocklist.asListContains(Build.MODEL) - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionNavigator.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionNavigator.kt index f741a06c16..2dd40f00be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionNavigator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionNavigator.kt @@ -1,12 +1,7 @@ package org.thoughtcrime.securesms.mediasend.v2 -import android.Manifest -import android.widget.Toast -import androidx.fragment.app.Fragment import androidx.navigation.NavController -import org.signal.core.ui.permissions.Permissions import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig import org.thoughtcrime.securesms.util.navigation.safeNavigate class MediaSelectionNavigator( @@ -32,23 +27,4 @@ class MediaSelectionNavigator( fun isPreviousScreenMediaReview(navController: NavController): Boolean { return navController.previousBackStackEntry?.destination?.id == R.id.mediaReviewFragment } - - companion object { - fun Fragment.requestPermissionsForCamera( - onGranted: () -> Unit - ) { - if (CameraXRemoteConfig.isSupported()) { - onGranted() - } else { - Permissions.with(this) - .request(Manifest.permission.CAMERA) - .ifNecessary() - .withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_capture_photos_and_video_allow_camera), R.drawable.ic_camera_24) - .withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos_videos, getParentFragmentManager()) - .onAllGranted(onGranted) - .onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show() } - .execute() - } - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt index e3422644b5..44b726f06b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt @@ -13,6 +13,8 @@ import org.signal.core.util.BreakIteratorCompat import org.signal.core.util.ThreadUtil import org.signal.core.util.logging.Log import org.signal.imageeditor.core.model.EditorModel +import org.signal.mediasend.MediaConstraints +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.database.SignalDatabase @@ -33,9 +35,7 @@ import org.thoughtcrime.securesms.mediasend.VideoTrimTransform import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData import org.thoughtcrime.securesms.mms.GifSlide import org.thoughtcrime.securesms.mms.ImageSlide -import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.mms.OutgoingMessage -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.mms.VideoSlide diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionState.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionState.kt index 4eada60453..cb4d35098d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionState.kt @@ -2,11 +2,12 @@ package org.thoughtcrime.securesms.mediasend.v2 import android.net.Uri import org.signal.core.models.media.Media +import org.signal.mediasend.MediaConstraints +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData -import org.thoughtcrime.securesms.mms.MediaConstraints -import org.thoughtcrime.securesms.mms.SentMediaQuality +import org.thoughtcrime.securesms.mms.PushMediaConstraints import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.MediaUtil @@ -35,7 +36,7 @@ data class MediaSelectionState( val isVideoTrimmingVisible: Boolean = focusedMedia != null && MediaUtil.isVideoType(focusedMedia.contentType) && MediaConstraints.isVideoTranscodeAvailable() && !focusedMedia.isVideoGif - val transcodingPreset: TranscodingPreset = MediaConstraints.getPushMediaConstraints(SentMediaQuality.fromCode(quality.code)).videoTranscodingSettings + val transcodingPreset: TranscodingPreset = PushMediaConstraints(SentMediaQuality.fromCode(quality.code)).videoTranscodingSettings val maxSelection = RemoteConfig.maxAttachmentCount diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt index 82f46785f0..f43de41fac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt @@ -24,14 +24,15 @@ import org.signal.core.util.Util import org.signal.core.util.getParcelableArrayListCompat import org.signal.core.util.getParcelableCompat import org.signal.core.util.logging.Log +import org.signal.mediasend.MediaConstraints +import org.signal.mediasend.SentMediaQuality 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.mediasend.MediaSendActivityResult import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData -import org.thoughtcrime.securesms.mms.MediaConstraints -import org.thoughtcrime.securesms.mms.SentMediaQuality +import org.thoughtcrime.securesms.mms.PushMediaConstraints import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.scribbles.ImageEditorFragment @@ -320,7 +321,7 @@ class MediaSelectionViewModel( } fun getMediaConstraints(): MediaConstraints { - return MediaConstraints.getPushMediaConstraints() + return PushMediaConstraints(null) } fun setSentMediaQuality(sentMediaQuality: SentMediaQuality) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaValidator.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaValidator.kt index 7868fe4c48..8aa2d5a374 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaValidator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaValidator.kt @@ -4,7 +4,7 @@ import android.content.Context import androidx.annotation.WorkerThread import org.signal.core.models.media.Media import org.signal.core.util.Util -import org.thoughtcrime.securesms.mms.MediaConstraints +import org.signal.mediasend.MediaConstraints import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.MediaUtil diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureFragment.kt index f43b8af966..22cc528e01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureFragment.kt @@ -8,24 +8,22 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.google.android.material.dialog.MaterialAlertDialogBuilder -import io.reactivex.rxjava3.core.Flowable -import org.signal.core.models.media.Media import org.signal.core.ui.permissions.Permissions import org.signal.core.util.concurrent.LifecycleDisposable import org.signal.core.util.logging.Log +import org.signal.mediasend.MediaConstraints +import org.signal.mediasend.capture.CameraFragment import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity -import org.thoughtcrime.securesms.mediasend.CameraFragment +import org.thoughtcrime.securesms.mediasend.CameraXFragment import org.thoughtcrime.securesms.mediasend.v2.HudCommand import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionNavigator import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel -import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.registration.olddevice.QuickTransferOldDeviceActivity import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.navigation.safeNavigate import java.io.FileDescriptor -import java.util.Optional import java.util.concurrent.TimeUnit private val TAG = Log.tag(MediaCaptureFragment::class.java) @@ -49,7 +47,7 @@ class MediaCaptureFragment : Fragment(R.layout.fragment_container), CameraFragme private val lifecycleDisposable = LifecycleDisposable() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - captureChildFragment = CameraFragment.newInstance(sharedViewModel.isContactSelectionRequired) as CameraFragment + captureChildFragment = CameraXFragment.newInstance(sharedViewModel.isContactSelectionRequired) as CameraFragment navigator = MediaSelectionNavigator( toGallery = R.id.action_mediaCaptureFragment_to_mediaGalleryFragment @@ -140,17 +138,6 @@ class MediaCaptureFragment : Fragment(R.layout.fragment_container), CameraFragme captureChildFragment.fadeInControls() } - override fun onCameraError() { - Log.w(TAG, "Camera Error.") - - val context = this.context - if (context != null) { - Toast.makeText(context, R.string.MediaSendActivity_camera_unavailable, Toast.LENGTH_SHORT).show() - } else { - Log.w(TAG, "Could not post toast, fragment not attached to a context.") - } - } - override fun onImageCaptured(data: ByteArray, width: Int, height: Int) { viewModel.onImageCaptured(data, width, height) } @@ -184,10 +171,6 @@ class MediaCaptureFragment : Fragment(R.layout.fragment_container), CameraFragme viewModel.onQrCodeFound(data) } - override fun getMostRecentMediaItem(): Flowable> { - return viewModel.getMostRecentMedia() - } - override fun getMediaConstraints(): MediaConstraints { return sharedViewModel.getMediaConstraints() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureRepository.kt index 6a019ec569..b5be426127 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureRepository.kt @@ -8,10 +8,8 @@ import android.os.Build import android.provider.MediaStore import androidx.annotation.WorkerThread import org.signal.core.models.media.Media -import org.signal.core.ui.util.StorageUtil import org.signal.core.util.CursorUtil import org.signal.core.util.concurrent.SignalExecutors -import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.mediasend.MediaRepository import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.util.MediaUtil @@ -21,25 +19,10 @@ import java.io.FileInputStream import java.io.IOException import java.util.LinkedList -private val TAG = Log.tag(MediaCaptureRepository::class.java) - class MediaCaptureRepository(context: Context) { private val context: Context = context.applicationContext - fun getMostRecentItem(callback: (Media?) -> Unit) { - if (!StorageUtil.canReadAnyFromMediaStore()) { - Log.w(TAG, "Cannot read from storage.") - callback(null) - return - } - - SignalExecutors.BOUNDED.execute { - val media: List = getMediaInBucket(context, Media.ALL_MEDIA_BUCKET_ID, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true) - callback(media.firstOrNull()) - } - } - fun renderImageToMedia(data: ByteArray, width: Int, height: Int, onMediaRendered: (Media) -> Unit, onFailedToRender: () -> Unit) { SignalExecutors.BOUNDED.execute { val media: Media? = renderCaptureToMedia( diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureState.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureState.kt deleted file mode 100644 index 2d1a5bb1a0..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureState.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.thoughtcrime.securesms.mediasend.v2.capture - -import org.signal.core.models.media.Media - -data class MediaCaptureState( - val mostRecentMedia: Media? = null -) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureViewModel.kt index e336d6cd72..f8cb3d6986 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/capture/MediaCaptureViewModel.kt @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.mediasend.v2.capture import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -16,9 +15,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.profiles.manage.UsernameRepository import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.registration.data.QuickRegistrationRepository -import org.thoughtcrime.securesms.util.rx.RxStore import java.io.FileDescriptor -import java.util.Optional import java.util.concurrent.TimeUnit class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : ViewModel() { @@ -27,8 +24,6 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi private val TAG = Log.tag(MediaCaptureViewModel::class.java) } - private val store: RxStore = RxStore(MediaCaptureState()) - private val internalEvents: Subject = PublishSubject.create() private val qrData: Subject = PublishSubject.create() @@ -36,12 +31,6 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi val disposables = CompositeDisposable() init { - repository.getMostRecentItem { media -> - store.update { state -> - state.copy(mostRecentMedia = media) - } - } - disposables += qrData .throttleFirst(5, TimeUnit.SECONDS) .filter { UsernameRepository.isValidLink(it) } @@ -84,7 +73,6 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi } override fun onCleared() { - store.dispose() disposables.dispose() } @@ -96,10 +84,6 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi repository.renderVideoToMedia(fd, this::onMediaRendered, this::onMediaRenderFailed) } - fun getMostRecentMedia(): Flowable> { - return store.stateFlowable.map { Optional.ofNullable(it.mostRecentMedia) } - } - fun onQrCodeFound(data: String) { qrData.onNext(data) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryFragment.kt index 915c2d9669..818197ce26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryFragment.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.mediasend.v2.gallery -import android.Manifest import android.os.Bundle import android.view.View import android.view.ViewGroup @@ -26,7 +25,6 @@ import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration import org.thoughtcrime.securesms.conversation.ManageContextMenu import org.thoughtcrime.securesms.databinding.V2MediaGalleryFragmentBinding import org.thoughtcrime.securesms.mediasend.MediaRepository -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig import org.thoughtcrime.securesms.mediasend.v2.review.MediaGalleryGridItemTouchListener import org.thoughtcrime.securesms.util.Material3OnScrollHelper import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter @@ -104,24 +102,7 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) { if (callbacks.isCameraEnabled()) { binding.mediaGalleryToolbar.setOnMenuItemClickListener { item -> if (item.itemId == R.id.action_camera) { - if (CameraXRemoteConfig.isSupported()) { - callbacks.onNavigateToCamera() - } else { - Permissions.with(this) - .request(Manifest.permission.CAMERA) - .ifNecessary() - .onAllGranted { callbacks.onNavigateToCamera() } - .withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_capture_photos_and_video_allow_camera), R.drawable.ic_camera_24) - .withPermanentDenialDialog( - getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), - null, - R.string.CameraXFragment_allow_access_camera, - R.string.CameraXFragment_to_capture_photos_videos, - getParentFragmentManager() - ) - .onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show() } - .execute() - } + callbacks.onNavigateToCamera() true } else { false diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaSelectionGalleryFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaSelectionGalleryFragment.kt index b098bdf347..9d383259a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaSelectionGalleryFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaSelectionGalleryFragment.kt @@ -14,7 +14,6 @@ import org.signal.core.ui.permissions.Permissions import org.signal.core.util.concurrent.LifecycleDisposable import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionNavigator -import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionNavigator.Companion.requestPermissionsForCamera import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel import org.thoughtcrime.securesms.mediasend.v2.review.MediaSelectionItemTouchHelper import org.signal.core.ui.R as CoreUiR @@ -126,9 +125,7 @@ class MediaSelectionGalleryFragment : Fragment(R.layout.fragment_container), Med override fun onNavigateToCamera() { val controller = findNavController() - requestPermissionsForCamera { - navigator.goToCamera(controller) - } + navigator.goToCamera(controller) } override fun onSubmit() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt index cc8af72207..9f12e3aebd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt @@ -39,6 +39,8 @@ import org.signal.core.util.concurrent.LifecycleDisposable import org.signal.core.util.concurrent.SimpleTask import org.signal.core.util.isNotNullOrBlank import org.signal.core.util.logging.Log +import org.signal.mediasend.MediaConstraints +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.MessageSendType @@ -57,8 +59,6 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionState import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel import org.thoughtcrime.securesms.mediasend.v2.stories.StoriesMultiselectForwardActivity import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData -import org.thoughtcrime.securesms.mms.MediaConstraints -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.scribbles.ImageEditorFragment import org.thoughtcrime.securesms.util.MediaUtil diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/QualitySelectorBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/QualitySelectorBottomSheet.kt index 19995c61d0..99c651219a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/QualitySelectorBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/QualitySelectorBottomSheet.kt @@ -30,9 +30,9 @@ import androidx.fragment.app.viewModels import org.signal.core.ui.compose.BottomSheets import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment import org.signal.core.ui.compose.Previews +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel -import org.thoughtcrime.securesms.mms.SentMediaQuality /** * Bottom sheet dialog to select the media quality (Standard vs. High) when sending media. diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3CameraSlot.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3CameraSlot.kt deleted file mode 100644 index 965e323126..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3CameraSlot.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2026 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.mediasend.v3 - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.fragment.compose.AndroidFragment -import org.thoughtcrime.securesms.mediasend.CameraFragment - -/** - * Displays the proper camera capture ui - */ -@Composable -fun MediaSendV3CameraSlot() { - val fragmentClass = remember { - CameraFragment.getFragmentClass() - } - - AndroidFragment( - clazz = fragmentClass, - modifier = Modifier.fillMaxSize() - ) -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Repository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Repository.kt index 3daf510712..3b4a66740e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Repository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Repository.kt @@ -5,6 +5,7 @@ package org.thoughtcrime.securesms.mediasend.v3 +import android.content.Context import android.net.Uri import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -17,12 +18,14 @@ import org.signal.core.models.media.Media import org.signal.core.models.media.MediaFolder import org.signal.core.util.logging.Log import org.signal.mediasend.EditorState +import org.signal.mediasend.MediaConstraints import org.signal.mediasend.MediaFilterError import org.signal.mediasend.MediaFilterResult import org.signal.mediasend.MediaRecipientId import org.signal.mediasend.MediaSendRepository import org.signal.mediasend.SendRequest import org.signal.mediasend.SendResult +import org.signal.mediasend.SentMediaQuality import org.signal.mediasend.StorySendRequirements import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.MessageSendType @@ -33,14 +36,15 @@ import org.thoughtcrime.securesms.mediasend.MediaRepository import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionRepository import org.thoughtcrime.securesms.mediasend.v2.MediaValidator import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData -import org.thoughtcrime.securesms.mms.MediaConstraints -import org.thoughtcrime.securesms.mms.SentMediaQuality +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 import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.MediaUtil +import java.io.InputStream import java.util.concurrent.TimeUnit import kotlin.coroutines.resume import kotlin.time.Duration.Companion.seconds @@ -73,7 +77,7 @@ object MediaSendV3Repository : MediaSendRepository { isStory: Boolean ): MediaFilterResult = withContext(Dispatchers.IO) { val populated = MediaRepository().getPopulatedMedia(appContext, media) - val constraints = MediaConstraints.getPushMediaConstraints() + val constraints = PushMediaConstraints(null) val result = MediaValidator.filterMedia(appContext, populated, constraints, maxSelection, isStory) val error = mapFilterError(result.filterError, populated, constraints, maxSelection, isStory) @@ -94,13 +98,12 @@ object MediaSendV3Repository : MediaSendRepository { } val legacyEditorStateMap = mapLegacyEditorState(request.editorStateMap) - val quality = SentMediaQuality.fromCode(request.quality) return@withContext try { legacyRepository.send( selectedMedia = request.selectedMedia, stateMap = legacyEditorStateMap, - quality = quality, + quality = request.quality, message = request.message, isViewOnce = request.isViewOnce, singleContact = null, @@ -116,13 +119,13 @@ object MediaSendV3Repository : MediaSendRepository { } } - override fun getMaxVideoDurationUs(quality: Int, maxFileSizeBytes: Long): Long { - val preset = MediaConstraints.getPushMediaConstraints(SentMediaQuality.fromCode(quality)).videoTranscodingSettings + override fun getMaxVideoDurationUs(quality: SentMediaQuality, maxFileSizeBytes: Long): Long { + val preset = PushMediaConstraints(quality).videoTranscodingSettings return preset.calculateMaxVideoUploadDurationInSeconds(maxFileSizeBytes).seconds.inWholeMicroseconds } override fun getVideoMaxSizeBytes(): Long { - return MediaConstraints.getPushMediaConstraints().videoMaxSize + return PushMediaConstraints(null).videoMaxSize } override fun isVideoTranscodeAvailable(): Boolean { @@ -176,6 +179,10 @@ object MediaSendV3Repository : MediaSendRepository { .distinctUntilChanged() } + override fun getAttachmentStream(context: Context, uri: Uri): InputStream { + return PartAuthority.getAttachmentStream(context, uri) + } + private fun resolveSendType(sendType: Int): MessageSendType { return when (sendType) { else -> MessageSendType.SignalMessageSendType diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index c234630901..a3461644fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -44,6 +44,7 @@ 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.mediasend.MediaConstraints; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AudioView; import org.thoughtcrime.securesms.components.DocumentView; @@ -72,7 +73,7 @@ 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.thoughtcrime.securesms.util.BitmapUtil; +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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/MediaConstraints.java b/app/src/main/java/org/thoughtcrime/securesms/mms/MediaConstraints.java deleted file mode 100644 index 693a5a605f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/MediaConstraints.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.thoughtcrime.securesms.mms; - -import android.content.Context; -import android.net.Uri; -import android.os.Build; -import kotlin.Pair; - -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.attachments.Attachment; -import org.thoughtcrime.securesms.jobs.AttachmentUploadJob; -import org.thoughtcrime.securesms.util.BitmapDecodingException; -import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.RemoteConfig; -import org.thoughtcrime.securesms.util.MediaUtil; -import org.signal.core.util.MemoryFileDescriptor; -import org.thoughtcrime.securesms.video.TranscodingPreset; - -import java.io.IOException; -import java.io.InputStream; - -public abstract class MediaConstraints { - private static final String TAG = Log.tag(MediaConstraints.class); - - public static MediaConstraints getPushMediaConstraints() { - return getPushMediaConstraints(null); - } - - public static MediaConstraints getPushMediaConstraints(@Nullable SentMediaQuality sentMediaQuality) { - return new PushMediaConstraints(sentMediaQuality); - } - - public abstract int getImageMaxWidth(Context context); - public abstract int getImageMaxHeight(Context context); - public abstract int getImageMaxSize(Context context); - - public TranscodingPreset getVideoTranscodingSettings() { - return TranscodingPreset.LEVEL_1; - } - - /** - * Provide a list of dimensions that should be attempted during compression. We will keep moving - * down the list until the image can be scaled to fit under {@link #getImageMaxSize(Context)}. - * The first entry in the list should match your max width/height. - */ - public abstract int[] getImageDimensionTargets(Context context); - - public abstract long getGifMaxSize(Context context); - public abstract long getVideoMaxSize(); - - public @IntRange(from = 0, to = 100) int getImageCompressionQualitySetting(@NonNull Context context) { - return 70; - } - - public long getUncompressedVideoMaxSize(Context context) { - return getVideoMaxSize(); - } - - public long getCompressedVideoMaxSize(Context context) { - return getVideoMaxSize(); - } - - public abstract long getAudioMaxSize(Context context); - public abstract long getDocumentMaxSize(Context context); - - public long getMaxAttachmentSize() { - return AttachmentUploadJob.getMaxPlaintextSize(); - } - - public boolean isSatisfied(@NonNull Context context, @NonNull Attachment attachment) { - try { - long size = attachment.size; - if (size > getMaxAttachmentSize()) { - return false; - } - return (MediaUtil.isGif(attachment) && size <= getGifMaxSize(context) && isWithinBounds(context, attachment.getUri())) || - (MediaUtil.isImage(attachment) && size <= getImageMaxSize(context) && isWithinBounds(context, attachment.getUri())) || - (MediaUtil.isAudio(attachment) && size <= getAudioMaxSize(context)) || - (MediaUtil.isVideo(attachment) && size <= getVideoMaxSize()) || - (MediaUtil.isFile(attachment) && size <= getDocumentMaxSize(context)); - } catch (IOException ioe) { - Log.w(TAG, "Failed to determine if media's constraints are satisfied.", ioe); - return false; - } - } - - public boolean isSatisfied(@NonNull Context context, @NonNull Uri uri, @NonNull String contentType, long size) { - try { - if (size > getMaxAttachmentSize()) { - return false; - } - return (MediaUtil.isGif(contentType) && size <= getGifMaxSize(context) && isWithinBounds(context, uri)) || - (MediaUtil.isImageType(contentType) && size <= getImageMaxSize(context) && isWithinBounds(context, uri)) || - (MediaUtil.isAudioType(contentType) && size <= getAudioMaxSize(context)) || - (MediaUtil.isVideoType(contentType) && size <= getVideoMaxSize()) || - size <= getDocumentMaxSize(context); - } catch (IOException ioe) { - Log.w(TAG, "Failed to determine if media's constraints are satisfied.", ioe); - return false; - } - } - - private boolean isWithinBounds(Context context, Uri uri) throws IOException { - try { - InputStream is = PartAuthority.getAttachmentStream(context, uri); - Pair dimensions = BitmapUtil.getDimensions(is); -return dimensions.getFirst() > 0 && dimensions.getFirst() <= getImageMaxWidth(context) && - dimensions.getSecond() > 0 && dimensions.getSecond() <= getImageMaxHeight(context); - } catch (BitmapDecodingException e) { - throw new IOException(e); - } - } - - public boolean canResize(@NonNull Attachment attachment) { - return MediaUtil.isImage(attachment) && !MediaUtil.isGif(attachment) || - MediaUtil.isVideo(attachment) && isVideoTranscodeAvailable(); - } - - public boolean canResize(@NonNull String mediaType) { - return MediaUtil.isImageType(mediaType) && !MediaUtil.isGif(mediaType) || - MediaUtil.isVideoType(mediaType) && isVideoTranscodeAvailable(); - } - - public static boolean isVideoTranscodeAvailable() { - return Build.VERSION.SDK_INT >= 26; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java b/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java index 1a40253672..3d55bd3f68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java @@ -7,7 +7,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.signal.core.util.Util; +import org.signal.mediasend.MediaConstraints; +import org.signal.mediasend.SentMediaQuality; import org.thoughtcrime.securesms.dependencies.AppDependencies; +import org.thoughtcrime.securesms.jobs.AttachmentUploadJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.LocaleRemoteConfig; import org.thoughtcrime.securesms.util.RemoteConfig; @@ -83,6 +86,11 @@ public class PushMediaConstraints extends MediaConstraints { return getMaxAttachmentSize(); } + @Override + public long getMaxAttachmentSize() { + return AttachmentUploadJob.getMaxPlaintextSize(); + } + @Override public int getImageCompressionQualitySetting(@NonNull Context context) { return currentConfig.qualitySetting; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SlideFactory.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SlideFactory.java index aa445a69c8..5d716bf036 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SlideFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SlideFactory.java @@ -13,6 +13,7 @@ import androidx.annotation.Nullable; import org.signal.blurhash.BlurHash; import org.signal.core.models.media.TransformProperties; import org.signal.core.util.logging.Log; +import org.signal.mediasend.SentMediaQuality; import org.thoughtcrime.securesms.util.MediaUtil; import java.io.IOException; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt index 67dae1f117..d81c9b8c12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt @@ -11,6 +11,7 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.MultiTransformation import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.CircleCrop +import org.signal.core.util.bitmaps.BitmapUtil import org.signal.glide.decryptableuri.DecryptableUri import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar @@ -20,7 +21,6 @@ import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto import org.thoughtcrime.securesms.conversation.colors.AvatarGradientColors import org.thoughtcrime.securesms.notifications.NotificationIds import org.thoughtcrime.securesms.recipients.Recipient -import org.thoughtcrime.securesms.util.BitmapUtil import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThumbnails.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThumbnails.kt index 8f40776566..fe43ab7b53 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThumbnails.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThumbnails.kt @@ -4,6 +4,7 @@ import android.content.Context import android.net.Uri import android.os.Build import org.signal.core.util.asListContains +import org.signal.core.util.bitmaps.BitmapDecodingException import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log import org.signal.glide.decryptableuri.DecryptableUri @@ -11,7 +12,6 @@ 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.BitmapDecodingException import org.thoughtcrime.securesms.util.ImageCompressionUtil import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.util.kb diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/transfer/PaymentsTransferQrScanFragment.java b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/transfer/PaymentsTransferQrScanFragment.java index 53123232f8..2bfcf74be9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/transfer/PaymentsTransferQrScanFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/transfer/PaymentsTransferQrScanFragment.java @@ -11,13 +11,12 @@ import androidx.appcompat.widget.Toolbar; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.Navigation; +import org.signal.core.ui.logging.LoggingFragment; +import org.signal.core.util.concurrent.LifecycleDisposable; import org.signal.core.util.logging.Log; import org.signal.qr.QrScannerView; -import org.signal.core.ui.logging.LoggingFragment; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig; import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress; -import org.signal.core.util.concurrent.LifecycleDisposable; import org.thoughtcrime.securesms.util.navigation.SafeNavigation; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -53,7 +52,7 @@ public final class PaymentsTransferQrScanFragment extends LoggingFragment { Toolbar toolbar = view.findViewById(R.id.payments_transfer_scan_qr); toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(v).popBackStack()); - scannerView.start(getViewLifecycleOwner(), CameraXRemoteConfig.isBlocklisted()); + scannerView.start(getViewLifecycleOwner()); lifecycleDisposable.bindTo(getViewLifecycleOwner()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileMediaConstraints.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileMediaConstraints.java index 1bdac31163..6a72e3d0b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileMediaConstraints.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileMediaConstraints.java @@ -3,7 +3,8 @@ package org.thoughtcrime.securesms.profiles; import android.content.Context; -import org.thoughtcrime.securesms.mms.MediaConstraints; +import org.thoughtcrime.securesms.jobs.AttachmentUploadJob; +import org.signal.mediasend.MediaConstraints; public class ProfileMediaConstraints extends MediaConstraints { @Override @@ -45,4 +46,9 @@ public class ProfileMediaConstraints extends MediaConstraints { public long getDocumentMaxSize(Context context) { return 0; } + + @Override + public long getMaxAttachmentSize() { + return AttachmentUploadJob.getMaxPlaintextSize(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/SystemProfileUtil.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/SystemProfileUtil.java index 0b03302e23..6b650c4717 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/SystemProfileUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/SystemProfileUtil.java @@ -14,12 +14,13 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.signal.core.util.ContentTypeUtil; import org.signal.core.util.concurrent.ListenableFuture; import org.signal.core.util.concurrent.SettableFuture; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.mms.MediaConstraints; -import org.thoughtcrime.securesms.util.BitmapDecodingException; -import org.thoughtcrime.securesms.util.BitmapUtil; +import org.signal.mediasend.MediaConstraints; +import org.signal.core.util.bitmaps.BitmapDecodingException; +import org.thoughtcrime.securesms.util.ImageCompressionUtil; public class SystemProfileUtil { @@ -38,8 +39,23 @@ public class SystemProfileUtil { if (!TextUtils.isEmpty(photoUri)) { try { - BitmapUtil.ScaleResult result = BitmapUtil.createScaledBytes(context, Uri.parse(photoUri), mediaConstraints); - return result.getBitmap(); + ImageCompressionUtil.Result result = null; + + for (int size : mediaConstraints.getImageDimensionTargets(context)) { + result = ImageCompressionUtil.compressWithinConstraints(context, + ContentTypeUtil.IMAGE_JPEG, + Uri.parse(photoUri), + size, + mediaConstraints.getImageMaxSize(context), + mediaConstraints.getImageCompressionQualitySetting(context)); + if (result != null) { + break; + } + } + + if (result != null) { + return result.getData(); + } } catch (BitmapDecodingException e) { Log.w(TAG, e); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java index bf15165fcf..73f3bd1198 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java @@ -54,9 +54,9 @@ 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.thoughtcrime.securesms.mms.MediaConstraints; +import org.signal.mediasend.MediaConstraints; import org.thoughtcrime.securesms.mms.PushMediaConstraints; -import org.thoughtcrime.securesms.mms.SentMediaQuality; +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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java index 050dc0fe9c..c009f3de9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java @@ -33,7 +33,7 @@ import org.signal.core.models.media.Media; import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryBackgroundColors; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.OutgoingMessage; -import org.thoughtcrime.securesms.mms.SentMediaQuality; +import org.signal.mediasend.SentMediaQuality; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideFactory; diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt index 94a297c707..b06539567e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt @@ -16,6 +16,8 @@ import org.signal.core.ui.BottomSheetUtil import org.signal.core.util.ThreadUtil import org.signal.core.util.logging.Log import org.signal.core.util.orNull +import org.signal.mediasend.MediaConstraints +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.contacts.HeaderAction import org.thoughtcrime.securesms.database.SignalDatabase @@ -25,9 +27,7 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseStoryTypeBottomSheet -import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.mms.OutgoingMessage -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.mms.VideoSlide import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt index 74d398c001..af0a429355 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.signal.core.util.Base64 import org.signal.core.util.DimensionUnit +import org.signal.core.util.bitmaps.BitmapUtil import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.attachments.Attachment @@ -33,7 +34,6 @@ import org.thoughtcrime.securesms.stories.StoryTextPostModel import org.thoughtcrime.securesms.stories.landing.StoriesLandingItem import org.thoughtcrime.securesms.stories.viewer.page.StoryPost import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageState -import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.DeleteDialog import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.SaveAttachmentUtil diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ImageCompressionUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/ImageCompressionUtil.java index 817a414bd1..8acbb55b00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ImageCompressionUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ImageCompressionUtil.java @@ -16,6 +16,7 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import org.signal.core.util.ByteSize; +import org.signal.core.util.bitmaps.BitmapDecodingException; import org.signal.core.util.logging.Log; import java.io.ByteArrayOutputStream; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java index 488d42d17f..48be86cf3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java @@ -27,6 +27,8 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.gif.GifDrawable; 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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt index 4fff871e4b..c124d7b42c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt @@ -841,13 +841,6 @@ object RemoteConfig { hotSwappable = true ) - /** A comma-separated list of manufacturers that should *not* use CameraX. */ - val cameraXModelBlocklist: String by remoteString( - key = "android.cameraXModelBlockList.3", - defaultValue = "", - hotSwappable = true - ) - /** A comma-separated list of manufacturers that should *not* use CameraX mixed mode. */ val cameraXMixedModelBlocklist: String by remoteString( key = "android.cameraXMixedModelBlockList.3", diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt index 754898b150..a5d998e547 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt @@ -13,7 +13,6 @@ import org.signal.qr.QrScannerView import org.signal.qr.kitkat.ScanListener import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.ShapeScrim -import org.thoughtcrime.securesms.mediasend.camerax.CameraXRemoteConfig import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.fragments.findListener @@ -41,7 +40,7 @@ class VerifyScanFragment : Fragment() { ViewUtil.updateLayoutParams(cameraMarks, width, height) } - cameraView.start(viewLifecycleOwner, CameraXRemoteConfig.isBlocklisted()) + cameraView.start(viewLifecycleOwner) lifecycleDisposable.bindTo(viewLifecycleOwner) diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/VideoUtil.java b/app/src/main/java/org/thoughtcrime/securesms/video/VideoUtil.java index 2b7879453e..10a91fbb78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoUtil.java @@ -1,13 +1,10 @@ package org.thoughtcrime.securesms.video; import android.content.Context; -import android.content.res.Resources; -import android.util.DisplayMetrics; -import android.util.Size; import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.mms.MediaConstraints; +import org.signal.mediasend.MediaConstraints; import org.thoughtcrime.securesms.video.videoconverter.utils.VideoConstants; public final class VideoUtil { diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropViewModel.java index bb80fe1426..ec4400a4ae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropViewModel.java @@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.fonts.FontTypefaceProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.AsynchronousCallback; -import org.thoughtcrime.securesms.util.BitmapUtil; +import org.signal.core.util.bitmaps.BitmapUtil; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 95a6a32edc..19e1b768c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -293,8 +293,6 @@ Allow access to your camera Allow access to your microphone - - To capture photos, allow Signal access to the camera. To capture photos and video, allow Signal access to the camera. @@ -1023,7 +1021,6 @@ None - Taking a photo requires the camera permission. Viewing your gallery requires the storage permission. @@ -6063,10 +6060,6 @@ Sending high quality media will use more data. Voice messages and stickers (under %1$s) are always auto-downloaded. - - High - - Standard Calls Stay connected in background diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/AttachmentDatabaseTransformPropertiesTest.java b/app/src/test/java/org/thoughtcrime/securesms/database/AttachmentDatabaseTransformPropertiesTest.java index f9a34846f5..8252a02673 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/AttachmentDatabaseTransformPropertiesTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/database/AttachmentDatabaseTransformPropertiesTest.java @@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database; import org.junit.Test; import org.signal.core.models.media.TransformProperties; -import org.thoughtcrime.securesms.mms.SentMediaQuality; +import org.signal.mediasend.SentMediaQuality; import static org.junit.Assert.assertEquals; import static org.thoughtcrime.securesms.database.TransformPropertiesUtilKt.parseTransformProperties; diff --git a/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt b/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt index b93820b24e..b14f680eea 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt @@ -12,6 +12,7 @@ import org.robolectric.annotation.Config import org.signal.core.models.database.AttachmentId import org.signal.core.models.media.TransformProperties import org.signal.core.util.JsonUtils +import org.signal.mediasend.SentMediaQuality import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.database.AttachmentTable @@ -23,7 +24,6 @@ import org.thoughtcrime.securesms.jobs.AttachmentCopyJob import org.thoughtcrime.securesms.jobs.AttachmentUploadJob import org.thoughtcrime.securesms.jobs.protos.AttachmentUploadJobData import org.thoughtcrime.securesms.mms.OutgoingMessage -import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.testutil.UriAttachmentBuilder import org.thoughtcrime.securesms.util.MediaUtil diff --git a/core/util/build.gradle.kts b/core/util/build.gradle.kts index ae7c74b7c3..1dadb35e3b 100644 --- a/core/util/build.gradle.kts +++ b/core/util/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(libs.androidx.sqlite) implementation(libs.androidx.documentfile) implementation(libs.androidx.lifecycle.process) + implementation(libs.androidx.exifinterface) implementation(libs.kotlinx.serialization.json) implementation(libs.jackson.core) implementation(libs.jackson.module.kotlin) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BitmapDecodingException.java b/core/util/src/main/java/org/signal/core/util/bitmaps/BitmapDecodingException.java similarity index 61% rename from app/src/main/java/org/thoughtcrime/securesms/util/BitmapDecodingException.java rename to core/util/src/main/java/org/signal/core/util/bitmaps/BitmapDecodingException.java index c106159434..d14ed5bde4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BitmapDecodingException.java +++ b/core/util/src/main/java/org/signal/core/util/bitmaps/BitmapDecodingException.java @@ -1,4 +1,9 @@ -package org.thoughtcrime.securesms.util; +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.core.util.bitmaps; public class BitmapDecodingException extends Exception { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java b/core/util/src/main/java/org/signal/core/util/bitmaps/BitmapUtil.java similarity index 60% rename from app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java rename to core/util/src/main/java/org/signal/core/util/bitmaps/BitmapUtil.java index 3a64ee3e55..6566d5de35 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/core/util/src/main/java/org/signal/core/util/bitmaps/BitmapUtil.java @@ -1,6 +1,10 @@ -package org.thoughtcrime.securesms.util; +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.core.util.bitmaps; -import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; @@ -11,28 +15,20 @@ import android.graphics.YuvImage; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; -import kotlin.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; import androidx.exifinterface.media.ExifInterface; -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; - import org.signal.core.util.ThreadUtil; import org.signal.core.util.Util; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.mms.MediaConstraints; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Locale; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import javax.microedition.khronos.egl.EGL10; @@ -40,152 +36,12 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; +import kotlin.Pair; + public class BitmapUtil { private static final String TAG = Log.tag(BitmapUtil.class); - private static final int MAX_COMPRESSION_QUALITY = 90; - private static final int MIN_COMPRESSION_QUALITY = 45; - private static final int MAX_COMPRESSION_ATTEMPTS = 5; - private static final int MIN_COMPRESSION_QUALITY_DECREASE = 5; - private static final int MAX_IMAGE_HALF_SCALES = 3; - - /** - * @deprecated You probably want to use {@link ImageCompressionUtil} instead, which has a clearer - * contract and handles mimetypes properly. - */ - @Deprecated - @WorkerThread - public static ScaleResult createScaledBytes(@NonNull Context context, @NonNull T model, @NonNull MediaConstraints constraints) - throws BitmapDecodingException - { - return createScaledBytes(context, model, - constraints.getImageMaxWidth(context), - constraints.getImageMaxHeight(context), - constraints.getImageMaxSize(context)); - } - - /** - * @deprecated You probably want to use {@link ImageCompressionUtil} instead, which has a clearer - * contract and handles mimetypes properly. - */ - @Deprecated - @WorkerThread - public static ScaleResult createScaledBytes(@NonNull Context context, - @NonNull T model, - final int maxImageWidth, - final int maxImageHeight, - final int maxImageSize) - throws BitmapDecodingException - { - return createScaledBytes(context, model, maxImageWidth, maxImageHeight, maxImageSize, CompressFormat.JPEG); - } - - /** - * @deprecated You probably want to use {@link ImageCompressionUtil} instead, which has a clearer - * contract and handles mimetypes properly. - */ - @Deprecated - @WorkerThread - public static ScaleResult createScaledBytes(Context context, - T model, - int maxImageWidth, - int maxImageHeight, - int maxImageSize, - @NonNull CompressFormat format) - throws BitmapDecodingException - { - return createScaledBytes(context, model, maxImageWidth, maxImageHeight, maxImageSize, format, 1, 0); - } - - @WorkerThread - private static ScaleResult createScaledBytes(@NonNull Context context, - @NonNull T model, - final int maxImageWidth, - final int maxImageHeight, - final int maxImageSize, - @NonNull CompressFormat format, - final int sizeAttempt, - int totalAttempts) - throws BitmapDecodingException - { - try { - int quality = MAX_COMPRESSION_QUALITY; - int attempts = 0; - byte[] bytes; - - Bitmap scaledBitmap = Glide.with(context.getApplicationContext()) - .asBitmap() - .load(model) - .skipMemoryCache(true) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .centerInside() - .submit(maxImageWidth, maxImageHeight) - .get(); - - if (scaledBitmap == null) { - throw new BitmapDecodingException("Unable to decode image"); - } - - Log.i(TAG, String.format(Locale.US,"Initial scaled bitmap has size of %d bytes.", scaledBitmap.getByteCount())); - Log.i(TAG, String.format(Locale.US, "Max dimensions %d x %d, %d bytes", maxImageWidth, maxImageHeight, maxImageSize)); - - try { - do { - totalAttempts++; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - scaledBitmap.compress(format, quality, baos); - bytes = baos.toByteArray(); - - Log.d(TAG, "iteration with quality " + quality + " size " + bytes.length + " bytes."); - if (quality == MIN_COMPRESSION_QUALITY) break; - - int nextQuality = (int)Math.floor(quality * Math.sqrt((double)maxImageSize / bytes.length)); - if (quality - nextQuality < MIN_COMPRESSION_QUALITY_DECREASE) { - nextQuality = quality - MIN_COMPRESSION_QUALITY_DECREASE; - } - quality = Math.max(nextQuality, MIN_COMPRESSION_QUALITY); - } - while (bytes.length > maxImageSize && attempts++ < MAX_COMPRESSION_ATTEMPTS); - - if (bytes.length > maxImageSize) { - if (sizeAttempt <= MAX_IMAGE_HALF_SCALES) { - scaledBitmap.recycle(); - scaledBitmap = null; - - Log.i(TAG, "Halving dimensions and retrying."); - return createScaledBytes(context, model, maxImageWidth / 2, maxImageHeight / 2, maxImageSize, format, sizeAttempt + 1, totalAttempts); - } else { - throw new BitmapDecodingException("Unable to scale image below " + bytes.length + " bytes."); - } - } - - if (bytes.length <= 0) { - throw new BitmapDecodingException("Decoding failed. Bitmap has a length of " + bytes.length + " bytes."); - } - - Log.i(TAG, String.format(Locale.US, "createScaledBytes(%s) -> quality %d, %d attempt(s) over %d sizes.", model.getClass().getName(), quality, totalAttempts, sizeAttempt)); - - return new ScaleResult(bytes, scaledBitmap.getWidth(), scaledBitmap.getHeight()); - } finally { - if (scaledBitmap != null) scaledBitmap.recycle(); - } - } catch (InterruptedException | ExecutionException e) { - throw new BitmapDecodingException(e); - } - } - - public static @NonNull CompressFormat getCompressFormatForContentType(@Nullable String contentType) { - if (contentType == null) return CompressFormat.JPEG; - - switch (contentType) { - case MediaUtil.IMAGE_JPEG: return CompressFormat.JPEG; - case MediaUtil.IMAGE_PNG: return CompressFormat.PNG; - case MediaUtil.IMAGE_WEBP: return CompressFormat.WEBP; - default: return CompressFormat.JPEG; - } - } - private static BitmapFactory.Options getImageDimensions(InputStream inputStream) throws BitmapDecodingException { diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaConstraints.java b/feature/media-send/src/main/java/org/signal/mediasend/MediaConstraints.java new file mode 100644 index 0000000000..cadcde223b --- /dev/null +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaConstraints.java @@ -0,0 +1,102 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.mediasend; + +import android.content.Context; +import android.net.Uri; +import android.os.Build; + +import androidx.annotation.ChecksSdkIntAtLeast; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.signal.core.util.ContentTypeUtil; +import org.signal.core.util.logging.Log; +import org.signal.core.util.bitmaps.BitmapDecodingException; +import org.signal.core.util.bitmaps.BitmapUtil; +import org.thoughtcrime.securesms.video.TranscodingPreset; + +import java.io.IOException; +import java.io.InputStream; + +import kotlin.Pair; + +public abstract class MediaConstraints { + private static final String TAG = Log.tag(MediaConstraints.class); + + public abstract int getImageMaxWidth(Context context); + public abstract int getImageMaxHeight(Context context); + public abstract int getImageMaxSize(Context context); + + public TranscodingPreset getVideoTranscodingSettings() { + return TranscodingPreset.LEVEL_1; + } + + /** + * Provide a list of dimensions that should be attempted during compression. We will keep moving + * down the list until the image can be scaled to fit under {@link #getImageMaxSize(Context)}. + * The first entry in the list should match your max width/height. + */ + public abstract int[] getImageDimensionTargets(Context context); + + public abstract long getGifMaxSize(Context context); + public abstract long getVideoMaxSize(); + + public @IntRange(from = 0, to = 100) int getImageCompressionQualitySetting(@NonNull Context context) { + return 70; + } + + public long getUncompressedVideoMaxSize(Context context) { + return getVideoMaxSize(); + } + + public long getCompressedVideoMaxSize(Context context) { + return getVideoMaxSize(); + } + + public abstract long getAudioMaxSize(Context context); + public abstract long getDocumentMaxSize(Context context); + + public abstract long getMaxAttachmentSize(); + + public boolean isSatisfied(@NonNull Context context, @NonNull Uri uri, @NonNull String contentType, long size) { + try { + if (size > getMaxAttachmentSize()) { + return false; + } + return (ContentTypeUtil.isGif(contentType) && size <= getGifMaxSize(context) && isWithinBounds(context, uri)) || + (ContentTypeUtil.isImageType(contentType) && size <= getImageMaxSize(context) && isWithinBounds(context, uri)) || + (ContentTypeUtil.isAudioType(contentType) && size <= getAudioMaxSize(context)) || + (ContentTypeUtil.isVideoType(contentType) && size <= getVideoMaxSize()) || + (ContentTypeUtil.isDocumentType(contentType) && size <= getDocumentMaxSize(context)); + } catch (IOException ioe) { + Log.w(TAG, "Failed to determine if media's constraints are satisfied.", ioe); + return false; + } + } + + private boolean isWithinBounds(Context context, Uri uri) throws IOException { + try { + InputStream is = MediaSendDependencies.INSTANCE.getMediaSendRepository().getAttachmentStream(context, uri); + Pair dimensions = BitmapUtil.getDimensions(is); +return dimensions.getFirst() > 0 && dimensions.getFirst() <= getImageMaxWidth(context) && + dimensions.getSecond() > 0 && dimensions.getSecond() <= getImageMaxHeight(context); + } catch (BitmapDecodingException e) { + throw new IOException(e); + } + } + + public boolean canResize(@Nullable String mediaType) { + return ContentTypeUtil.isImageType(mediaType) && !ContentTypeUtil.isGif(mediaType) || + ContentTypeUtil.isVideoType(mediaType) && isVideoTranscodeAvailable(); + } + + @ChecksSdkIntAtLeast(api = 26) + public static boolean isVideoTranscodeAvailable() { + return Build.VERSION.SDK_INT >= 26; + } +} diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendActivityContract.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendActivityContract.kt index aa8b8b33ca..6616ea6d01 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendActivityContract.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendActivityContract.kt @@ -202,7 +202,7 @@ class MediaSendActivityContract(private val clazz: Class) : Activi val videoTrim: Boolean = false, val videoTrimStartTimeUs: Long = 0, val videoTrimEndTimeUs: Long = 0, - val sentMediaQuality: Int = 0, + val sentMediaQuality: SentMediaQuality = SentMediaQuality.STANDARD, val mp4FastStart: Boolean = false ) : Parcelable diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendRepository.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendRepository.kt index 32046a5814..ea596c1087 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendRepository.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendRepository.kt @@ -5,10 +5,12 @@ package org.signal.mediasend +import android.content.Context import android.net.Uri import kotlinx.coroutines.flow.Flow import org.signal.core.models.media.Media import org.signal.core.models.media.MediaFolder +import java.io.InputStream /** * Repository interface for media send operations that require app-layer implementation. @@ -57,11 +59,11 @@ interface MediaSendRepository { /** * Gets the maximum video duration in microseconds based on quality and file size limits. * - * @param quality The sent media quality code. + * @param quality The sent media quality. * @param maxFileSizeBytes Maximum file size in bytes. * @return Maximum duration in microseconds. */ - fun getMaxVideoDurationUs(quality: Int, maxFileSizeBytes: Long): Long + fun getMaxVideoDurationUs(quality: SentMediaQuality, maxFileSizeBytes: Long): Long /** * Gets the maximum video file size in bytes. @@ -98,6 +100,8 @@ interface MediaSendRepository { * @return Flow that emits whenever recipient validity changes. */ fun observeRecipientValid(recipientId: MediaRecipientId): Flow + + fun getAttachmentStream(context: Context, uri: Uri): InputStream } /** @@ -126,7 +130,7 @@ sealed interface MediaFilterError { data class SendRequest( val selectedMedia: List, val editorStateMap: Map, - val quality: Int, + val quality: SentMediaQuality, val message: String?, val isViewOnce: Boolean, val singleRecipientId: MediaRecipientId?, diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendState.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendState.kt index 63d6de56f4..fa7c4eef9a 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendState.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendState.kt @@ -37,10 +37,7 @@ data class MediaSendState( val focusedMedia: Media? = null, val isMeteredConnection: Boolean = false, val isPreUploadEnabled: Boolean = false, - /** - * Int code to avoid depending on app-layer enums. Conventionally 0 == STANDARD. - */ - val sentMediaQuality: Int = 0, + val sentMediaQuality: SentMediaQuality = SentMediaQuality.STANDARD, /** * Per-media editor state keyed by URI (video trim data, image editor data, etc.). */ diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendViewModel.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendViewModel.kt index 928fa75e04..099285da95 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendViewModel.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendViewModel.kt @@ -400,7 +400,7 @@ class MediaSendViewModel( * * Cancels all pre-uploads and re-initializes video trim data. */ - fun setSentMediaQuality(sentMediaQuality: Int) { + fun setSentMediaQuality(sentMediaQuality: SentMediaQuality) { val snapshot = state.value if (snapshot.sentMediaQuality == sentMediaQuality) return diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SentMediaQuality.kt b/feature/media-send/src/main/java/org/signal/mediasend/SentMediaQuality.kt similarity index 71% rename from app/src/main/java/org/thoughtcrime/securesms/mms/SentMediaQuality.kt rename to feature/media-send/src/main/java/org/signal/mediasend/SentMediaQuality.kt index fa5b3d5ba6..731555c481 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SentMediaQuality.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/SentMediaQuality.kt @@ -1,15 +1,19 @@ -package org.thoughtcrime.securesms.mms +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.mediasend import android.content.Context import androidx.annotation.StringRes -import org.thoughtcrime.securesms.R /** * Quality levels to send media at. */ enum class SentMediaQuality(@JvmField val code: Int, @param:StringRes private val label: Int) { - STANDARD(0, R.string.DataAndStorageSettingsFragment__standard), - HIGH(1, R.string.DataAndStorageSettingsFragment__high); + STANDARD(0, R.string.SentMediaQuality__standard), + HIGH(1, R.string.SentMediaQuality__high); companion object { @JvmStatic diff --git a/feature/media-send/src/main/java/org/signal/mediasend/capture/CameraFragment.java b/feature/media-send/src/main/java/org/signal/mediasend/capture/CameraFragment.java new file mode 100644 index 0000000000..237fd14c8d --- /dev/null +++ b/feature/media-send/src/main/java/org/signal/mediasend/capture/CameraFragment.java @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.mediasend.capture; + +import androidx.annotation.NonNull; + +import org.signal.mediasend.MediaConstraints; + +import java.io.FileDescriptor; + +public interface CameraFragment { + + void presentHud(int selectedMediaCount); + void fadeOutControls(@NonNull Runnable onEndAction); + void fadeInControls(); + + interface Controller { + void onImageCaptured(@NonNull byte[] data, int width, int height); + void onVideoCaptured(@NonNull FileDescriptor fd); + void onVideoCaptureError(); + void onGalleryClicked(); + void onCameraCountButtonClicked(); + void onQrCodeFound(@NonNull String data); + @NonNull MediaConstraints getMediaConstraints(); + int getMaxVideoDuration(); + } +} diff --git a/feature/media-send/src/main/res/values/strings.xml b/feature/media-send/src/main/res/values/strings.xml index f497b57832..227797302e 100644 --- a/feature/media-send/src/main/res/values/strings.xml +++ b/feature/media-send/src/main/res/values/strings.xml @@ -14,4 +14,8 @@ Message Next + + High + + Standard diff --git a/lib/qr/src/main/java/org/signal/qr/QrScannerView.kt b/lib/qr/src/main/java/org/signal/qr/QrScannerView.kt index 831ba57fcc..21fc4b7af0 100644 --- a/lib/qr/src/main/java/org/signal/qr/QrScannerView.kt +++ b/lib/qr/src/main/java/org/signal/qr/QrScannerView.kt @@ -26,12 +26,8 @@ class QrScannerView @JvmOverloads constructor( val qrData: Observable = qrDataPublish - private fun initScannerView(forceLegacy: Boolean) { - val scannerView: FrameLayout = if (!forceLegacy) { - ScannerView21(context) { qrDataPublish.onNext(it) } - } else { - ScannerView19(context) { qrDataPublish.onNext(it) } - } + private fun initScannerView() { + val scannerView: FrameLayout = ScannerView21(context) { qrDataPublish.onNext(it) } scannerView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) addView(scannerView) @@ -39,14 +35,13 @@ class QrScannerView @JvmOverloads constructor( this.scannerView = (scannerView as ScannerView) } - @JvmOverloads - fun start(lifecycleOwner: LifecycleOwner, forceLegacy: Boolean = false) { + fun start(lifecycleOwner: LifecycleOwner) { if (scannerView != null) { Log.w(TAG, "Attempt to start scanning that has already started") return } - initScannerView(forceLegacy) + initScannerView() scannerView?.start(lifecycleOwner) lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {