diff --git a/app/build.gradle b/app/build.gradle index 55a440e1c1..dc4efd6b5d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,7 +29,7 @@ configurations.all { } def canonicalVersionCode = 379 -def canonicalVersionName = "1.19.0" +def canonicalVersionName = "1.19.1" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, @@ -246,7 +246,7 @@ dependencies { implementation "androidx.appcompat:appcompat:$appcompatVersion" implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "com.google.android.material:material:$materialVersion" - implementation 'com.google.android:flexbox:2.0.1' + implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation 'androidx.legacy:legacy-support-v13:1.0.0' implementation 'androidx.cardview:cardview:1.0.0' implementation "androidx.preference:preference-ktx:$preferenceVersion" @@ -273,11 +273,11 @@ dependencies { if (project.hasProperty('huawei')) huaweiImplementation 'com.huawei.hms:push:6.7.0.300' - implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1' - implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1' + implementation 'androidx.media3:media3-exoplayer:1.4.0' + implementation 'androidx.media3:media3-ui:1.4.0' implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'org.signal:aesgcmprovider:0.0.3' - implementation 'org.webrtc:google-webrtc:1.0.32006' + implementation 'io.github.webrtc-sdk:android:125.6422.04' implementation "me.leolin:ShortcutBadger:1.1.16" implementation 'se.emilsjolander:stickylistheaders:2.7.0' implementation 'com.jpardogo.materialtabstrip:library:1.0.9' @@ -289,17 +289,13 @@ dependencies { implementation 'com.pnikosis:materialish-progress:1.5' implementation 'org.greenrobot:eventbus:3.0.0' implementation 'pl.tajchert:waitingdots:0.1.0' - implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' + implementation 'com.vanniktech:android-image-cropper:4.5.0' implementation 'com.melnykov:floatingactionbutton:1.3.0' implementation 'com.google.zxing:android-integration:3.1.0' - implementation 'mobi.upod:time-duration-picker:1.1.3' implementation 'com.google.zxing:core:3.2.1' implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') { exclude group: 'com.android.support', module: 'support-annotations' } - implementation ('cn.carbswang.android:NumberPickerView:1.0.9') { - exclude group: 'com.android.support', module: 'appcompat-v7' - } implementation ('com.tomergoldst.android:tooltips:1.0.6') { exclude group: 'com.android.support', module: 'appcompat-v7' } @@ -308,7 +304,7 @@ dependencies { exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection' } implementation 'com.annimon:stream:1.1.8' - implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' + implementation project(':stickyheader') implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2' implementation 'androidx.sqlite:sqlite-ktx:2.3.1' implementation 'net.zetetic:sqlcipher-android:4.5.4@aar' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c2a985df7d..38f83051f5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,8 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> - + HARDWARE_AEC_BLACKLIST = new HashSet() {{ - add("Pixel"); - add("Pixel XL"); - add("Moto G5"); - add("Moto G (5S) Plus"); - add("Moto G4"); - add("TA-1053"); - add("Mi A1"); - add("E5823"); // Sony z5 compact - add("Redmi Note 5"); - add("FP2"); // Fairphone FP2 - add("MI 5"); - }}; - - Set OPEN_SL_ES_WHITELIST = new HashSet() {{ - add("Pixel"); - add("Pixel XL"); - }}; - - if (HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) { - WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true); - } - - if (!OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) { - WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true); - } - PeerConnectionFactory.initialize(InitializationOptions.builder(this).createInitializationOptions()); } catch (UnsatisfiedLinkError e) { Log.w(TAG, e); diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java index 48b34376ca..3a07eba357 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java @@ -6,8 +6,6 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.media.AudioManager; -import android.net.Uri; -import android.os.Build; import android.os.Handler; import android.os.Message; import android.os.PowerManager.WakeLock; @@ -15,24 +13,17 @@ import android.os.PowerManager; import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.DefaultLoadControl; -import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.LoadControl; -import com.google.android.exoplayer2.PlaybackParameters; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.audio.AudioAttributes; -import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; -import com.google.android.exoplayer2.source.ExtractorMediaSource; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import androidx.annotation.OptIn; +import androidx.media3.common.AudioAttributes; +import androidx.media3.common.C; +import androidx.media3.common.MediaItem; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.PlaybackParameters; +import androidx.media3.common.Player; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.ExoPlayer; import java.io.IOException; import java.lang.ref.WeakReference; -import network.loki.messenger.BuildConfig; import org.jetbrains.annotations.NotNull; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.Util; @@ -56,7 +47,7 @@ public class AudioSlidePlayer implements SensorEventListener { private final @Nullable WakeLock wakeLock; private @NonNull WeakReference listener; - private @Nullable SimpleExoPlayer mediaPlayer; + private @Nullable ExoPlayer mediaPlayer; private @Nullable AttachmentServer audioAttachmentServer; private long startTime; @@ -89,40 +80,38 @@ public class AudioSlidePlayer implements SensorEventListener { this.sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); this.proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - if (Build.VERSION.SDK_INT >= 21) { - this.wakeLock = ServiceUtil.getPowerManager(context).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); - } else { - this.wakeLock = null; - } + this.wakeLock = ServiceUtil.getPowerManager(context).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); } public void play(final double progress) throws IOException { play(progress, false); } + @OptIn(markerClass = UnstableApi.class) private void play(final double progress, boolean earpiece) throws IOException { if (this.mediaPlayer != null) { stop(); } - LoadControl loadControl = new DefaultLoadControl.Builder().setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE).createDefaultLoadControl(); - this.mediaPlayer = ExoPlayerFactory.newSimpleInstance(context, new DefaultRenderersFactory(context), new DefaultTrackSelector(), loadControl); + this.mediaPlayer = new ExoPlayer.Builder(context).build(); this.audioAttachmentServer = new AttachmentServer(context, slide.asAttachment()); this.startTime = System.currentTimeMillis(); audioAttachmentServer.start(); - mediaPlayer.prepare(createMediaSource(audioAttachmentServer.getUri())); - mediaPlayer.setPlayWhenReady(true); + MediaItem mediaItem = MediaItem.fromUri(audioAttachmentServer.getUri()); + mediaPlayer.setMediaItem(mediaItem); + mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() - .setContentType(earpiece ? C.CONTENT_TYPE_SPEECH : C.CONTENT_TYPE_MUSIC) + .setContentType(earpiece ? C.AUDIO_CONTENT_TYPE_SPEECH : C.AUDIO_CONTENT_TYPE_MUSIC) .setUsage(earpiece ? C.USAGE_VOICE_COMMUNICATION : C.USAGE_MEDIA) - .build()); - mediaPlayer.addListener(new Player.EventListener() { + .build(), + !earpiece); + mediaPlayer.addListener(new Player.Listener() { boolean started = false; @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - Log.d(TAG, "onPlayerStateChanged(" + playWhenReady + ", " + playbackState + ")"); + public void onPlaybackStateChanged(int playbackState) { + Log.d(TAG, "onPlaybackStateChanged(" + playbackState + ")"); switch (playbackState) { case Player.STATE_READY: Log.i(TAG, "onPrepared() " + mediaPlayer.getBufferedPercentage() + "% buffered"); @@ -166,9 +155,7 @@ public class AudioSlidePlayer implements SensorEventListener { sensorManager.unregisterListener(AudioSlidePlayer.this); if (wakeLock != null && wakeLock.isHeld()) { - if (Build.VERSION.SDK_INT >= 21) { - wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY); - } + wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY); } } @@ -178,8 +165,9 @@ public class AudioSlidePlayer implements SensorEventListener { } } + @Override - public void onPlayerError(ExoPlaybackException error) { + public void onPlayerError(PlaybackException error) { Log.w(TAG, "MediaPlayer Error: " + error); synchronized (AudioSlidePlayer.this) { @@ -193,9 +181,7 @@ public class AudioSlidePlayer implements SensorEventListener { sensorManager.unregisterListener(AudioSlidePlayer.this); if (wakeLock != null && wakeLock.isHeld()) { - if (Build.VERSION.SDK_INT >= 21) { - wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY); - } + wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY); } } @@ -203,12 +189,9 @@ public class AudioSlidePlayer implements SensorEventListener { progressEventHandler.removeMessages(0); } }); - } - private MediaSource createMediaSource(@NonNull Uri uri) { - return new ExtractorMediaSource.Factory(new DefaultDataSourceFactory(context, BuildConfig.USER_AGENT)) - .setExtractorsFactory(new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)) - .createMediaSource(uri); + mediaPlayer.prepare(); + mediaPlayer.setPlayWhenReady(true); } public synchronized void stop() { @@ -340,14 +323,18 @@ public class AudioSlidePlayer implements SensorEventListener { int streamType; - if (event.values[0] < 5f && event.values[0] != proximitySensor.getMaximumRange()) { - streamType = AudioManager.STREAM_VOICE_CALL; + if ( + proximitySensor != null && + event.values[0] < 5f && + event.values[0] != proximitySensor.getMaximumRange() + ) { + streamType = C.AUDIO_CONTENT_TYPE_SPEECH; } else { - streamType = AudioManager.STREAM_MUSIC; + streamType = C.AUDIO_CONTENT_TYPE_MUSIC; } - if (streamType == AudioManager.STREAM_VOICE_CALL && - mediaPlayer.getAudioStreamType() != streamType && + if (streamType == C.AUDIO_CONTENT_TYPE_SPEECH && + mediaPlayer.getAudioAttributes().contentType != streamType && !audioManager.isWiredHeadsetOn()) { double position = mediaPlayer.getCurrentPosition(); @@ -361,11 +348,11 @@ public class AudioSlidePlayer implements SensorEventListener { } catch (IOException e) { Log.w(TAG, e); } - } else if (streamType == AudioManager.STREAM_MUSIC && - mediaPlayer.getAudioStreamType() != streamType && + } else if (streamType == C.AUDIO_CONTENT_TYPE_MUSIC && + mediaPlayer.getAudioAttributes().contentType != streamType && System.currentTimeMillis() - startTime > 500) { - if (wakeLock != null) wakeLock.release(); + if (wakeLock != null && wakeLock.isHeld()) wakeLock.release(); stop(); notifyOnStop(); } @@ -403,7 +390,7 @@ public class AudioSlidePlayer implements SensorEventListener { sendEmptyMessageDelayed(0, 50); } - private boolean isPlayerActive(@NonNull SimpleExoPlayer player) { + private boolean isPlayerActive(@NonNull ExoPlayer player) { return player.getPlaybackState() == Player.STATE_READY || player.getPlaybackState() == Player.STATE_BUFFERING; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.java b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.java deleted file mode 100644 index 63fad1e6de..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.thoughtcrime.securesms.avatar; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.provider.MediaStore; - -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.core.content.ContextCompat; - -import com.theartofdev.edmodo.cropper.CropImage; -import com.theartofdev.edmodo.cropper.CropImageView; - -import org.session.libsignal.utilities.NoExternalStorageException; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.ExternalStorageUtil; -import org.thoughtcrime.securesms.util.FileProviderUtil; -import org.thoughtcrime.securesms.util.IntentUtils; - -import java.io.File; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; - -import network.loki.messenger.R; - -import static android.provider.MediaStore.EXTRA_OUTPUT; - -public final class AvatarSelection { - - private static final String TAG = AvatarSelection.class.getSimpleName(); - - public static final int REQUEST_CODE_CROP_IMAGE = CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE; - public static final int REQUEST_CODE_AVATAR = REQUEST_CODE_CROP_IMAGE + 1; - - private AvatarSelection() { - } - - /** - * Returns result on {@link #REQUEST_CODE_CROP_IMAGE} - */ - public static void circularCropImage(Activity activity, Uri inputFile, Uri outputFile, @StringRes int title) { - CropImage.activity(inputFile) - .setGuidelines(CropImageView.Guidelines.ON) - .setAspectRatio(1, 1) - .setCropShape(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? CropImageView.CropShape.RECTANGLE : CropImageView.CropShape.OVAL) - .setOutputUri(outputFile) - .setAllowRotation(true) - .setAllowFlipping(true) - .setBackgroundColor(ContextCompat.getColor(activity, R.color.avatar_background)) - .setActivityTitle(activity.getString(title)) - .start(activity); - } - - public static Uri getResultUri(Intent data) { - return CropImage.getActivityResult(data).getUri(); - } - - /** - * Returns result on {@link #REQUEST_CODE_AVATAR} - * - * @return Temporary capture file if created. - */ - public static File startAvatarSelection(Activity activity, boolean includeClear, boolean attemptToIncludeCamera) { - File captureFile = null; - boolean hasCameraPermission = ContextCompat - .checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; - if (attemptToIncludeCamera && hasCameraPermission) { - try { - captureFile = File.createTempFile("avatar-capture", ".jpg", ExternalStorageUtil.getImageDir(activity)); - } catch (IOException | NoExternalStorageException e) { - Log.e("Cannot reserve a temporary avatar capture file.", e); - } - } - - Intent chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear); - activity.startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR); - return captureFile; - } - - private static Intent createAvatarSelectionIntent(Context context, @Nullable File tempCaptureFile, boolean includeClear) { - List extraIntents = new LinkedList<>(); - Intent galleryIntent = new Intent(Intent.ACTION_PICK); - galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*"); - - if (!IntentUtils.isResolvable(context, galleryIntent)) { - galleryIntent = new Intent(Intent.ACTION_GET_CONTENT); - galleryIntent.setType("image/*"); - } - - if (tempCaptureFile != null) { - Uri uri = FileProviderUtil.getUriFor(context, tempCaptureFile); - Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - cameraIntent.putExtra(EXTRA_OUTPUT, uri); - cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - extraIntents.add(cameraIntent); - } - - if (includeClear) { - extraIntents.add(new Intent("network.loki.securesms.action.CLEAR_PROFILE_PHOTO")); - } - - Intent chooserIntent = Intent.createChooser(galleryIntent, context.getString(R.string.profileDisplayPicture)); - - if (!extraIntents.isEmpty()) { - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Intent[0])); - } - - return chooserIntent; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.kt new file mode 100644 index 0000000000..adf4b0927c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarSelection.kt @@ -0,0 +1,141 @@ +package org.thoughtcrime.securesms.avatar + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.provider.MediaStore +import androidx.activity.result.ActivityResultLauncher +import androidx.core.content.ContextCompat +import com.canhub.cropper.CropImageContractOptions +import com.canhub.cropper.CropImageOptions +import com.canhub.cropper.CropImageView +import network.loki.messenger.R +import org.session.libsession.utilities.getColorFromAttr +import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.NoExternalStorageException +import org.thoughtcrime.securesms.util.FileProviderUtil +import org.thoughtcrime.securesms.util.IntentUtils +import java.io.File +import java.io.IOException +import java.util.LinkedList + +class AvatarSelection( + private val activity: Activity, + private val onAvatarCropped: ActivityResultLauncher, + private val onPickImage: ActivityResultLauncher +) { + private val TAG: String = AvatarSelection::class.java.simpleName + + private val bgColor by lazy { activity.getColorFromAttr(android.R.attr.colorPrimary) } + private val txtColor by lazy { activity.getColorFromAttr(android.R.attr.textColorPrimary) } + private val imageScrim by lazy { ContextCompat.getColor(activity, R.color.avatar_background) } + private val activityTitle by lazy { activity.getString(R.string.CropImageActivity_profile_avatar) } + + /** + * Returns result on [.REQUEST_CODE_CROP_IMAGE] + */ + fun circularCropImage( + inputFile: Uri?, + outputFile: Uri? + ) { + onAvatarCropped.launch( + CropImageContractOptions( + uri = inputFile, + cropImageOptions = CropImageOptions( + guidelines = CropImageView.Guidelines.ON, + aspectRatioX = 1, + aspectRatioY = 1, + fixAspectRatio = true, + cropShape = CropImageView.CropShape.OVAL, + customOutputUri = outputFile, + allowRotation = true, + allowFlipping = true, + backgroundColor = imageScrim, + toolbarColor = bgColor, + activityBackgroundColor = bgColor, + toolbarTintColor = txtColor, + toolbarBackButtonColor = txtColor, + toolbarTitleColor = txtColor, + activityMenuIconColor = txtColor, + activityMenuTextColor = txtColor, + activityTitle = activityTitle + ) + ) + ) + } + + /** + * Returns result on [.REQUEST_CODE_AVATAR] + * + * @return Temporary capture file if created. + */ + fun startAvatarSelection( + includeClear: Boolean, + attemptToIncludeCamera: Boolean + ): File? { + var captureFile: File? = null + val hasCameraPermission = ContextCompat + .checkSelfPermission( + activity, + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + if (attemptToIncludeCamera && hasCameraPermission) { + try { + captureFile = File.createTempFile("avatar-capture", ".jpg", getImageDir(activity)) + } catch (e: IOException) { + Log.e("Cannot reserve a temporary avatar capture file.", e) + } catch (e: NoExternalStorageException) { + Log.e("Cannot reserve a temporary avatar capture file.", e) + } + } + + val chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear) + onPickImage.launch(chooserIntent) + return captureFile + } + + private fun createAvatarSelectionIntent( + context: Context, + tempCaptureFile: File?, + includeClear: Boolean + ): Intent { + val extraIntents: MutableList = LinkedList() + var galleryIntent = Intent(Intent.ACTION_PICK) + galleryIntent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*") + + if (!IntentUtils.isResolvable(context, galleryIntent)) { + galleryIntent = Intent(Intent.ACTION_GET_CONTENT) + galleryIntent.setType("image/*") + } + + if (tempCaptureFile != null) { + val uri = FileProviderUtil.getUriFor(context, tempCaptureFile) + val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri) + cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + extraIntents.add(cameraIntent) + } + + if (includeClear) { + extraIntents.add(Intent("network.loki.securesms.action.CLEAR_PROFILE_PHOTO")) + } + + val chooserIntent = Intent.createChooser( + galleryIntent, + context.getString(R.string.CreateProfileActivity_profile_photo) + ) + + if (!extraIntents.isEmpty()) { + chooserIntent.putExtra( + Intent.EXTRA_INITIAL_INTENTS, + extraIntents.toTypedArray() + ) + } + + return chooserIntent + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt index 03df07366d..7205f92d66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -6,13 +6,9 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.graphics.Outline -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorManager import android.media.AudioManager import android.os.Build import android.os.Bundle -import android.provider.Settings import android.view.MenuItem import android.view.View import android.view.ViewOutlineProvider @@ -23,8 +19,6 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job @@ -34,7 +28,6 @@ import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ActivityWebrtcBinding import org.apache.commons.lang3.time.DurationFormatUtils -import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.TextSecurePreferences @@ -44,7 +37,6 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.service.WebRtcCallService -import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator import org.thoughtcrime.securesms.webrtc.AudioManagerCommand import org.thoughtcrime.securesms.webrtc.CallViewModel import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_CONNECTED @@ -56,7 +48,6 @@ import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_RINGING import org.thoughtcrime.securesms.webrtc.Orientation import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.EARPIECE import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SPEAKER_PHONE -import kotlin.math.asin @AndroidEntryPoint class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { @@ -73,7 +64,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { } private val viewModel by viewModels() - private val glide by lazy { Glide.with(this) } private lateinit var binding: ActivityWebrtcBinding private var uiJob: Job? = null private var wantsToAnswer = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java deleted file mode 100644 index 0139e9932c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.provider.ContactsContract; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewOutlineProvider; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.AppCompatImageView; - -import com.bumptech.glide.load.engine.DiskCacheStrategy; - -import org.session.libsession.avatars.ContactColors; -import org.session.libsession.avatars.ContactPhoto; -import org.session.libsession.avatars.ResourceContactPhoto; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.ThemeUtil; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientExporter; -import com.bumptech.glide.Glide; -import com.bumptech.glide.RequestManager; -import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator; - -import java.util.Objects; - -import network.loki.messenger.R; - -public class AvatarImageView extends AppCompatImageView { - - private static final String TAG = AvatarImageView.class.getSimpleName(); - - private static final Paint LIGHT_THEME_OUTLINE_PAINT = new Paint(); - private static final Paint DARK_THEME_OUTLINE_PAINT = new Paint(); - - static { - LIGHT_THEME_OUTLINE_PAINT.setColor(Color.argb((int) (255 * 0.2), 0, 0, 0)); - LIGHT_THEME_OUTLINE_PAINT.setStyle(Paint.Style.STROKE); - LIGHT_THEME_OUTLINE_PAINT.setStrokeWidth(1f); - LIGHT_THEME_OUTLINE_PAINT.setAntiAlias(true); - - DARK_THEME_OUTLINE_PAINT.setColor(Color.argb((int) (255 * 0.2), 255, 255, 255)); - DARK_THEME_OUTLINE_PAINT.setStyle(Paint.Style.STROKE); - DARK_THEME_OUTLINE_PAINT.setStrokeWidth(1f); - DARK_THEME_OUTLINE_PAINT.setAntiAlias(true); - } - - private boolean inverted; - private Paint outlinePaint; - private OnClickListener listener; - - private @Nullable RecipientContactPhoto recipientContactPhoto; - private @NonNull Drawable unknownRecipientDrawable; - - public AvatarImageView(Context context) { - super(context); - initialize(context, null); - } - - public AvatarImageView(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(context, attrs); - } - - private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) { - setScaleType(ScaleType.CENTER_CROP); - - if (attrs != null) { - TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0); - inverted = typedArray.getBoolean(0, false); - typedArray.recycle(); - } - - outlinePaint = ThemeUtil.isDarkTheme(getContext()) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT; - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setOval(0, 0, view.getWidth(), view.getHeight()); - } - }); - setClipToOutline(true); - - unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_default).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - float width = getWidth() - getPaddingRight() - getPaddingLeft(); - float height = getHeight() - getPaddingBottom() - getPaddingTop(); - float cx = width / 2f; - float cy = height / 2f; - float radius = Math.min(cx, cy) - (outlinePaint.getStrokeWidth() / 2f); - - canvas.translate(getPaddingLeft(), getPaddingTop()); - canvas.drawCircle(cx, cy, radius, outlinePaint); - } - - @Override - public void setOnClickListener(OnClickListener listener) { - this.listener = listener; - super.setOnClickListener(listener); - } - - public void update(String hexEncodedPublicKey) { - Address address = Address.fromSerialized(hexEncodedPublicKey); - Recipient recipient = Recipient.from(getContext(), address, false); - updateAvatar(recipient); - } - - private void updateAvatar(Recipient recipient) { - setAvatar(Glide.with(getContext()), recipient, false); - } - - public void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) { - if (recipient != null) { - if (recipient.isLocalNumber()) { - setImageDrawable(new ResourceContactPhoto(R.drawable.ic_note_to_self).asDrawable(getContext(), recipient.getColor().toAvatarColor(getContext()), inverted)); - } else { - RecipientContactPhoto photo = new RecipientContactPhoto(recipient); - if (!photo.equals(recipientContactPhoto)) { - requestManager.clear(this); - recipientContactPhoto = photo; - - Drawable photoPlaceholderDrawable = AvatarPlaceholderGenerator.generate( - getContext(), 128, recipient.getAddress().serialize(), recipient.getName()); - - if (photo.contactPhoto != null) { - requestManager.load(photo.contactPhoto) - .fallback(photoPlaceholderDrawable) - .error(photoPlaceholderDrawable) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .circleCrop() - .into(this); - } else { - requestManager.load(photoPlaceholderDrawable) - .circleCrop() - .into(this); -// setImageDrawable(photoPlaceholderDrawable); - } - } - } - } else { - recipientContactPhoto = null; - requestManager.clear(this); - setImageDrawable(unknownRecipientDrawable); - super.setOnClickListener(listener); - } - } - - public void clear(@NonNull RequestManager glideRequests) { - glideRequests.clear(this); - } - - private void setAvatarClickHandler(final Recipient recipient, boolean quickContactEnabled) { - if (!recipient.isGroupRecipient() && quickContactEnabled) { - super.setOnClickListener(v -> { - if (recipient.getContactUri() != null) { - ContactsContract.QuickContact.showQuickContact(getContext(), AvatarImageView.this, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null); - } else { - getContext().startActivity(RecipientExporter.export(recipient).asAddContactIntent()); - } - }); - } else { - super.setOnClickListener(listener); - } - } - - private static class RecipientContactPhoto { - - private final @NonNull Recipient recipient; - private final @Nullable ContactPhoto contactPhoto; - private final boolean ready; - - RecipientContactPhoto(@NonNull Recipient recipient) { - this.recipient = recipient; - this.ready = !recipient.isResolving(); - this.contactPhoto = recipient.getContactPhoto(); - } - - public boolean equals(@Nullable RecipientContactPhoto other) { - if (other == null) return false; - - return other.recipient.equals(recipient) && - other.recipient.getColor().equals(recipient.getColor()) && - other.ready == ready && - Objects.equals(other.contactPhoto, contactPhoto); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 571c778d4a..9511bddb6a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -38,10 +38,13 @@ class ProfilePictureView @JvmOverloads constructor( var additionalDisplayName: String? = null private val profilePicturesCache = mutableMapOf() + private val resourcePadding by lazy { + context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat() + } private val unknownRecipientDrawable by lazy { ResourceContactPhoto(R.drawable.ic_profile_default) - .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) } + .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) } private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification) - .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) } + .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) } constructor(context: Context, sender: Recipient): this(context) { update(sender) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index d0bec0b710..b5553a1157 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -868,9 +868,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun onDestroy() { - viewModel.saveDraft(binding.inputBar.text.trim()) - cancelVoiceMessage() - tearDownRecipientObserver() + if(::binding.isInitialized) { + viewModel.saveDraft(binding.inputBar.text.trim()) + cancelVoiceMessage() + tearDownRecipientObserver() + } super.onDestroy() } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 3115275773..08aad8b6df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -536,7 +536,7 @@ open class Storage( } private fun updateConvoVolatile(convos: ConversationVolatileConfig, messageTimestamp: Long) { - val extracted = convos.all() + val extracted = convos.all().filterNotNull() for (conversation in extracted) { val threadId = when (conversation) { is Conversation.OneToOne -> getThreadIdFor(conversation.accountId, null, null, createThread = false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java index 5e4ee5f3e7..825012c032 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendVideoFragment.java @@ -4,7 +4,10 @@ import android.net.Uri; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import androidx.fragment.app.Fragment; +import androidx.media3.common.util.UnstableApi; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -16,6 +19,7 @@ import org.thoughtcrime.securesms.video.VideoPlayer; import java.io.IOException; +@OptIn(markerClass = UnstableApi.class) public class MediaSendVideoFragment extends Fragment implements MediaSendPageFragment { private static final String TAG = MediaSendVideoFragment.class.getSimpleName(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index c18b950959..3a13f3f430 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -28,7 +28,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.session.libsession.avatars.ContactColors; import org.session.libsession.avatars.ContactPhoto; -import org.session.libsession.avatars.GeneratedContactPhoto; +import org.session.libsession.avatars.ResourceContactPhoto; import org.session.libsession.messaging.contacts.Contact; import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.TextSecurePreferences; @@ -60,6 +60,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil private CharSequence contentTitle; private CharSequence contentText; + private static final Integer ICON_SIZE = 128; + public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy) { super(context, privacy); @@ -108,7 +110,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil } else { setContentTitle(context.getString(R.string.app_name)); - setLargeIcon(new GeneratedContactPhoto("Unknown", R.drawable.ic_profile_default).asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context))); + setLargeIcon(AvatarPlaceholderGenerator.generate(context, ICON_SIZE, "", "Unknown")); } } @@ -330,7 +332,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil private static Drawable getPlaceholderDrawable(Context context, Recipient recipient) { String publicKey = recipient.getAddress().serialize(); String displayName = recipient.getName(); - return AvatarPlaceholderGenerator.generate(context, 128, publicKey, displayName); + return AvatarPlaceholderGenerator.generate(context, ICON_SIZE, publicKey, displayName); } /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 84d6d9aa03..32ddf827e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -18,6 +18,7 @@ import android.view.View import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -34,6 +35,8 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.canhub.cropper.CropImage +import com.canhub.cropper.CropImageContract import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import java.io.File @@ -111,6 +114,48 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { private val hexEncodedPublicKey: String get() = TextSecurePreferences.getLocalNumber(this)!! + private val onAvatarCropped = registerForActivityResult(CropImageContract()) { result -> + when { + result.isSuccessful -> { + Log.i(TAG, result.getUriFilePath(this).toString()) + + lifecycleScope.launch(Dispatchers.IO) { + try { + val profilePictureToBeUploaded = + BitmapUtil.createScaledBytes( + this@SettingsActivity, + result.getUriFilePath(this@SettingsActivity).toString(), + ProfileMediaConstraints() + ).bitmap + launch(Dispatchers.Main) { + updateProfilePicture(profilePictureToBeUploaded) + } + } catch (e: BitmapDecodingException) { + Log.e(TAG, e) + } + } + } + result is CropImage.CancelledResult -> { + Log.i(TAG, "Cropping image was cancelled by the user") + } + else -> { + Log.e(TAG, "Cropping image failed") + } + } + } + + private val onPickImage = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ){ result -> + if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult + + val outputFile = Uri.fromFile(File(cacheDir, "cropped")) + val inputFile: Uri? = result.data?.data ?: tempFile?.let(Uri::fromFile) + cropImage(inputFile, outputFile) + } + + private val avatarSelection = AvatarSelection(this, onAvatarCropped, onPickImage) + companion object { private const val SCROLL_STATE = "SCROLL_STATE" } @@ -186,32 +231,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } } - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (resultCode != Activity.RESULT_OK) return - when (requestCode) { - AvatarSelection.REQUEST_CODE_AVATAR -> { - val outputFile = Uri.fromFile(File(cacheDir, "cropped")) - val inputFile: Uri? = data?.data ?: tempFile?.let(Uri::fromFile) - AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.photo) - } - - AvatarSelection.REQUEST_CODE_CROP_IMAGE -> { - lifecycleScope.launch(Dispatchers.IO) { - try { - val profilePictureToBeUploaded = BitmapUtil.createScaledBytes(this@SettingsActivity, AvatarSelection.getResultUri(data), ProfileMediaConstraints()).bitmap - launch(Dispatchers.Main) { - updateProfilePicture(profilePictureToBeUploaded) - } - } catch (e: BitmapDecodingException) { - Log.e(TAG, e) - } - } - } - } - } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults) @@ -417,10 +436,17 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { Permissions.with(this) .request(Manifest.permission.CAMERA) .onAnyResult { - tempFile = AvatarSelection.startAvatarSelection(this, false, true) + tempFile = avatarSelection.startAvatarSelection( false, true) } .execute() } + + private fun cropImage(inputFile: Uri?, outputFile: Uri?){ + avatarSelection.circularCropImage( + inputFile = inputFile, + outputFile = outputFile, + ) + } // endregion private inner class DisplayNameEditActionModeCallback: ActionMode.Callback { diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java index 715ff322f7..67a7e0335d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -18,56 +18,46 @@ package org.thoughtcrime.securesms.video; import android.content.Context; import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; -import android.widget.MediaController; import android.widget.Toast; import android.widget.VideoView; -import com.google.android.exoplayer2.DefaultLoadControl; -import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.LoadControl; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; -import com.google.android.exoplayer2.extractor.ExtractorsFactory; -import com.google.android.exoplayer2.source.ExtractorMediaSource; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelector; -import com.google.android.exoplayer2.ui.PlayerControlView; -import com.google.android.exoplayer2.ui.PlayerView; -import com.google.android.exoplayer2.upstream.BandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.attachments.AttachmentServer; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; +import androidx.media3.common.AudioAttributes; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.ui.LegacyPlayerControlView; +import androidx.media3.ui.PlayerView; + + +import org.session.libsession.utilities.ViewUtil; import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.attachments.AttachmentServer; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.VideoSlide; -import org.session.libsession.utilities.ViewUtil; -import org.thoughtcrime.securesms.video.exo.AttachmentDataSourceFactory; import java.io.IOException; import network.loki.messenger.R; +@UnstableApi public class VideoPlayer extends FrameLayout { private static final String TAG = VideoPlayer.class.getSimpleName(); @Nullable private final VideoView videoView; - @Nullable private final PlayerView exoView; + @Nullable private final PlayerView exoView; - @Nullable private SimpleExoPlayer exoPlayer; - @Nullable private PlayerControlView exoControls; + @Nullable private ExoPlayer exoPlayer; + @Nullable private LegacyPlayerControlView exoControls; @Nullable private AttachmentServer attachmentServer; @Nullable private Window window; @@ -84,23 +74,16 @@ public class VideoPlayer extends FrameLayout { inflate(context, R.layout.video_player, this); - if (Build.VERSION.SDK_INT >= 16) { - this.exoView = ViewUtil.findById(this, R.id.video_view); - this.videoView = null; - this.exoControls = new PlayerControlView(getContext()); - this.exoControls.setShowTimeoutMs(-1); - } else { - this.videoView = ViewUtil.findById(this, R.id.video_view); - this.exoView = null; - initializeVideoViewControls(videoView); - } + this.exoView = ViewUtil.findById(this, R.id.video_view); + this.videoView = null; + this.exoControls = new LegacyPlayerControlView(getContext()); + this.exoControls.setShowTimeoutMs(-1); } public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay) throws IOException { - if (Build.VERSION.SDK_INT >= 16) setExoViewSource(videoSource, autoplay); - else setVideoViewSource(videoSource, autoplay); + setExoViewSource(videoSource, autoplay); } public void pause() { @@ -141,25 +124,20 @@ public class VideoPlayer extends FrameLayout { private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) throws IOException { - BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); - TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); - TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); - LoadControl loadControl = new DefaultLoadControl(); - - exoPlayer = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, loadControl); + exoPlayer = new ExoPlayer.Builder(getContext()).build(); exoPlayer.addListener(new ExoPlayerListener(window)); + exoPlayer.setAudioAttributes(AudioAttributes.DEFAULT, true); //noinspection ConstantConditions exoView.setPlayer(exoPlayer); //noinspection ConstantConditions exoControls.setPlayer(exoPlayer); - DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(getContext(), "GenericUserAgent", null); - AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(getContext(), defaultDataSourceFactory, null); - ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); + if(videoSource.getUri() != null){ + MediaItem mediaItem = MediaItem.fromUri(videoSource.getUri()); + exoPlayer.setMediaItem(mediaItem); + } - MediaSource mediaSource = new ExtractorMediaSource(videoSource.getUri(), attachmentDataSourceFactory, extractorsFactory, null, null); - - exoPlayer.prepare(mediaSource); + exoPlayer.prepare(); exoPlayer.setPlayWhenReady(autoplay); } @@ -189,15 +167,7 @@ public class VideoPlayer extends FrameLayout { if (autoplay) this.videoView.start(); } - private void initializeVideoViewControls(@NonNull VideoView videoView) { - MediaController mediaController = new MediaController(getContext()); - mediaController.setAnchorView(videoView); - mediaController.setMediaPlayer(videoView); - - videoView.setMediaController(mediaController); - } - - private static class ExoPlayerListener extends Player.DefaultEventListener { + private static class ExoPlayerListener implements Player.Listener { private final Window window; ExoPlayerListener(Window window) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java deleted file mode 100644 index 2989ff35c2..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.thoughtcrime.securesms.video.exo; - - -import android.net.Uri; - -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.DefaultDataSource; -import com.google.android.exoplayer2.upstream.TransferListener; - -import org.thoughtcrime.securesms.mms.PartAuthority; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class AttachmentDataSource implements DataSource { - - private final DefaultDataSource defaultDataSource; - private final PartDataSource partDataSource; - - private DataSource dataSource; - - public AttachmentDataSource(DefaultDataSource defaultDataSource, PartDataSource partDataSource) { - this.defaultDataSource = defaultDataSource; - this.partDataSource = partDataSource; - } - - @Override - public void addTransferListener(TransferListener transferListener) { - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - if (PartAuthority.isLocalUri(dataSpec.uri)) dataSource = partDataSource; - else dataSource = defaultDataSource; - - return dataSource.open(dataSpec); - } - - @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { - return dataSource.read(buffer, offset, readLength); - } - - @Override - public Uri getUri() { - return dataSource.getUri(); - } - - @Override - public Map> getResponseHeaders() { - return Collections.emptyMap(); - } - - @Override - public void close() throws IOException { - dataSource.close(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java deleted file mode 100644 index 99a6e28d9b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.thoughtcrime.securesms.video.exo; - - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.TransferListener; - -public class AttachmentDataSourceFactory implements DataSource.Factory { - - private final Context context; - - private final DefaultDataSourceFactory defaultDataSourceFactory; - private final TransferListener listener; - - public AttachmentDataSourceFactory(@NonNull Context context, - @NonNull DefaultDataSourceFactory defaultDataSourceFactory, - @Nullable TransferListener listener) - { - this.context = context; - this.defaultDataSourceFactory = defaultDataSourceFactory; - this.listener = listener; - } - - @Override - public AttachmentDataSource createDataSource() { - return new AttachmentDataSource(defaultDataSourceFactory.createDataSource(), - new PartDataSource(context, listener)); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java deleted file mode 100644 index 45e46cf054..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/PartDataSource.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.thoughtcrime.securesms.video.exo; - - -import android.content.Context; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.TransferListener; - -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.thoughtcrime.securesms.database.AttachmentDatabase; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import org.thoughtcrime.securesms.mms.PartUriParser; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class PartDataSource implements DataSource { - - private final @NonNull Context context; - private final @Nullable TransferListener listener; - - private Uri uri; - private InputStream inputSteam; - - PartDataSource(@NonNull Context context, @Nullable TransferListener listener) { - this.context = context.getApplicationContext(); - this.listener = listener; - } - - @Override - public void addTransferListener(TransferListener transferListener) { - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - this.uri = dataSpec.uri; - - AttachmentDatabase attachmentDatabase = DatabaseComponent.get(context).attachmentDatabase(); - PartUriParser partUri = new PartUriParser(uri); - Attachment attachment = attachmentDatabase.getAttachment(partUri.getPartId()); - - if (attachment == null) throw new IOException("Attachment not found"); - - this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position); - - if (listener != null) { - listener.onTransferStart(this, dataSpec, false); - } - - if (attachment.getSize() - dataSpec.position <= 0) throw new EOFException("No more data"); - - return attachment.getSize() - dataSpec.position; - } - - @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { - int read = inputSteam.read(buffer, offset, readLength); - - if (read > 0 && listener != null) { - listener.onBytesTransferred(this, null, false, read); - } - - return read; - } - - @Override - public Uri getUri() { - return uri; - } - - @Override - public Map> getResponseHeaders() { - return Collections.emptyMap(); - } - - @Override - public void close() throws IOException { - inputSteam.close(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt index 24ea2a390f..eda4c1a39b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt @@ -63,6 +63,7 @@ class PeerConnectionWrapper(private val context: Context, val configuration = PeerConnection.RTCConfiguration(iceServers).apply { bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE + sdpSemantics = PeerConnection.SdpSemantics.PLAN_B if (relay) { iceTransportsType = PeerConnection.IceTransportsType.RELAY } diff --git a/app/src/main/res/layout/media_preview_exoplayer_layout.xml b/app/src/main/res/layout/media_preview_exoplayer_layout.xml index d6b870c7c5..b904a6a4d1 100644 --- a/app/src/main/res/layout/media_preview_exoplayer_layout.xml +++ b/app/src/main/res/layout/media_preview_exoplayer_layout.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b336d5ae02..3bfd879d0d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -18,10 +18,6 @@ @dimen/very_large_font_size - -