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
-
-