mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-19 19:38:45 +00:00
Merge branch 'od' into on-2
This commit is contained in:
commit
30ee3946ef
@ -31,8 +31,8 @@ configurations.all {
|
||||
exclude module: "commons-logging"
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 373
|
||||
def canonicalVersionName = "1.18.4"
|
||||
def canonicalVersionCode = 374
|
||||
def canonicalVersionName = "1.18.5"
|
||||
|
||||
def postFixSize = 10
|
||||
def abiPostFix = ['armeabi-v7a' : 1,
|
||||
@ -271,7 +271,7 @@ 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 'org.conscrypt:conscrypt-android:2.0.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 "me.leolin:ShortcutBadger:1.1.16"
|
||||
|
@ -0,0 +1,87 @@
|
||||
package org.thoughtcrime.securesms.calls
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.provider.Settings
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity.SENSOR_SERVICE
|
||||
import org.thoughtcrime.securesms.webrtc.Orientation
|
||||
import kotlin.math.asin
|
||||
|
||||
class OrientationManager(private val context: Context): SensorEventListener {
|
||||
private var sensorManager: SensorManager? = null
|
||||
private var rotationVectorSensor: Sensor? = null
|
||||
|
||||
private val _orientation = MutableStateFlow(Orientation.UNKNOWN)
|
||||
val orientation: StateFlow<Orientation> = _orientation
|
||||
|
||||
fun startOrientationListener(){
|
||||
// create the sensor manager if it's still null
|
||||
if(sensorManager == null) {
|
||||
sensorManager = context.getSystemService(SENSOR_SERVICE) as SensorManager
|
||||
}
|
||||
|
||||
if(rotationVectorSensor == null) {
|
||||
rotationVectorSensor = sensorManager!!.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
|
||||
}
|
||||
|
||||
sensorManager?.registerListener(this, rotationVectorSensor, SensorManager.SENSOR_DELAY_UI)
|
||||
}
|
||||
|
||||
fun stopOrientationListener(){
|
||||
sensorManager?.unregisterListener(this)
|
||||
}
|
||||
|
||||
fun destroy(){
|
||||
stopOrientationListener()
|
||||
sensorManager = null
|
||||
rotationVectorSensor = null
|
||||
_orientation.value = Orientation.UNKNOWN
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
|
||||
// if auto-rotate is off, bail and send UNKNOWN
|
||||
if (!isAutoRotateOn()) {
|
||||
_orientation.value = Orientation.UNKNOWN
|
||||
return
|
||||
}
|
||||
|
||||
// Get the quaternion from the rotation vector sensor
|
||||
val quaternion = FloatArray(4)
|
||||
SensorManager.getQuaternionFromVector(quaternion, event.values)
|
||||
|
||||
// Calculate Euler angles from the quaternion
|
||||
val pitch = asin(2.0 * (quaternion[0] * quaternion[2] - quaternion[3] * quaternion[1]))
|
||||
|
||||
// Convert radians to degrees
|
||||
val pitchDegrees = Math.toDegrees(pitch).toFloat()
|
||||
|
||||
// Determine the device's orientation based on the pitch and roll values
|
||||
val currentOrientation = when {
|
||||
pitchDegrees > 45 -> Orientation.LANDSCAPE
|
||||
pitchDegrees < -45 -> Orientation.REVERSED_LANDSCAPE
|
||||
else -> Orientation.PORTRAIT
|
||||
}
|
||||
|
||||
if (currentOrientation != _orientation.value) {
|
||||
_orientation.value = currentOrientation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Function to check if Android System Auto-rotate is on or off
|
||||
private fun isAutoRotateOn(): Boolean {
|
||||
return Settings.System.getInt(
|
||||
context.contentResolver,
|
||||
Settings.System.ACCELEROMETER_ROTATION, 0
|
||||
) == 1
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
|
||||
}
|
@ -5,11 +5,17 @@ import android.content.BroadcastReceiver
|
||||
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.OrientationEventListener
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -21,7 +27,6 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import android.provider.Settings
|
||||
import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivityWebrtcBinding
|
||||
@ -43,8 +48,10 @@ import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_OUTGOING
|
||||
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_PRE_INIT
|
||||
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_RECONNECTING
|
||||
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() {
|
||||
@ -71,16 +78,13 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
}
|
||||
private var hangupReceiver: BroadcastReceiver? = null
|
||||
|
||||
private val rotationListener by lazy {
|
||||
object : OrientationEventListener(this) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
if ((orientation + 15) % 90 < 30) {
|
||||
viewModel.deviceRotation = orientation
|
||||
// updateControlsRotation(orientation.quadrantRotation() * -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* We need to track the device's orientation so we can calculate whether or not to rotate the video streams
|
||||
* This works a lot better than using `OrientationEventListener > onOrientationChanged'
|
||||
* which gives us a rotation angle that doesn't take into account pitch vs roll, so tipping the device from front to back would
|
||||
* trigger the video rotation logic, while we really only want it when the device is in portrait or landscape.
|
||||
*/
|
||||
private var orientationManager = OrientationManager(this)
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
@ -102,13 +106,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
|
||||
// Only enable auto-rotate if system auto-rotate is enabled
|
||||
if (isAutoRotateOn()) {
|
||||
rotationListener.enable()
|
||||
} else {
|
||||
rotationListener.disable()
|
||||
}
|
||||
|
||||
binding = ActivityWebrtcBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
@ -136,6 +133,10 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||
}
|
||||
|
||||
binding.floatingRendererContainer.setOnClickListener {
|
||||
viewModel.swapVideos()
|
||||
}
|
||||
|
||||
binding.microphoneButton.setOnClickListener {
|
||||
val audioEnabledIntent =
|
||||
WebRtcCallService.microphoneIntent(this, !viewModel.microphoneEnabled)
|
||||
@ -174,7 +175,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.onAllGranted {
|
||||
val intent = WebRtcCallService.cameraEnabled(this, !viewModel.videoEnabled)
|
||||
val intent = WebRtcCallService.cameraEnabled(this, !viewModel.videoState.value.userVideoEnabled)
|
||||
startService(intent)
|
||||
}
|
||||
.execute()
|
||||
@ -191,14 +192,44 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
onBackPressed()
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
orientationManager.orientation.collect { orientation ->
|
||||
viewModel.deviceOrientation = orientation
|
||||
updateControlsRotation()
|
||||
}
|
||||
}
|
||||
|
||||
clipFloatingInsets()
|
||||
}
|
||||
|
||||
//Function to check if Android System Auto-rotate is on or off
|
||||
private fun isAutoRotateOn(): Boolean {
|
||||
return Settings.System.getInt(
|
||||
contentResolver,
|
||||
Settings.System.ACCELEROMETER_ROTATION, 0
|
||||
) == 1
|
||||
/**
|
||||
* Makes sure the floating video inset has clipped rounded corners, included with the video stream itself
|
||||
*/
|
||||
private fun clipFloatingInsets() {
|
||||
// clip the video inset with rounded corners
|
||||
val videoInsetProvider = object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View, outline: Outline) {
|
||||
// all corners
|
||||
outline.setRoundRect(
|
||||
0, 0, view.width, view.height,
|
||||
resources.getDimensionPixelSize(R.dimen.video_inset_radius).toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
binding.floatingRendererContainer.outlineProvider = videoInsetProvider
|
||||
binding.floatingRendererContainer.clipToOutline = true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
orientationManager.startOrientationListener()
|
||||
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
orientationManager.stopOrientationListener()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@ -206,7 +237,8 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
hangupReceiver?.let { receiver ->
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||
}
|
||||
rotationListener.disable()
|
||||
|
||||
orientationManager.destroy()
|
||||
}
|
||||
|
||||
private fun answerCall() {
|
||||
@ -214,15 +246,31 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
ContextCompat.startForegroundService(this, answerIntent)
|
||||
}
|
||||
|
||||
private fun updateControlsRotation(newRotation: Int) {
|
||||
private fun updateControlsRotation() {
|
||||
with (binding) {
|
||||
val rotation = newRotation.toFloat()
|
||||
remoteRecipient.rotation = rotation
|
||||
speakerPhoneButton.rotation = rotation
|
||||
microphoneButton.rotation = rotation
|
||||
enableCameraButton.rotation = rotation
|
||||
switchCameraButton.rotation = rotation
|
||||
endCallButton.rotation = rotation
|
||||
val rotation = when(viewModel.deviceOrientation){
|
||||
Orientation.LANDSCAPE -> -90f
|
||||
Orientation.REVERSED_LANDSCAPE -> 90f
|
||||
else -> 0f
|
||||
}
|
||||
|
||||
remoteRecipient.animate().cancel()
|
||||
remoteRecipient.animate().rotation(rotation).start()
|
||||
|
||||
speakerPhoneButton.animate().cancel()
|
||||
speakerPhoneButton.animate().rotation(rotation).start()
|
||||
|
||||
microphoneButton.animate().cancel()
|
||||
microphoneButton.animate().rotation(rotation).start()
|
||||
|
||||
enableCameraButton.animate().cancel()
|
||||
enableCameraButton.animate().rotation(rotation).start()
|
||||
|
||||
switchCameraButton.animate().cancel()
|
||||
switchCameraButton.animate().rotation(rotation).start()
|
||||
|
||||
endCallButton.animate().cancel()
|
||||
endCallButton.animate().rotation(rotation).start()
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,34 +394,43 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// handle video state
|
||||
launch {
|
||||
viewModel.localVideoEnabledState.collect { isEnabled ->
|
||||
binding.localRenderer.removeAllViews()
|
||||
if (isEnabled) {
|
||||
viewModel.localRenderer?.let { surfaceView ->
|
||||
surfaceView.setZOrderOnTop(true)
|
||||
viewModel.videoState.collect { state ->
|
||||
binding.floatingRenderer.removeAllViews()
|
||||
binding.fullscreenRenderer.removeAllViews()
|
||||
|
||||
// Mirror the video preview of the person making the call to prevent disorienting them
|
||||
surfaceView.setMirror(true)
|
||||
// the floating video inset (empty or not) should be shown
|
||||
// the moment we have either of the video streams
|
||||
val showFloatingContainer = state.userVideoEnabled || state.remoteVideoEnabled
|
||||
binding.floatingRendererContainer.isVisible = showFloatingContainer
|
||||
binding.swapViewIcon.isVisible = showFloatingContainer
|
||||
|
||||
binding.localRenderer.addView(surfaceView)
|
||||
// handle fullscreen video window
|
||||
if(state.showFullscreenVideo()){
|
||||
viewModel.fullscreenRenderer?.let { surfaceView ->
|
||||
binding.fullscreenRenderer.addView(surfaceView)
|
||||
binding.fullscreenRenderer.isVisible = true
|
||||
binding.remoteRecipient.isVisible = false
|
||||
}
|
||||
} else {
|
||||
binding.fullscreenRenderer.isVisible = false
|
||||
binding.remoteRecipient.isVisible = true
|
||||
}
|
||||
binding.localRenderer.isVisible = isEnabled
|
||||
binding.enableCameraButton.isSelected = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.remoteVideoEnabledState.collect { isEnabled ->
|
||||
binding.remoteRenderer.removeAllViews()
|
||||
if (isEnabled) {
|
||||
viewModel.remoteRenderer?.let { surfaceView ->
|
||||
binding.remoteRenderer.addView(surfaceView)
|
||||
// handle floating video window
|
||||
if(state.showFloatingVideo()){
|
||||
viewModel.floatingRenderer?.let { surfaceView ->
|
||||
binding.floatingRenderer.addView(surfaceView)
|
||||
binding.floatingRenderer.isVisible = true
|
||||
binding.swapViewIcon.bringToFront()
|
||||
}
|
||||
} else {
|
||||
binding.floatingRenderer.isVisible = false
|
||||
}
|
||||
binding.remoteRenderer.isVisible = isEnabled
|
||||
binding.remoteRecipient.isVisible = !isEnabled
|
||||
|
||||
// handle buttons
|
||||
binding.enableCameraButton.isSelected = state.userVideoEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -388,7 +445,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
uiJob?.cancel()
|
||||
binding.remoteRenderer.removeAllViews()
|
||||
binding.localRenderer.removeAllViews()
|
||||
binding.fullscreenRenderer.removeAllViews()
|
||||
binding.floatingRenderer.removeAllViews()
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import androidx.core.view.isVisible
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewVisibleMessageContentBinding
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
@ -289,7 +290,7 @@ class VisibleMessageContentView : ConstraintLayout {
|
||||
|
||||
// replace URLSpans with ModalURLSpans
|
||||
body.getSpans<URLSpan>(0, body.length).toList().forEach { urlSpan ->
|
||||
val updatedUrl = urlSpan.url.let { HttpUrl.parse(it).toString() }
|
||||
val updatedUrl = urlSpan.url.let { it.toHttpUrlOrNull().toString() }
|
||||
val replacementSpan = ModalURLSpan(updatedUrl) { url ->
|
||||
val activity = context as AppCompatActivity
|
||||
ModalUrlBottomSheet(url).show(activity.supportFragmentManager, "Open URL Dialog")
|
||||
|
@ -18,6 +18,7 @@ import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
import org.session.libsession.utilities.getColorFromAttr
|
||||
import org.session.libsession.utilities.truncateIdForDisplay
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.util.RoundedBackgroundSpan
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
@ -67,7 +68,7 @@ object MentionUtilities {
|
||||
} else {
|
||||
val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithAccountID(publicKey)
|
||||
@Suppress("NAME_SHADOWING") val context = if (openGroup != null) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR
|
||||
contact?.displayName(context)
|
||||
contact?.displayName(context) ?: truncateIdForDisplay(publicKey)
|
||||
}
|
||||
if (userDisplayName != null) {
|
||||
val mention = "@$userDisplayName"
|
||||
|
@ -55,7 +55,7 @@ public class RecipientDatabase extends Database {
|
||||
private static final String SYSTEM_PHONE_LABEL = "system_phone_label";
|
||||
private static final String SYSTEM_CONTACT_URI = "system_contact_uri";
|
||||
private static final String SIGNAL_PROFILE_NAME = "signal_profile_name";
|
||||
private static final String SIGNAL_PROFILE_AVATAR = "signal_profile_avatar";
|
||||
private static final String SESSION_PROFILE_AVATAR = "signal_profile_avatar";
|
||||
private static final String PROFILE_SHARING = "profile_sharing_approval";
|
||||
private static final String CALL_RINGTONE = "call_ringtone";
|
||||
private static final String CALL_VIBRATE = "call_vibrate";
|
||||
@ -69,7 +69,7 @@ public class RecipientDatabase extends Database {
|
||||
private static final String[] RECIPIENT_PROJECTION = new String[] {
|
||||
BLOCK, APPROVED, APPROVED_ME, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED,
|
||||
PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI,
|
||||
SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
|
||||
SIGNAL_PROFILE_NAME, SESSION_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
|
||||
UNIDENTIFIED_ACCESS_MODE,
|
||||
FORCE_SMS_SELECTION, NOTIFY_TYPE, DISAPPEARING_STATE, WRAPPER_HASH, BLOCKS_COMMUNITY_MESSAGE_REQUESTS
|
||||
};
|
||||
@ -97,7 +97,7 @@ public class RecipientDatabase extends Database {
|
||||
SYSTEM_CONTACT_URI + " TEXT DEFAULT NULL, " +
|
||||
PROFILE_KEY + " TEXT DEFAULT NULL, " +
|
||||
SIGNAL_PROFILE_NAME + " TEXT DEFAULT NULL, " +
|
||||
SIGNAL_PROFILE_AVATAR + " TEXT DEFAULT NULL, " +
|
||||
SESSION_PROFILE_AVATAR + " TEXT DEFAULT NULL, " +
|
||||
PROFILE_SHARING + " INTEGER DEFAULT 0, " +
|
||||
CALL_RINGTONE + " TEXT DEFAULT NULL, " +
|
||||
CALL_VIBRATE + " INTEGER DEFAULT " + Recipient.VibrateState.DEFAULT.getId() + ", " +
|
||||
@ -204,7 +204,7 @@ public class RecipientDatabase extends Database {
|
||||
String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL));
|
||||
String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI));
|
||||
String signalProfileName = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_NAME));
|
||||
String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR));
|
||||
String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SESSION_PROFILE_AVATAR));
|
||||
boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1;
|
||||
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL));
|
||||
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
|
||||
@ -361,7 +361,7 @@ public class RecipientDatabase extends Database {
|
||||
|
||||
public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar);
|
||||
contentValues.put(SESSION_PROFILE_AVATAR, profileAvatar);
|
||||
updateOrInsert(recipient.getAddress(), contentValues);
|
||||
recipient.resolve().setProfileAvatar(profileAvatar);
|
||||
notifyRecipientListeners();
|
||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import java.security.MessageDigest
|
||||
import network.loki.messenger.libsession_util.ConfigBase
|
||||
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN
|
||||
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED
|
||||
@ -10,6 +11,7 @@ import network.loki.messenger.libsession_util.ConversationVolatileConfig
|
||||
import network.loki.messenger.libsession_util.UserGroupsConfig
|
||||
import network.loki.messenger.libsession_util.UserProfile
|
||||
import network.loki.messenger.libsession_util.util.BaseCommunityInfo
|
||||
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
|
||||
import network.loki.messenger.libsession_util.util.Conversation
|
||||
import network.loki.messenger.libsession_util.util.ExpiryMode
|
||||
import network.loki.messenger.libsession_util.util.GroupInfo
|
||||
@ -92,8 +94,6 @@ import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.util.SessionMetaProtocol
|
||||
import java.security.MessageDigest
|
||||
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
|
||||
|
||||
private const val TAG = "Storage"
|
||||
|
||||
@ -472,7 +472,8 @@ open class Storage(
|
||||
val userPublicKey = getUserPublicKey() ?: return
|
||||
// would love to get rid of recipient and context from this
|
||||
val recipient = Recipient.from(context, fromSerialized(userPublicKey), false)
|
||||
// update name
|
||||
|
||||
// Update profile name
|
||||
val name = userProfile.getName() ?: return
|
||||
val userPic = userProfile.getPic()
|
||||
val profileManager = SSKEnvironment.shared.profileManager
|
||||
@ -483,13 +484,14 @@ open class Storage(
|
||||
if (it != name) userProfile.setName(it)
|
||||
}
|
||||
|
||||
// update pfp
|
||||
// Update profile picture
|
||||
if (userPic == UserPic.DEFAULT) {
|
||||
clearUserPic()
|
||||
} else if (userPic.key.isNotEmpty() && userPic.url.isNotEmpty()
|
||||
&& TextSecurePreferences.getProfilePictureURL(context) != userPic.url) {
|
||||
setUserProfilePicture(userPic.url, userPic.key)
|
||||
}
|
||||
|
||||
if (userProfile.getNtsPriority() == PRIORITY_HIDDEN) {
|
||||
// delete nts thread if needed
|
||||
val ourThread = getThreadId(recipient) ?: return
|
||||
@ -517,12 +519,13 @@ open class Storage(
|
||||
addLibSessionContacts(extracted, messageTimestamp)
|
||||
}
|
||||
|
||||
override fun clearUserPic() {
|
||||
val userPublicKey = getUserPublicKey() ?: return
|
||||
override fun clearUserPic() {
|
||||
val userPublicKey = getUserPublicKey() ?: return Log.w(TAG, "No user public key when trying to clear user pic")
|
||||
val recipientDatabase = DatabaseComponent.get(context).recipientDatabase()
|
||||
// would love to get rid of recipient and context from this
|
||||
|
||||
val recipient = Recipient.from(context, fromSerialized(userPublicKey), false)
|
||||
// clear picture if userPic is null
|
||||
|
||||
// Clear details related to the user's profile picture
|
||||
TextSecurePreferences.setProfileKey(context, null)
|
||||
ProfileKeyUtil.setEncodedProfileKey(context, null)
|
||||
recipientDatabase.setProfileAvatar(recipient, null)
|
||||
@ -531,7 +534,6 @@ open class Storage(
|
||||
|
||||
Recipient.removeCached(fromSerialized(userPublicKey))
|
||||
configFactory.user?.setPic(UserPic.DEFAULT)
|
||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
|
||||
}
|
||||
|
||||
private fun updateConvoVolatile(convos: ConversationVolatileConfig, messageTimestamp: Long) {
|
||||
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.groups
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.open_groups.GroupMemberRole
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
@ -143,9 +144,9 @@ object OpenGroupManager {
|
||||
|
||||
@WorkerThread
|
||||
fun addOpenGroup(urlAsString: String, context: Context): OpenGroupApi.RoomInfo? {
|
||||
val url = HttpUrl.parse(urlAsString) ?: return null
|
||||
val url = urlAsString.toHttpUrlOrNull() ?: return null
|
||||
val server = OpenGroup.getServer(urlAsString)
|
||||
val room = url.pathSegments().firstOrNull() ?: return null
|
||||
val room = url.pathSegments.firstOrNull() ?: return null
|
||||
val publicKey = url.queryParameter("public_key") ?: return null
|
||||
|
||||
return add(server.toString().removeSuffix("/"), room, publicKey, context).second // assume migrated from calling function
|
||||
|
@ -35,6 +35,5 @@ public class AndroidLogger extends Log.Logger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockUntilAllWritesFinished() {
|
||||
}
|
||||
public void blockUntilAllWritesFinished() { }
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import kotlinx.serialization.json.decodeFromStream
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.Response
|
||||
@ -99,7 +100,7 @@ class PushRegistryV2 @Inject constructor(private val pushReceiver: PushReceiver)
|
||||
private inline fun <reified T: Response> getResponseBody(path: String, requestParameters: String): Promise<T, Exception> {
|
||||
val server = Server.LATEST
|
||||
val url = "${server.url}/$path"
|
||||
val body = RequestBody.create(MediaType.get("application/json"), requestParameters)
|
||||
val body = RequestBody.create("application/json".toMediaType(), requestParameters)
|
||||
val request = Request.Builder().url(url).post(body).build()
|
||||
|
||||
return OnionRequestAPI.sendOnionRequest(
|
||||
|
@ -35,6 +35,9 @@ import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.io.File
|
||||
import java.security.SecureRandom
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -47,8 +50,8 @@ import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ActivitySettingsBinding
|
||||
import network.loki.messenger.libsession_util.util.UserPic
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.all
|
||||
import nl.komponents.kovenant.ui.alwaysUi
|
||||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.session.libsession.avatars.AvatarHelper
|
||||
import org.session.libsession.avatars.ProfileContactPhoto
|
||||
@ -89,14 +92,10 @@ import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.util.push
|
||||
import org.thoughtcrime.securesms.util.show
|
||||
import java.io.File
|
||||
import java.security.SecureRandom
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "SettingsActivity"
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
private val TAG = "SettingsActivity"
|
||||
|
||||
@Inject
|
||||
lateinit var configFactory: ConfigFactory
|
||||
@ -253,39 +252,77 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
displayName: String? = null
|
||||
) {
|
||||
binding.loader.isVisible = true
|
||||
val promises = mutableListOf<Promise<*, Exception>>()
|
||||
|
||||
if (displayName != null) {
|
||||
TextSecurePreferences.setProfileName(this, displayName)
|
||||
configFactory.user?.setName(displayName)
|
||||
}
|
||||
|
||||
// Bail if we're not updating the profile picture in any way
|
||||
if (!isUpdatingProfilePicture) return
|
||||
|
||||
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
|
||||
if (isUpdatingProfilePicture) {
|
||||
if (profilePicture != null) {
|
||||
promises.add(ProfilePictureUtilities.upload(profilePicture, encodedProfileKey, this))
|
||||
} else {
|
||||
|
||||
val uploadProfilePicturePromise: Promise<*, Exception>
|
||||
var removingProfilePic = false
|
||||
|
||||
// Adding a new profile picture?
|
||||
if (profilePicture != null) {
|
||||
uploadProfilePicturePromise = ProfilePictureUtilities.upload(profilePicture, encodedProfileKey, this)
|
||||
} else {
|
||||
// If not then we must be removing the existing one.
|
||||
// Note: To get a promise that will resolve / sync correctly we overwrite the existing profile picture with
|
||||
// a 0 byte image.
|
||||
removingProfilePic = true
|
||||
val emptyByteArray = ByteArray(0)
|
||||
uploadProfilePicturePromise = ProfilePictureUtilities.upload(emptyByteArray, encodedProfileKey, this)
|
||||
}
|
||||
|
||||
// If the upload picture promise succeeded then we hit this successUi block
|
||||
uploadProfilePicturePromise.successUi {
|
||||
|
||||
// If we successfully removed the profile picture on the network then we can clear the
|
||||
// local data - otherwise it's weird to fail the online section but it _looks_ like it
|
||||
// worked because we cleared the local image (also it denies them the chance to retry
|
||||
// removal if we do it locally, and may result in them having a visible profile picture
|
||||
// everywhere EXCEPT on their own device!).
|
||||
if (removingProfilePic) {
|
||||
MessagingModuleConfiguration.shared.storage.clearUserPic()
|
||||
}
|
||||
}
|
||||
all(promises) successUi { // Do this on the UI thread so that it happens before the alwaysUi clause below
|
||||
|
||||
val userConfig = configFactory.user
|
||||
if (isUpdatingProfilePicture) {
|
||||
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
|
||||
prefs.setProfileAvatarId(profilePicture?.let { SecureRandom().nextInt() } ?: 0 )
|
||||
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
|
||||
prefs.setProfileAvatarId(profilePicture?.let { SecureRandom().nextInt() } ?: 0 )
|
||||
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
|
||||
// new config
|
||||
val url = TextSecurePreferences.getProfilePictureURL(this)
|
||||
val profileKey = ProfileKeyUtil.getProfileKey(this)
|
||||
if (profilePicture == null) {
|
||||
userConfig?.setPic(UserPic.DEFAULT)
|
||||
} else if (!url.isNullOrEmpty() && profileKey.isNotEmpty()) {
|
||||
userConfig?.setPic(UserPic(url, profileKey))
|
||||
}
|
||||
|
||||
// If we have a URL and a profile key then set the user's profile picture
|
||||
if (!url.isNullOrEmpty() && profileKey.isNotEmpty()) {
|
||||
userConfig?.setPic(UserPic(url, profileKey))
|
||||
}
|
||||
|
||||
if (userConfig != null && userConfig.needsDump()) {
|
||||
configFactory.persist(userConfig, SnodeAPI.nowWithOffset)
|
||||
}
|
||||
|
||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
|
||||
} alwaysUi {
|
||||
}
|
||||
|
||||
// Or if the promise failed to upload the new profile picture then we hit this failUi block
|
||||
uploadProfilePicturePromise.failUi {
|
||||
if (removingProfilePic) {
|
||||
Log.e(TAG, "Failed to remove profile picture")
|
||||
Toast.makeText(this@SettingsActivity, R.string.profileDisplayPictureRemoveError, Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
Log.e(TAG, "Failed to upload profile picture")
|
||||
Toast.makeText(this@SettingsActivity, R.string.profileErrorUpdate, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, regardless of whether the promise succeeded or failed, we always hit this `alwaysUi` block
|
||||
uploadProfilePicturePromise.alwaysUi {
|
||||
if (displayName != null) {
|
||||
binding.btnGroupNameDisplay.text = displayName
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
|
||||
const val EXTRA_RECIPIENT_ADDRESS = "RECIPIENT_ID"
|
||||
const val EXTRA_ENABLED = "ENABLED"
|
||||
const val EXTRA_AUDIO_COMMAND = "AUDIO_COMMAND"
|
||||
const val EXTRA_SWAPPED = "is_video_swapped"
|
||||
const val EXTRA_MUTE = "mute_value"
|
||||
const val EXTRA_AVAILABLE = "enabled_value"
|
||||
const val EXTRA_REMOTE_DESCRIPTION = "remote_description"
|
||||
|
@ -24,6 +24,7 @@ import org.session.libsession.utilities.WindowDebouncer
|
||||
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
@ -31,10 +32,12 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import java.util.Timer
|
||||
|
||||
object ConfigurationMessageUtilities {
|
||||
private const val TAG = "ConfigMessageUtils"
|
||||
|
||||
private val debouncer = WindowDebouncer(3000, Timer())
|
||||
|
||||
private fun scheduleConfigSync(userPublicKey: String) {
|
||||
|
||||
debouncer.publish {
|
||||
// don't schedule job if we already have one
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
@ -44,23 +47,20 @@ object ConfigurationMessageUtilities {
|
||||
(currentStorageJob as ConfigurationSyncJob).shouldRunAgain.set(true)
|
||||
return@publish
|
||||
}
|
||||
val newConfigSync = ConfigurationSyncJob(ourDestination)
|
||||
JobQueue.shared.add(newConfigSync)
|
||||
val newConfigSyncJob = ConfigurationSyncJob(ourDestination)
|
||||
JobQueue.shared.add(newConfigSyncJob)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun syncConfigurationIfNeeded(context: Context) {
|
||||
// add if check here to schedule new config job process and return early
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Log.w(TAG, "User Public Key is null")
|
||||
scheduleConfigSync(userPublicKey)
|
||||
}
|
||||
|
||||
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
|
||||
// add if check here to schedule new config job process and return early
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofFail(NullPointerException("User Public Key is null"))
|
||||
// schedule job if none exist
|
||||
// don't schedule job if we already have one
|
||||
// Schedule a new job if one doesn't already exist (only)
|
||||
scheduleConfigSync(userPublicKey)
|
||||
return Promise.ofSuccess(Unit)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
@ -50,6 +49,17 @@ class RoundedBackgroundSpan(
|
||||
override fun getSize(
|
||||
paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?
|
||||
): Int {
|
||||
// If the span covers the whole text, and the height is not set, draw() will not be called for the span.
|
||||
// To help with that we need to take the font metric into account
|
||||
val metrics = paint.fontMetricsInt
|
||||
if (fm != null) {
|
||||
fm.top = metrics.top
|
||||
fm.ascent = metrics.ascent
|
||||
fm.descent = metrics.descent
|
||||
|
||||
fm.bottom = metrics.bottom
|
||||
}
|
||||
|
||||
return (paint.measureText(text, start, end) + 2 * paddingHorizontal).toInt()
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,14 @@ import android.telephony.TelephonyManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.boolean
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
@ -51,6 +54,7 @@ import org.webrtc.MediaStream
|
||||
import org.webrtc.PeerConnection
|
||||
import org.webrtc.PeerConnection.IceConnectionState
|
||||
import org.webrtc.PeerConnectionFactory
|
||||
import org.webrtc.RendererCommon
|
||||
import org.webrtc.RtpReceiver
|
||||
import org.webrtc.SessionDescription
|
||||
import org.webrtc.SurfaceViewRenderer
|
||||
@ -105,10 +109,15 @@ class CallManager(
|
||||
|
||||
private val _audioEvents = MutableStateFlow(AudioEnabled(false))
|
||||
val audioEvents = _audioEvents.asSharedFlow()
|
||||
private val _videoEvents = MutableStateFlow(VideoEnabled(false))
|
||||
val videoEvents = _videoEvents.asSharedFlow()
|
||||
private val _remoteVideoEvents = MutableStateFlow(VideoEnabled(false))
|
||||
val remoteVideoEvents = _remoteVideoEvents.asSharedFlow()
|
||||
|
||||
private val _videoState: MutableStateFlow<VideoState> = MutableStateFlow(
|
||||
VideoState(
|
||||
swapped = false,
|
||||
userVideoEnabled = false,
|
||||
remoteVideoEnabled = false
|
||||
)
|
||||
)
|
||||
val videoState = _videoState
|
||||
|
||||
private val stateProcessor = StateProcessor(CallState.Idle)
|
||||
|
||||
@ -151,9 +160,9 @@ class CallManager(
|
||||
|
||||
private val outgoingIceDebouncer = Debouncer(200L)
|
||||
|
||||
var localRenderer: SurfaceViewRenderer? = null
|
||||
var floatingRenderer: SurfaceViewRenderer? = null
|
||||
var remoteRotationSink: RemoteRotationVideoProxySink? = null
|
||||
var remoteRenderer: SurfaceViewRenderer? = null
|
||||
var fullscreenRenderer: SurfaceViewRenderer? = null
|
||||
private var peerConnectionFactory: PeerConnectionFactory? = null
|
||||
|
||||
fun clearPendingIceUpdates() {
|
||||
@ -216,20 +225,18 @@ class CallManager(
|
||||
Util.runOnMainSync {
|
||||
val base = EglBase.create()
|
||||
eglBase = base
|
||||
localRenderer = SurfaceViewRenderer(context).apply {
|
||||
// setScalingType(SCALE_ASPECT_FIT)
|
||||
}
|
||||
floatingRenderer = SurfaceViewRenderer(context)
|
||||
floatingRenderer?.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
|
||||
|
||||
fullscreenRenderer = SurfaceViewRenderer(context)
|
||||
fullscreenRenderer?.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
|
||||
|
||||
remoteRenderer = SurfaceViewRenderer(context).apply {
|
||||
// setScalingType(SCALE_ASPECT_FIT)
|
||||
}
|
||||
remoteRotationSink = RemoteRotationVideoProxySink()
|
||||
|
||||
|
||||
localRenderer?.init(base.eglBaseContext, null)
|
||||
localRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT)
|
||||
remoteRenderer?.init(base.eglBaseContext, null)
|
||||
remoteRotationSink!!.setSink(remoteRenderer!!)
|
||||
floatingRenderer?.init(base.eglBaseContext, null)
|
||||
fullscreenRenderer?.init(base.eglBaseContext, null)
|
||||
remoteRotationSink!!.setSink(fullscreenRenderer!!)
|
||||
|
||||
val encoderFactory = DefaultVideoEncoderFactory(base.eglBaseContext, true, true)
|
||||
val decoderFactory = DefaultVideoDecoderFactory(base.eglBaseContext)
|
||||
@ -363,7 +370,8 @@ class CallManager(
|
||||
val byteArray = ByteArray(buffer.data.remaining()) { buffer.data[it] }
|
||||
val json = Json.parseToJsonElement(byteArray.decodeToString()) as JsonObject
|
||||
if (json.containsKey("video")) {
|
||||
_remoteVideoEvents.value = VideoEnabled((json["video"] as JsonPrimitive).boolean)
|
||||
_videoState.update { it.copy(remoteVideoEnabled = json["video"]?.jsonPrimitive?.boolean ?: false) }
|
||||
handleMirroring()
|
||||
} else if (json.containsKey("hangup")) {
|
||||
peerConnectionObservers.forEach(WebRtcListener::onHangup)
|
||||
}
|
||||
@ -383,13 +391,13 @@ class CallManager(
|
||||
peerConnection?.dispose()
|
||||
peerConnection = null
|
||||
|
||||
localRenderer?.release()
|
||||
floatingRenderer?.release()
|
||||
remoteRotationSink?.release()
|
||||
remoteRenderer?.release()
|
||||
fullscreenRenderer?.release()
|
||||
eglBase?.release()
|
||||
|
||||
localRenderer = null
|
||||
remoteRenderer = null
|
||||
floatingRenderer = null
|
||||
fullscreenRenderer = null
|
||||
eglBase = null
|
||||
|
||||
localCameraState = CameraState.UNKNOWN
|
||||
@ -399,8 +407,11 @@ class CallManager(
|
||||
pendingOffer = null
|
||||
callStartTime = -1
|
||||
_audioEvents.value = AudioEnabled(false)
|
||||
_videoEvents.value = VideoEnabled(false)
|
||||
_remoteVideoEvents.value = VideoEnabled(false)
|
||||
_videoState.value = VideoState(
|
||||
swapped = false,
|
||||
userVideoEnabled = false,
|
||||
remoteVideoEnabled = false
|
||||
)
|
||||
pendingOutgoingIceUpdates.clear()
|
||||
pendingIncomingIceUpdates.clear()
|
||||
}
|
||||
@ -411,7 +422,7 @@ class CallManager(
|
||||
|
||||
// If the camera we've switched to is the front one then mirror it to match what someone
|
||||
// would see when looking in the mirror rather than the left<-->right flipped version.
|
||||
localRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT)
|
||||
handleMirroring()
|
||||
}
|
||||
|
||||
fun onPreOffer(callId: UUID, recipient: Recipient, onSuccess: () -> Unit) {
|
||||
@ -469,7 +480,7 @@ class CallManager(
|
||||
val recipient = recipient ?: return Promise.ofFail(NullPointerException("recipient is null"))
|
||||
val offer = pendingOffer ?: return Promise.ofFail(NullPointerException("pendingOffer is null"))
|
||||
val factory = peerConnectionFactory ?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null"))
|
||||
val local = localRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null"))
|
||||
val local = floatingRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null"))
|
||||
val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null"))
|
||||
val connection = PeerConnectionWrapper(
|
||||
context,
|
||||
@ -515,7 +526,7 @@ class CallManager(
|
||||
?: return Promise.ofFail(NullPointerException("recipient is null"))
|
||||
val factory = peerConnectionFactory
|
||||
?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null"))
|
||||
val local = localRenderer
|
||||
val local = floatingRenderer
|
||||
?: return Promise.ofFail(NullPointerException("localRenderer is null"))
|
||||
val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null"))
|
||||
|
||||
@ -609,13 +620,58 @@ class CallManager(
|
||||
}
|
||||
}
|
||||
|
||||
fun swapVideos() {
|
||||
// update the state
|
||||
_videoState.update { it.copy(swapped = !it.swapped) }
|
||||
handleMirroring()
|
||||
|
||||
if (_videoState.value.swapped) {
|
||||
peerConnection?.rotationVideoSink?.setSink(fullscreenRenderer)
|
||||
floatingRenderer?.let{remoteRotationSink?.setSink(it) }
|
||||
} else {
|
||||
peerConnection?.rotationVideoSink?.apply {
|
||||
setSink(floatingRenderer)
|
||||
}
|
||||
fullscreenRenderer?.let{ remoteRotationSink?.setSink(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun handleSetMuteAudio(muted: Boolean) {
|
||||
_audioEvents.value = AudioEnabled(!muted)
|
||||
peerConnection?.setAudioEnabled(!muted)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the renderer currently showing the user's video, not the contact's
|
||||
*/
|
||||
private fun getUserRenderer() = if(_videoState.value.swapped) fullscreenRenderer else floatingRenderer
|
||||
|
||||
/**
|
||||
* Returns the renderer currently showing the contact's video, not the user's
|
||||
*/
|
||||
private fun getRemoteRenderer() = if(_videoState.value.swapped) floatingRenderer else fullscreenRenderer
|
||||
|
||||
/**
|
||||
* Makes sure the user's renderer applies mirroring if necessary
|
||||
*/
|
||||
private fun handleMirroring() {
|
||||
val videoState = _videoState.value
|
||||
|
||||
// if we have user video and the camera is front facing, make sure to mirror stream
|
||||
if(videoState.userVideoEnabled) {
|
||||
getUserRenderer()?.setMirror(isCameraFrontFacing())
|
||||
}
|
||||
|
||||
// the remote video is never mirrored
|
||||
if(videoState.remoteVideoEnabled){
|
||||
getRemoteRenderer()?.setMirror(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleSetMuteVideo(muted: Boolean, lockManager: LockManager) {
|
||||
_videoEvents.value = VideoEnabled(!muted)
|
||||
_videoState.update { it.copy(userVideoEnabled = !muted) }
|
||||
handleMirroring()
|
||||
|
||||
val connection = peerConnection ?: return
|
||||
connection.setVideoEnabled(!muted)
|
||||
dataChannel?.let { channel ->
|
||||
@ -651,9 +707,18 @@ class CallManager(
|
||||
}
|
||||
}
|
||||
|
||||
fun setDeviceRotation(newRotation: Int) {
|
||||
peerConnection?.setDeviceRotation(newRotation)
|
||||
remoteRotationSink?.rotation = newRotation
|
||||
fun setDeviceOrientation(orientation: Orientation) {
|
||||
// set rotation to the video based on the device's orientation and the camera facing direction
|
||||
val rotation = when (orientation) {
|
||||
Orientation.PORTRAIT -> 0
|
||||
Orientation.LANDSCAPE -> if (isCameraFrontFacing()) 90 else -90
|
||||
Orientation.REVERSED_LANDSCAPE -> 270
|
||||
else -> 0
|
||||
}
|
||||
|
||||
// apply the rotation to the streams
|
||||
peerConnection?.setDeviceRotation(rotation)
|
||||
remoteRotationSink?.rotation = rotation
|
||||
}
|
||||
|
||||
fun handleWiredHeadsetChanged(present: Boolean) {
|
||||
@ -721,7 +786,7 @@ class CallManager(
|
||||
connection.setCommunicationMode()
|
||||
setAudioEnabled(true)
|
||||
dataChannel?.let { channel ->
|
||||
val toSend = if (!_videoEvents.value.isEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON
|
||||
val toSend = if (_videoState.value.userVideoEnabled) VIDEO_ENABLED_JSON else VIDEO_DISABLED_JSON
|
||||
val buffer = DataChannel.Buffer(ByteBuffer.wrap(toSend.toString().encodeToByteArray()), false)
|
||||
channel.send(buffer)
|
||||
}
|
||||
@ -750,6 +815,8 @@ class CallManager(
|
||||
|
||||
fun isInitiator(): Boolean = peerConnection?.isInitiator() == true
|
||||
|
||||
fun isCameraFrontFacing() = localCameraState.activeDirection != CameraState.Direction.BACK
|
||||
|
||||
interface WebRtcListener: PeerConnection.Observer {
|
||||
fun onHangup()
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.webrtc
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
@ -29,16 +31,11 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
|
||||
UNTRUSTED_IDENTITY,
|
||||
}
|
||||
|
||||
val localRenderer: SurfaceViewRenderer?
|
||||
get() = callManager.localRenderer
|
||||
val floatingRenderer: SurfaceViewRenderer?
|
||||
get() = callManager.floatingRenderer
|
||||
|
||||
val remoteRenderer: SurfaceViewRenderer?
|
||||
get() = callManager.remoteRenderer
|
||||
|
||||
private var _videoEnabled: Boolean = false
|
||||
|
||||
val videoEnabled: Boolean
|
||||
get() = _videoEnabled
|
||||
val fullscreenRenderer: SurfaceViewRenderer?
|
||||
get() = callManager.fullscreenRenderer
|
||||
|
||||
private var _microphoneEnabled: Boolean = true
|
||||
|
||||
@ -59,18 +56,13 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
|
||||
get() = callManager.audioEvents.map { it.isEnabled }
|
||||
.onEach { _microphoneEnabled = it }
|
||||
|
||||
val localVideoEnabledState
|
||||
get() = callManager.videoEvents
|
||||
.map { it.isEnabled }
|
||||
.onEach { _videoEnabled = it }
|
||||
val videoState: StateFlow<VideoState>
|
||||
get() = callManager.videoState
|
||||
|
||||
val remoteVideoEnabledState
|
||||
get() = callManager.remoteVideoEvents.map { it.isEnabled }
|
||||
|
||||
var deviceRotation: Int = 0
|
||||
var deviceOrientation: Orientation = Orientation.UNKNOWN
|
||||
set(value) {
|
||||
field = value
|
||||
callManager.setDeviceRotation(value)
|
||||
callManager.setDeviceOrientation(value)
|
||||
}
|
||||
|
||||
val currentCallState
|
||||
@ -85,4 +77,7 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
|
||||
val callStartTime: Long
|
||||
get() = callManager.callStartTime
|
||||
|
||||
fun swapVideos() {
|
||||
callManager.swapVideos()
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package org.thoughtcrime.securesms.webrtc
|
||||
|
||||
enum class Orientation {
|
||||
PORTRAIT,
|
||||
LANDSCAPE,
|
||||
REVERSED_LANDSCAPE,
|
||||
UNKNOWN
|
||||
}
|
@ -41,7 +41,7 @@ class PeerConnectionWrapper(private val context: Context,
|
||||
private val mediaStream: MediaStream
|
||||
private val videoSource: VideoSource?
|
||||
private val videoTrack: VideoTrack?
|
||||
private val rotationVideoSink = RotationVideoSink()
|
||||
public val rotationVideoSink = RotationVideoSink()
|
||||
|
||||
val readyForIce
|
||||
get() = peerConnection?.localDescription != null && peerConnection?.remoteDescription != null
|
||||
@ -103,7 +103,7 @@ class PeerConnectionWrapper(private val context: Context,
|
||||
context,
|
||||
rotationVideoSink
|
||||
)
|
||||
rotationVideoSink.mirrored = newCamera.activeDirection == CameraState.Direction.FRONT
|
||||
|
||||
rotationVideoSink.setSink(localRenderer)
|
||||
newVideoTrack.setEnabled(false)
|
||||
mediaStream.addTrack(newVideoTrack)
|
||||
|
@ -0,0 +1,17 @@
|
||||
package org.thoughtcrime.securesms.webrtc
|
||||
|
||||
data class VideoState (
|
||||
val swapped: Boolean,
|
||||
val userVideoEnabled: Boolean,
|
||||
val remoteVideoEnabled: Boolean
|
||||
){
|
||||
fun showFloatingVideo(): Boolean {
|
||||
return userVideoEnabled && !swapped ||
|
||||
remoteVideoEnabled && swapped
|
||||
}
|
||||
|
||||
fun showFullscreenVideo(): Boolean {
|
||||
return userVideoEnabled && swapped ||
|
||||
remoteVideoEnabled && !swapped
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package org.thoughtcrime.securesms.webrtc.data
|
||||
|
||||
// get the video rotation from a specific rotation, locked into 90 degree
|
||||
// chunks offset by 45 degrees
|
||||
fun Int.quadrantRotation() = when (this % 360) {
|
||||
in 315 .. 360,
|
||||
in 0 until 45 -> 0
|
||||
in 45 until 135 -> 90
|
||||
in 135 until 225 -> 180
|
||||
else -> 270
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package org.thoughtcrime.securesms.webrtc.video
|
||||
|
||||
import org.thoughtcrime.securesms.webrtc.data.quadrantRotation
|
||||
|
||||
import org.webrtc.VideoFrame
|
||||
import org.webrtc.VideoSink
|
||||
|
||||
@ -14,8 +14,7 @@ class RemoteRotationVideoProxySink: VideoSink {
|
||||
val thisSink = targetSink ?: return
|
||||
val thisFrame = frame ?: return
|
||||
|
||||
val quadrantRotation = rotation.quadrantRotation()
|
||||
val modifiedRotation = thisFrame.rotation - quadrantRotation
|
||||
val modifiedRotation = thisFrame.rotation - rotation
|
||||
|
||||
val newFrame = VideoFrame(thisFrame.buffer, modifiedRotation, thisFrame.timestampNs)
|
||||
thisSink.onFrame(newFrame)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.webrtc.video
|
||||
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.webrtc.data.quadrantRotation
|
||||
|
||||
import org.webrtc.CapturerObserver
|
||||
import org.webrtc.VideoFrame
|
||||
import org.webrtc.VideoProcessor
|
||||
@ -12,7 +11,6 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
class RotationVideoSink: CapturerObserver, VideoProcessor {
|
||||
|
||||
var rotation: Int = 0
|
||||
var mirrored = false
|
||||
|
||||
private val capturing = AtomicBoolean(false)
|
||||
private var capturerObserver = SoftReference<CapturerObserver>(null)
|
||||
@ -31,13 +29,14 @@ class RotationVideoSink: CapturerObserver, VideoProcessor {
|
||||
val observer = capturerObserver.get()
|
||||
if (videoFrame == null || observer == null || !capturing.get()) return
|
||||
|
||||
val quadrantRotation = rotation.quadrantRotation()
|
||||
|
||||
val newFrame = VideoFrame(videoFrame.buffer, (videoFrame.rotation + quadrantRotation * if (mirrored && quadrantRotation in listOf(90,270)) -1 else 1) % 360, videoFrame.timestampNs)
|
||||
val localFrame = VideoFrame(videoFrame.buffer, videoFrame.rotation * if (mirrored && quadrantRotation in listOf(90,270)) -1 else 1, videoFrame.timestampNs)
|
||||
// cater for frame rotation so that the video is always facing up as we rotate pas a certain point
|
||||
val newFrame = VideoFrame(videoFrame.buffer, videoFrame.rotation - rotation, videoFrame.timestampNs)
|
||||
|
||||
// the frame we are sending to our contact needs to cater for rotation
|
||||
observer.onFrameCaptured(newFrame)
|
||||
sink.get()?.onFrame(localFrame)
|
||||
|
||||
// the frame we see on the user's phone doesn't require changes
|
||||
sink.get()?.onFrame(videoFrame)
|
||||
}
|
||||
|
||||
override fun setSink(sink: VideoSink?) {
|
||||
|
@ -4,6 +4,6 @@
|
||||
android:color="?android:colorControlHighlight">
|
||||
|
||||
<item>
|
||||
<color android:color="?colorCellBackground" />
|
||||
<color android:color="?colorPrimary" />
|
||||
</item>
|
||||
</ripple>
|
@ -3,7 +3,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="?dialog_background_color" />
|
||||
<solid android:color="?backgroundSecondary" />
|
||||
|
||||
<corners
|
||||
android:topLeftRadius="@dimen/dialog_corner_radius"
|
||||
|
@ -3,7 +3,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="?attr/dialog_background_color" />
|
||||
<solid android:color="?backgroundSecondary" />
|
||||
|
||||
<corners android:radius="?dialogCornerRadius" />
|
||||
|
||||
|
@ -6,6 +6,6 @@
|
||||
android:insetBottom="16dp">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="2dp" />
|
||||
<solid android:color="?dialog_background_color" />
|
||||
<solid android:color="?backgroundSecondary" />
|
||||
</shape>
|
||||
</inset>
|
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,7.59l5,-5c0.78,-0.78 2.05,-0.78 2.83,0L20.24,11h-2.83L10.4,4L5.41,9H8v2H2V5h2V7.59zM20,19h2v-6h-6v2h2.59l-4.99,5l-7.01,-7H3.76l8.41,8.41c0.78,0.78 2.05,0.78 2.83,0l5,-5V19z"/>
|
||||
</vector>
|
@ -7,5 +7,5 @@
|
||||
android:pathData="M19.907,7.674H19.907H4.54H4.54C4.317,7.674 4.095,7.719 3.888,7.806L3.888,7.806C3.681,7.893 3.491,8.023 3.334,8.189C3.176,8.355 3.054,8.554 2.978,8.775L3.922,9.097L2.978,8.775C2.903,8.996 2.877,9.231 2.904,9.465L2.904,9.465L2.904,9.469L4.555,23.412C4.555,23.413 4.555,23.413 4.555,23.414C4.603,23.823 4.807,24.189 5.111,24.447C5.415,24.705 5.798,24.84 6.187,24.84H6.188H18.26H18.26C18.649,24.84 19.032,24.705 19.336,24.447C19.64,24.189 19.844,23.823 19.892,23.414C19.892,23.413 19.892,23.413 19.892,23.412L21.543,9.469L21.544,9.465C21.57,9.231 21.544,8.996 21.469,8.775L21.469,8.775C21.393,8.554 21.271,8.355 21.113,8.189C20.956,8.023 20.766,7.893 20.559,7.806L20.17,8.728L20.559,7.806C20.352,7.719 20.13,7.674 19.907,7.674ZM21.412,1.84H3.031C2.045,1.84 1.149,2.609 1.149,3.674V5.828C1.149,6.893 2.045,7.662 3.031,7.662H21.412C22.398,7.662 23.294,6.893 23.294,5.828V3.674C23.294,2.609 22.398,1.84 21.412,1.84Z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#FF3A3A"
|
||||
android:strokeColor="?colorPrimaryDark"/>
|
||||
android:strokeColor="?backgroundSecondary"/>
|
||||
</vector>
|
||||
|
@ -6,7 +6,7 @@
|
||||
android:bottom="@dimen/small_spacing"
|
||||
>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorSettingsBackground"/>
|
||||
<solid android:color="?backgroundSecondary"/>
|
||||
<corners android:bottomLeftRadius="?preferenceCornerRadius"
|
||||
android:bottomRightRadius="?preferenceCornerRadius"/>
|
||||
</shape>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<item android:left="@dimen/medium_spacing"
|
||||
android:right="@dimen/medium_spacing">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorSettingsBackground"/>
|
||||
<solid android:color="?backgroundSecondary"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="bottom"
|
||||
|
@ -6,7 +6,7 @@
|
||||
android:right="@dimen/medium_spacing"
|
||||
android:bottom="@dimen/small_spacing">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorSettingsBackground"/>
|
||||
<solid android:color="?backgroundSecondary"/>
|
||||
<corners android:radius="?preferenceCornerRadius"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorSettingsBackground"/>
|
||||
<solid android:color="?backgroundSecondary"/>
|
||||
<corners android:radius="?preferenceCornerRadius"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
@ -6,7 +6,7 @@
|
||||
android:top="@dimen/small_spacing"
|
||||
>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorSettingsBackground"/>
|
||||
<solid android:color="?backgroundSecondary"/>
|
||||
<corners android:topLeftRadius="?preferenceCornerRadius"
|
||||
android:topRightRadius="?preferenceCornerRadius"/>
|
||||
</shape>
|
||||
|
@ -25,7 +25,7 @@
|
||||
app:cardElevation="0dp"
|
||||
app:cardCornerRadius="@dimen/dialog_corner_radius"
|
||||
android:layout_marginHorizontal="@dimen/medium_spacing"
|
||||
app:cardBackgroundColor="?colorSettingsBackground"
|
||||
app:cardBackgroundColor="?backgroundSecondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
@ -317,7 +317,7 @@
|
||||
<androidx.cardview.widget.CardView
|
||||
app:cardElevation="0dp"
|
||||
android:elevation="0dp"
|
||||
app:cardBackgroundColor="?colorSettingsBackground"
|
||||
app:cardBackgroundColor="?backgroundSecondary"
|
||||
app:cardCornerRadius="@dimen/dialog_corner_radius"
|
||||
android:layout_margin="@dimen/medium_spacing"
|
||||
android:layout_marginBottom="@dimen/massive_spacing"
|
||||
|
@ -10,7 +10,7 @@
|
||||
app:layout_constraintBottom_toTopOf="@+id/unblockButton"
|
||||
app:cardCornerRadius="?preferenceCornerRadius"
|
||||
app:cardElevation="0dp"
|
||||
app:cardBackgroundColor="?colorSettingsBackground"
|
||||
app:cardBackgroundColor="?backgroundSecondary"
|
||||
android:layout_marginHorizontal="@dimen/medium_spacing"
|
||||
android:layout_marginVertical="@dimen/large_spacing"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -8,7 +8,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/remote_parent"
|
||||
android:id="@+id/fullscreen_renderer_container"
|
||||
android:background="@color/black"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@ -18,17 +18,17 @@
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/remote_renderer"
|
||||
android:id="@+id/fullscreen_renderer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/remote_recipient"
|
||||
app:layout_constraintStart_toStartOf="@id/remote_parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/remote_parent"
|
||||
app:layout_constraintTop_toTopOf="@id/remote_parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/remote_parent"
|
||||
app:layout_constraintStart_toStartOf="@id/fullscreen_renderer_container"
|
||||
app:layout_constraintEnd_toEndOf="@id/fullscreen_renderer_container"
|
||||
app:layout_constraintTop_toTopOf="@id/fullscreen_renderer_container"
|
||||
app:layout_constraintBottom_toBottomOf="@id/fullscreen_renderer_container"
|
||||
app:layout_constraintVertical_bias="0.4"
|
||||
android:layout_width="@dimen/extra_large_profile_picture_size"
|
||||
android:layout_height="@dimen/extra_large_profile_picture_size"/>
|
||||
@ -111,6 +111,7 @@
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/floating_renderer_container"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintDimensionRatio="h,9:16"
|
||||
@ -118,12 +119,21 @@
|
||||
android:layout_marginVertical="@dimen/massive_spacing"
|
||||
app:layout_constraintWidth_percent="0.2"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="0dp">
|
||||
android:layout_width="0dp"
|
||||
android:background="?backgroundSecondary">
|
||||
<ImageView
|
||||
android:id="@+id/videocam_off_icon"
|
||||
android:src="@drawable/ic_baseline_videocam_off_24"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
app:tint="?android:textColorPrimary"/>
|
||||
<FrameLayout
|
||||
android:elevation="8dp"
|
||||
android:id="@+id/local_renderer"
|
||||
android:id="@+id/floating_renderer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
<com.github.ybq.android.spinkit.SpinKitView
|
||||
android:id="@+id/local_loading_view"
|
||||
style="@style/SpinKitView.Large.ThreeBounce"
|
||||
@ -133,8 +143,20 @@
|
||||
android:layout_gravity="center"
|
||||
tools:visibility="visible"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/swap_view_icon"
|
||||
android:src="@drawable/ic_baseline_screen_rotation_alt_24"
|
||||
app:layout_constraintTop_toTopOf="@id/floating_renderer_container"
|
||||
app:layout_constraintEnd_toEndOf="@id/floating_renderer_container"
|
||||
app:tint="?android:textColorPrimary"
|
||||
android:layout_marginTop="@dimen/very_small_spacing"
|
||||
android:layout_marginEnd="@dimen/very_small_spacing"
|
||||
android:layout_width="14dp"
|
||||
android:layout_height="14dp"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/endCallButton"
|
||||
android:background="@drawable/circle_tintable"
|
||||
|
@ -10,7 +10,7 @@
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
app:chipStrokeWidth="1dp"
|
||||
app:chipStrokeColor="?elementBorderColor"
|
||||
app:chipBackgroundColor="?dialog_background_color"
|
||||
app:chipBackgroundColor="?backgroundSecondary"
|
||||
app:chipMinTouchTargetSize="0dp"
|
||||
app:chipStartPadding="4dp"
|
||||
tools:ignore="TouchTargetSizeCheck"
|
||||
|
@ -85,8 +85,7 @@
|
||||
android:textColor="?unreadIndicatorTextColor"
|
||||
android:textSize="@dimen/very_small_font_size"
|
||||
android:textStyle="bold"
|
||||
tools:text="8"
|
||||
tools:textColor="?android:textColorPrimary" />
|
||||
tools:text="8"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@ -115,8 +114,7 @@
|
||||
android:textColor="?unreadIndicatorTextColor"
|
||||
android:textSize="@dimen/very_small_font_size"
|
||||
android:textStyle="bold"
|
||||
android:text="@"
|
||||
tools:textColor="?android:textColorPrimary" />
|
||||
android:text="@" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
@ -61,7 +61,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:background="?colorPrimaryDark"
|
||||
android:background="?backgroundSecondary"
|
||||
app:SpinKit_Color="?android:textColorPrimary"
|
||||
android:visibility="gone"/>
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
||||
<attr name="onLightCell" format="reference|color"/>
|
||||
|
||||
<attr name="accentColor" format="reference|color"/>
|
||||
<attr name="backgroundSecondary" format="reference|color"/>
|
||||
<attr name="prominentButtonColor" format="reference|color"/>
|
||||
<attr name="elementBorderColor" format="reference|color"/>
|
||||
<attr name="conversation_background" format="reference|color"/>
|
||||
@ -59,8 +60,6 @@
|
||||
|
||||
<attr name="emoji_tab_strip_background" format="color" />
|
||||
<attr name="emoji_tab_indicator" format="color" />
|
||||
<attr name="emoji_tab_underline" format="color" />
|
||||
<attr name="emoji_tab_seperator" format="color" />
|
||||
<attr name="emoji_drawer_background" format="color" />
|
||||
<attr name="emoji_text_color" format="color" />
|
||||
|
||||
@ -100,7 +99,6 @@
|
||||
|
||||
<attr name="dialog_info_icon" format="reference" />
|
||||
<attr name="dialog_alert_icon" format="reference" />
|
||||
<attr name="dialog_background_color" format="reference|color" />
|
||||
<attr name="conversation_menu_background_color" format="reference|color" />
|
||||
<attr name="conversation_menu_cell_color" format="reference|color"/>
|
||||
<attr name="conversation_menu_border_color" format="reference|color"/>
|
||||
@ -155,7 +153,6 @@
|
||||
<attr name="default_background_start" format="color|reference"/>
|
||||
<attr name="default_background_end" format="color|reference"/>
|
||||
<attr name="colorCellBackground" format="color|reference" />
|
||||
<attr name="colorSettingsBackground" format="color|reference" />
|
||||
<attr name="colorDividerBackground" format="color|reference" />
|
||||
<attr name="outlineButtonBorder" format="color|reference" />
|
||||
<attr name="outlineButtonText" format="color|reference" />
|
||||
|
@ -43,8 +43,6 @@
|
||||
<color name="gray27">#ffbbbbbb</color>
|
||||
<color name="gray50">#ff808080</color>
|
||||
<color name="gray65">#ff595959</color>
|
||||
<color name="gray70">#ff4d4d4d</color>
|
||||
<color name="gray78">#ff383838</color>
|
||||
|
||||
<color name="transparent_black_6">#0f000000</color>
|
||||
<color name="transparent_black_15">#26000000</color>
|
||||
@ -126,7 +124,7 @@
|
||||
|
||||
<color name="classic_accent">#31F196</color>
|
||||
|
||||
<color name="classic_dark_0">#111111</color>
|
||||
<color name="classic_dark_0">#000000</color>
|
||||
<color name="classic_dark_1">#1B1B1B</color>
|
||||
<color name="classic_dark_2">#2D2D2D</color>
|
||||
<color name="classic_dark_3">#414141</color>
|
||||
|
@ -32,6 +32,7 @@
|
||||
<dimen name="fake_chat_view_height">250dp</dimen>
|
||||
<dimen name="setting_button_height">64dp</dimen>
|
||||
<dimen name="dialog_corner_radius">8dp</dimen>
|
||||
<dimen name="video_inset_radius">11dp</dimen>
|
||||
<dimen name="dialog_button_corner_radius">4dp</dimen>
|
||||
<dimen name="pn_option_corner_radius">8dp</dimen>
|
||||
<dimen name="path_status_view_size">8dp</dimen>
|
||||
|
@ -24,8 +24,7 @@
|
||||
|
||||
<style name="ThemeOverlay.Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
|
||||
<item name="android:windowBackground">@drawable/default_dialog_background</item>
|
||||
<item name="android:colorBackground">?attr/dialog_background_color</item>
|
||||
<item name="dialog_background_color">?colorPrimary</item>
|
||||
<item name="android:colorBackground">?backgroundSecondary</item>
|
||||
<item name="android:colorBackgroundFloating">?colorPrimary</item>
|
||||
<item name="backgroundTint">?colorPrimary</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
@ -266,7 +265,7 @@
|
||||
</style>
|
||||
|
||||
<style name="PopupMenu.MessageRequests" parent="@style/Widget.AppCompat.PopupMenu">
|
||||
<item name="android:background">?attr/colorSettingsBackground</item>
|
||||
<item name="android:background">?backgroundSecondary</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<style name="Base.Theme.Session" parent="@style/Theme.AppCompat.DayNight">
|
||||
<item name="actionModeBackground">?colorPrimary</item>
|
||||
<item name="android:colorBackground">?colorPrimary</item>
|
||||
<item name="dialog_background_color">?colorPrimary</item>
|
||||
<item name="backgroundSecondary">@color/classic_dark_1</item>
|
||||
<item name="theme_preview_incoming">?message_received_background_color</item>
|
||||
<item name="theme_preview_outgoing">?message_sent_background_color</item>
|
||||
<item name="theme_preview_background">?colorPrimary</item>
|
||||
@ -33,7 +33,7 @@
|
||||
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.MaterialComponents.SmallComponent</item>
|
||||
<item name="elementBorderColor">?android:textColorSecondary</item>
|
||||
<item name="colorOnSurface">?android:textColorPrimary</item>
|
||||
<item name="colorSurface">?dialog_background_color</item>
|
||||
<item name="colorSurface">?backgroundSecondary</item>
|
||||
<item name="media_overview_toolbar_background">@color/transparent</item>
|
||||
<item name="media_overview_header_foreground">?android:textColorPrimary</item>
|
||||
<item name="menu_accept_icon">@drawable/ic_baseline_done_24</item>
|
||||
@ -193,8 +193,6 @@
|
||||
|
||||
<item name="emoji_tab_strip_background">@color/compose_view_background</item>
|
||||
<item name="emoji_tab_indicator">?colorAccent</item>
|
||||
<item name="emoji_tab_underline">@color/gray78</item>
|
||||
<item name="emoji_tab_seperator">@color/gray70</item>
|
||||
<item name="emoji_drawer_background">@color/compose_text_view_background</item>
|
||||
<item name="emoji_text_color">@color/white</item>
|
||||
|
||||
@ -249,7 +247,7 @@
|
||||
</style>
|
||||
|
||||
<style name="Theme.TextSecure.Dialog.MediaSendProgress" parent="@android:style/Theme.Dialog">
|
||||
<item name="android:colorBackground">?attr/dialog_background_color</item>
|
||||
<item name="android:colorBackground">?backgroundSecondary</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
@ -317,6 +315,7 @@
|
||||
<item name="sessionLogoTint">@color/classic_dark_6</item>
|
||||
<item name="colorPrimary">@color/classic_dark_0</item>
|
||||
<item name="colorPrimaryDark">@color/classic_dark_0</item>
|
||||
<item name="backgroundSecondary">@color/classic_dark_1</item>
|
||||
<item name="colorControlNormal">?android:textColorPrimary</item>
|
||||
<item name="colorControlActivated">?colorAccent</item>
|
||||
<item name="android:textColorPrimary">@color/classic_dark_6</item>
|
||||
@ -326,12 +325,10 @@
|
||||
<item name="android:textColorHint">@color/gray27</item>
|
||||
<item name="android:windowBackground">?colorPrimary</item>
|
||||
<item name="android:navigationBarColor">@color/navigation_bar</item>
|
||||
<item name="dialog_background_color">@color/classic_dark_1</item>
|
||||
<item name="bottomSheetDialogTheme">@style/Classic.Dark.BottomSheet</item>
|
||||
<item name="actionMenuTextColor">?android:textColorPrimary</item>
|
||||
<item name="popupTheme">?actionBarPopupTheme</item>
|
||||
<item name="colorCellBackground">@color/classic_dark_1</item>
|
||||
<item name="colorSettingsBackground">@color/classic_dark_1</item>
|
||||
<item name="colorDividerBackground">@color/classic_dark_3</item>
|
||||
<item name="android:colorControlHighlight">@color/classic_dark_3</item>
|
||||
<item name="colorControlHighlight">@color/classic_dark_3</item>
|
||||
@ -357,10 +354,10 @@
|
||||
<item name="home_gradient_start">#00000000</item>
|
||||
<item name="home_gradient_end">@color/classic_dark_1</item>
|
||||
<item name="conversation_pinned_background_color">?colorCellBackground</item>
|
||||
<item name="conversation_unread_background_color">@color/classic_dark_2</item>
|
||||
<item name="conversation_unread_background_color">@color/classic_dark_1</item>
|
||||
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
|
||||
<item name="unreadIndicatorBackgroundColor">@color/classic_dark_3</item>
|
||||
<item name="unreadIndicatorTextColor">@color/classic_dark_6</item>
|
||||
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
|
||||
<item name="unreadIndicatorTextColor">@color/classic_dark_0</item>
|
||||
|
||||
<!-- New conversation button -->
|
||||
<item name="conversation_color_non_main">@color/classic_dark_2</item>
|
||||
@ -372,7 +369,7 @@
|
||||
<item name="conversationMenuSearchBackgroundColor">@color/classic_dark_0</item>
|
||||
|
||||
<!-- Conversation -->
|
||||
<item name="message_received_background_color">@color/classic_dark_3</item>
|
||||
<item name="message_received_background_color">@color/classic_dark_2</item>
|
||||
<item name="message_received_text_color">@color/classic_dark_6</item>
|
||||
<item name="message_sent_background_color">?colorAccent</item>
|
||||
<item name="message_sent_text_color">@color/classic_dark_0</item>
|
||||
@ -398,7 +395,7 @@
|
||||
<!-- Main styles -->
|
||||
<item name="sessionLogoTint">@color/classic_light_0</item>
|
||||
<item name="colorPrimary">@color/classic_light_6</item>
|
||||
<item name="dialog_background_color">@color/classic_light_5</item>
|
||||
<item name="backgroundSecondary">@color/classic_light_5</item>
|
||||
<item name="colorPrimaryDark">@color/classic_light_6</item>
|
||||
<item name="colorControlNormal">?android:textColorPrimary</item>
|
||||
<item name="colorControlActivated">?colorAccent</item>
|
||||
@ -410,7 +407,6 @@
|
||||
<item name="android:windowBackground">?colorPrimary</item>
|
||||
<item name="android:navigationBarColor">@color/classic_light_navigation_bar</item>
|
||||
<item name="colorCellBackground">@color/classic_light_6</item>
|
||||
<item name="colorSettingsBackground">@color/classic_light_5</item>
|
||||
<item name="colorDividerBackground">@color/classic_light_3</item>
|
||||
<item name="android:colorControlHighlight">@color/classic_light_3</item>
|
||||
<item name="colorControlHighlight">@color/classic_light_3</item>
|
||||
@ -448,7 +444,7 @@
|
||||
<item name="conversation_pinned_background_color">?colorCellBackground</item>
|
||||
<item name="conversation_unread_background_color">@color/classic_light_6</item>
|
||||
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
|
||||
<item name="unreadIndicatorBackgroundColor">@color/classic_light_3</item>
|
||||
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
|
||||
<item name="unreadIndicatorTextColor">@color/classic_light_0</item>
|
||||
<!-- New conversation button -->
|
||||
<item name="conversation_color_non_main">@color/classic_light_4</item>
|
||||
@ -461,7 +457,7 @@
|
||||
<item name="conversationMenuSearchBackgroundColor">@color/classic_light_6</item>
|
||||
|
||||
<!-- Conversation -->
|
||||
<item name="message_received_background_color">@color/classic_light_3</item>
|
||||
<item name="message_received_background_color">@color/classic_light_4</item>
|
||||
<item name="message_received_text_color">@color/classic_light_0</item>
|
||||
<item name="message_sent_background_color">?colorAccent</item>
|
||||
<item name="message_sent_text_color">@color/classic_light_0</item>
|
||||
@ -489,6 +485,7 @@
|
||||
<item name="sessionLogoTint">@color/ocean_dark_7</item>
|
||||
<item name="colorPrimary">@color/ocean_dark_2</item>
|
||||
<item name="colorPrimaryDark">@color/ocean_dark_2</item>
|
||||
<item name="backgroundSecondary">@color/ocean_dark_1</item>
|
||||
<item name="colorControlNormal">@color/ocean_dark_7</item>
|
||||
<item name="colorControlActivated">?colorAccent</item>
|
||||
<item name="android:textColorPrimary">@color/ocean_dark_7</item>
|
||||
@ -501,7 +498,6 @@
|
||||
<item name="default_background_end">?colorPrimary</item>
|
||||
<item name="default_background_start">?colorPrimaryDark</item>
|
||||
<item name="colorCellBackground">@color/ocean_dark_3</item>
|
||||
<item name="colorSettingsBackground">@color/ocean_dark_1</item>
|
||||
<item name="colorDividerBackground">@color/ocean_dark_4</item>
|
||||
<item name="android:colorControlHighlight">@color/ocean_dark_4</item>
|
||||
<item name="colorControlHighlight">@color/ocean_dark_4</item>
|
||||
@ -530,7 +526,7 @@
|
||||
<item name="home_gradient_start">#00000000</item>
|
||||
<item name="home_gradient_end">@color/ocean_dark_3</item>
|
||||
<item name="conversation_pinned_background_color">?colorCellBackground</item>
|
||||
<item name="conversation_unread_background_color">@color/ocean_dark_4</item>
|
||||
<item name="conversation_unread_background_color">@color/ocean_dark_3</item>
|
||||
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
|
||||
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
|
||||
<item name="unreadIndicatorTextColor">@color/ocean_dark_0</item>
|
||||
@ -575,6 +571,7 @@
|
||||
<item name="sessionLogoTint">@color/ocean_light_1</item>
|
||||
<item name="colorPrimary">@color/ocean_light_7</item>
|
||||
<item name="colorPrimaryDark">@color/ocean_light_6</item>
|
||||
<item name="backgroundSecondary">@color/ocean_light_6</item>
|
||||
<item name="colorControlNormal">@color/ocean_light_1</item>
|
||||
<item name="colorControlActivated">?colorAccent</item>
|
||||
<item name="android:textColorPrimary">@color/ocean_light_1</item>
|
||||
@ -587,7 +584,6 @@
|
||||
<item name="default_background_end">@color/ocean_light_7</item>
|
||||
<item name="default_background_start">@color/ocean_light_6</item>
|
||||
<item name="colorCellBackground">@color/ocean_light_5</item>
|
||||
<item name="colorSettingsBackground">@color/ocean_light_6</item>
|
||||
<item name="colorDividerBackground">@color/ocean_light_3</item>
|
||||
<item name="android:colorControlHighlight">@color/ocean_light_4</item>
|
||||
<item name="colorControlHighlight">@color/ocean_light_4</item>
|
||||
@ -654,7 +650,7 @@
|
||||
<item name="scroll_to_bottom_button_border">?input_bar_button_background_opaque_border</item>
|
||||
<item name="conversation_unread_count_indicator_background">?colorAccent</item>
|
||||
<item name="conversation_pinned_background_color">?colorCellBackground</item>
|
||||
<item name="conversation_unread_background_color">@color/ocean_light_6</item>
|
||||
<item name="conversation_unread_background_color">@color/ocean_light_5</item>
|
||||
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
|
||||
<item name="message_selected">@color/ocean_light_5</item>
|
||||
</style>
|
||||
@ -668,6 +664,7 @@
|
||||
<item name="sessionLogoTint">@color/classic_dark_6</item>
|
||||
<item name="colorPrimary">@color/classic_dark_0</item>
|
||||
<item name="colorPrimaryDark">@color/classic_dark_0</item>
|
||||
<item name="backgroundSecondary">@color/classic_dark_1</item>
|
||||
<item name="colorControlNormal">?android:textColorPrimary</item>
|
||||
<item name="colorControlActivated">?colorAccent</item>
|
||||
<item name="android:colorControlHighlight">?colorAccent</item>
|
||||
@ -678,12 +675,10 @@
|
||||
<item name="android:textColorHint">@color/gray27</item>
|
||||
<item name="android:windowBackground">?colorPrimary</item>
|
||||
<item name="android:navigationBarColor">@color/compose_view_background</item>
|
||||
<item name="dialog_background_color">@color/classic_dark_1</item>
|
||||
<item name="bottomSheetDialogTheme">@style/Classic.Dark.BottomSheet</item>
|
||||
<item name="actionMenuTextColor">?android:textColorPrimary</item>
|
||||
<item name="popupTheme">?actionBarPopupTheme</item>
|
||||
<item name="colorCellBackground">@color/classic_dark_1</item>
|
||||
<item name="colorSettingsBackground">@color/classic_dark_1</item>
|
||||
<item name="colorDividerBackground">@color/classic_dark_3</item>
|
||||
<item name="actionBarPopupTheme">@style/Dark.Popup</item>
|
||||
<item name="actionBarWidgetTheme">@null</item>
|
||||
@ -701,10 +696,10 @@
|
||||
<item name="home_gradient_start">#00000000</item>
|
||||
<item name="home_gradient_end">@color/classic_dark_1</item>
|
||||
<item name="conversation_pinned_background_color">?colorCellBackground</item>
|
||||
<item name="conversation_unread_background_color">@color/classic_dark_2</item>
|
||||
<item name="conversation_unread_background_color">@color/classic_dark_1</item>
|
||||
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
|
||||
<item name="unreadIndicatorBackgroundColor">@color/classic_dark_3</item>
|
||||
<item name="unreadIndicatorTextColor">@color/classic_dark_6</item>
|
||||
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
|
||||
<item name="unreadIndicatorTextColor">@color/classic_dark_0</item>
|
||||
|
||||
<!-- New conversation button -->
|
||||
<item name="conversation_color_non_main">@color/classic_dark_2</item>
|
||||
@ -716,7 +711,7 @@
|
||||
<item name="conversationMenuSearchBackgroundColor">@color/classic_dark_0</item>
|
||||
|
||||
<!-- Conversation -->
|
||||
<item name="message_received_background_color">@color/classic_dark_3</item>
|
||||
<item name="message_received_background_color">@color/classic_dark_2</item>
|
||||
<item name="message_received_text_color">@color/classic_dark_6</item>
|
||||
<item name="message_sent_background_color">?colorAccent</item>
|
||||
<item name="message_sent_text_color">@color/classic_dark_0</item>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
<domain includeSubdomains="true">public.loki.foundation</domain>
|
||||
</domain-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="false">seed1.getsession.org</domain>
|
||||
|
@ -21,7 +21,7 @@ googleServicesVersion=4.3.12
|
||||
kotlinVersion=1.8.21
|
||||
android.useAndroidX=true
|
||||
appcompatVersion=1.6.1
|
||||
coreVersion=1.8.0
|
||||
coreVersion=1.13.1
|
||||
composeVersion=1.6.4
|
||||
coroutinesVersion=1.6.4
|
||||
curve25519Version=0.6.0
|
||||
@ -34,7 +34,7 @@ kovenantVersion=3.3.0
|
||||
lifecycleVersion=2.7.0
|
||||
materialVersion=1.8.0
|
||||
mockitoKotlinVersion=4.1.0
|
||||
okhttpVersion=3.12.1
|
||||
okhttpVersion=4.12.0
|
||||
pagingVersion=3.0.0
|
||||
preferenceVersion=1.2.0
|
||||
protobufVersion=2.5.0
|
||||
|
@ -3,8 +3,11 @@ package org.session.libsession.messaging.file_server
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsignal.utilities.HTTP
|
||||
@ -37,18 +40,18 @@ object FileServerApi {
|
||||
)
|
||||
|
||||
private fun createBody(body: ByteArray?, parameters: Any?): RequestBody? {
|
||||
if (body != null) return RequestBody.create(MediaType.get("application/octet-stream"), body)
|
||||
if (body != null) return RequestBody.create("application/octet-stream".toMediaType(), body)
|
||||
if (parameters == null) return null
|
||||
val parametersAsJSON = JsonUtil.toJson(parameters)
|
||||
return RequestBody.create(MediaType.get("application/json"), parametersAsJSON)
|
||||
return RequestBody.create("application/json".toMediaType(), parametersAsJSON)
|
||||
}
|
||||
|
||||
private fun send(request: Request): Promise<ByteArray, Exception> {
|
||||
val url = HttpUrl.parse(server) ?: return Promise.ofFail(Error.InvalidURL)
|
||||
val url = server.toHttpUrlOrNull() ?: return Promise.ofFail(Error.InvalidURL)
|
||||
val urlBuilder = HttpUrl.Builder()
|
||||
.scheme(url.scheme())
|
||||
.host(url.host())
|
||||
.port(url.port())
|
||||
.scheme(url.scheme)
|
||||
.host(url.host)
|
||||
.port(url.port)
|
||||
.addPathSegments(request.endpoint)
|
||||
if (request.verb == HTTP.Verb.GET) {
|
||||
for ((key, value) in request.queryParameters) {
|
||||
@ -57,7 +60,7 @@ object FileServerApi {
|
||||
}
|
||||
val requestBuilder = okhttp3.Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.headers(Headers.of(request.headers))
|
||||
.headers(request.headers.toHeaders())
|
||||
when (request.verb) {
|
||||
HTTP.Verb.GET -> requestBuilder.get()
|
||||
HTTP.Verb.PUT -> requestBuilder.put(createBody(request.body, request.parameters)!!)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.session.libsession.database.MessageDataProvider
|
||||
import org.session.libsession.database.StorageProtocol
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
@ -141,8 +142,8 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
|
||||
DownloadUtilities.downloadFile(tempFile, attachment.url)
|
||||
} else {
|
||||
Log.d("AttachmentDownloadJob", "downloading open group attachment")
|
||||
val url = HttpUrl.parse(attachment.url)!!
|
||||
val fileID = url.pathSegments().last()
|
||||
val url = attachment.url.toHttpUrlOrNull()!!
|
||||
val fileID = url.pathSegments.last()
|
||||
OpenGroupApi.download(fileID, openGroup.room, openGroup.server).get().let {
|
||||
tempFile.writeBytes(it)
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
val kryo = Kryo()
|
||||
kryo.isRegistrationRequired = false
|
||||
val serializedMessage = ByteArray(4096)
|
||||
val output = Output(serializedMessage, Job.MAX_BUFFER_SIZE)
|
||||
val output = Output(serializedMessage, Job.MAX_BUFFER_SIZE_BYTES)
|
||||
kryo.writeClassAndObject(output, message)
|
||||
output.close()
|
||||
return Data.Builder()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
@ -21,9 +22,9 @@ class BackgroundGroupAddJob(val joinUrl: String): Job {
|
||||
override val maxFailureCount: Int = 1
|
||||
|
||||
val openGroupId: String? get() {
|
||||
val url = HttpUrl.parse(joinUrl) ?: return null
|
||||
val url = joinUrl.toHttpUrlOrNull() ?: return null
|
||||
val server = OpenGroup.getServer(joinUrl)?.toString()?.removeSuffix("/") ?: return null
|
||||
val room = url.pathSegments().firstOrNull() ?: return null
|
||||
val room = url.pathSegments.firstOrNull() ?: return null
|
||||
return "$server.$room"
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import network.loki.messenger.libsession_util.ConfigBase.Companion.protoKindFor
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
@ -10,7 +11,6 @@ import org.session.libsession.messaging.utilities.Data
|
||||
import org.session.libsession.snode.RawResponse
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsignal.utilities.Log
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
// only contact (self) and closed group destinations will be supported
|
||||
data class ConfigurationSyncJob(val destination: Destination): Job {
|
||||
@ -180,7 +180,6 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
|
||||
// type mappings
|
||||
const val CONTACT_TYPE = 1
|
||||
const val GROUP_TYPE = 2
|
||||
|
||||
}
|
||||
|
||||
class Factory: Job.Factory<ConfigurationSyncJob> {
|
||||
|
@ -14,7 +14,7 @@ interface Job {
|
||||
// Keys used for database storage
|
||||
private val ID_KEY = "id"
|
||||
private val FAILURE_COUNT_KEY = "failure_count"
|
||||
internal const val MAX_BUFFER_SIZE = 1_000_000 // bytes
|
||||
internal const val MAX_BUFFER_SIZE_BYTES = 1_000_000 // ~1MB
|
||||
}
|
||||
|
||||
suspend fun execute(dispatcherName: String)
|
||||
|
@ -4,7 +4,7 @@ import com.esotericsoftware.kryo.Kryo
|
||||
import com.esotericsoftware.kryo.io.Input
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE
|
||||
import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE_BYTES
|
||||
import org.session.libsession.messaging.messages.Destination
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
@ -118,12 +118,12 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
val kryo = Kryo()
|
||||
kryo.isRegistrationRequired = false
|
||||
// Message
|
||||
val messageOutput = Output(ByteArray(4096), MAX_BUFFER_SIZE)
|
||||
val messageOutput = Output(ByteArray(4096), MAX_BUFFER_SIZE_BYTES)
|
||||
kryo.writeClassAndObject(messageOutput, message)
|
||||
messageOutput.close()
|
||||
val serializedMessage = messageOutput.toBytes()
|
||||
// Destination
|
||||
val destinationOutput = Output(ByteArray(4096), MAX_BUFFER_SIZE)
|
||||
val destinationOutput = Output(ByteArray(4096), MAX_BUFFER_SIZE_BYTES)
|
||||
kryo.writeClassAndObject(destinationOutput, destination)
|
||||
destinationOutput.close()
|
||||
val serializedDestination = destinationOutput.toBytes()
|
||||
|
@ -3,10 +3,10 @@ package org.session.libsession.messaging.jobs
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
import com.esotericsoftware.kryo.io.Input
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE
|
||||
import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE_BYTES
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.Server
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
@ -33,7 +33,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
|
||||
val server = Server.LEGACY
|
||||
val parameters = mapOf( "data" to message.data, "send_to" to message.recipient )
|
||||
val url = "${server.url}/notify"
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val body = RequestBody.create("application/json".toMediaType(), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body).build()
|
||||
retryIfNeeded(4) {
|
||||
OnionRequestAPI.sendOnionRequest(
|
||||
@ -67,7 +67,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
|
||||
val kryo = Kryo()
|
||||
kryo.isRegistrationRequired = false
|
||||
val serializedMessage = ByteArray(4096)
|
||||
val output = Output(serializedMessage, MAX_BUFFER_SIZE)
|
||||
val output = Output(serializedMessage, MAX_BUFFER_SIZE_BYTES)
|
||||
kryo.writeObject(output, message)
|
||||
output.close()
|
||||
return Data.Builder()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.session.libsession.messaging.open_groups
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.Log
|
||||
import java.util.Locale
|
||||
@ -47,11 +48,11 @@ data class OpenGroup(
|
||||
}
|
||||
|
||||
fun getServer(urlAsString: String): HttpUrl? {
|
||||
val url = HttpUrl.parse(urlAsString) ?: return null
|
||||
val builder = HttpUrl.Builder().scheme(url.scheme()).host(url.host())
|
||||
if (url.port() != 80 || url.port() != 443) {
|
||||
val url = urlAsString.toHttpUrlOrNull() ?: return null
|
||||
val builder = HttpUrl.Builder().scheme(url.scheme).host(url.host)
|
||||
if (url.port != 80 || url.port != 443) {
|
||||
// Non-standard port; add to server
|
||||
builder.port(url.port())
|
||||
builder.port(url.port)
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
|
@ -14,8 +14,11 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller.Companion.maxInactivityPeriod
|
||||
@ -282,10 +285,10 @@ object OpenGroupApi {
|
||||
)
|
||||
|
||||
private fun createBody(body: ByteArray?, parameters: Any?): RequestBody? {
|
||||
if (body != null) return RequestBody.create(MediaType.get("application/octet-stream"), body)
|
||||
if (body != null) return RequestBody.create("application/octet-stream".toMediaType(), body)
|
||||
if (parameters == null) return null
|
||||
val parametersAsJSON = JsonUtil.toJson(parameters)
|
||||
return RequestBody.create(MediaType.get("application/json"), parametersAsJSON)
|
||||
return RequestBody.create("application/json".toMediaType(), parametersAsJSON)
|
||||
}
|
||||
|
||||
private fun getResponseBody(request: Request): Promise<ByteArray, Exception> {
|
||||
@ -301,7 +304,7 @@ object OpenGroupApi {
|
||||
}
|
||||
|
||||
private fun send(request: Request): Promise<OnionResponse, Exception> {
|
||||
HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.InvalidURL)
|
||||
request.server.toHttpUrlOrNull() ?: return Promise.ofFail(Error.InvalidURL)
|
||||
val urlBuilder = StringBuilder("${request.server}/${request.endpoint.value}")
|
||||
if (request.verb == GET && request.queryParameters.isNotEmpty()) {
|
||||
urlBuilder.append("?")
|
||||
@ -387,7 +390,7 @@ object OpenGroupApi {
|
||||
|
||||
val requestBuilder = okhttp3.Request.Builder()
|
||||
.url(urlRequest)
|
||||
.headers(Headers.of(headers))
|
||||
.headers(headers.toHeaders())
|
||||
when (request.verb) {
|
||||
GET -> requestBuilder.get()
|
||||
PUT -> requestBuilder.put(createBody(request.body, request.parameters)!!)
|
||||
|
@ -3,6 +3,7 @@ package org.session.libsession.messaging.sending_receiving.notifications
|
||||
import android.annotation.SuppressLint
|
||||
import nl.komponents.kovenant.Promise
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
@ -58,7 +59,7 @@ object PushRegistryV1 {
|
||||
|
||||
val url = "${server.url}/register_legacy_groups_only"
|
||||
val body = RequestBody.create(
|
||||
MediaType.get("application/json"),
|
||||
"application/json".toMediaType(),
|
||||
JsonUtil.toJson(parameters)
|
||||
)
|
||||
val request = Request.Builder().url(url).post(body).build()
|
||||
@ -83,7 +84,7 @@ object PushRegistryV1 {
|
||||
return retryIfNeeded(maxRetryCount) {
|
||||
val parameters = mapOf("token" to token)
|
||||
val url = "${server.url}/unregister"
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val body = RequestBody.create("application/json".toMediaType(), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body).build()
|
||||
|
||||
sendOnionRequest(request) success {
|
||||
@ -120,7 +121,7 @@ object PushRegistryV1 {
|
||||
): Promise<*, Exception> {
|
||||
val parameters = mapOf("closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey)
|
||||
val url = "${server.url}/$operation"
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val body = RequestBody.create("application/json".toMediaType(), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body).build()
|
||||
|
||||
return retryIfNeeded(maxRetryCount) {
|
||||
|
@ -467,9 +467,9 @@ object OnionRequestAPI {
|
||||
x25519PublicKey: String,
|
||||
version: Version = Version.V4
|
||||
): Promise<OnionResponse, Exception> {
|
||||
val url = request.url()
|
||||
val url = request.url
|
||||
val payload = generatePayload(request, server, version)
|
||||
val destination = Destination.Server(url.host(), version.value, x25519PublicKey, url.scheme(), url.port())
|
||||
val destination = Destination.Server(url.host, version.value, x25519PublicKey, url.scheme, url.port)
|
||||
return sendOnionRequest(destination, payload, version).recover { exception ->
|
||||
Log.d("Loki", "Couldn't reach server: $url due to error: $exception.")
|
||||
throw exception
|
||||
@ -478,7 +478,7 @@ object OnionRequestAPI {
|
||||
|
||||
private fun generatePayload(request: Request, server: String, version: Version): ByteArray {
|
||||
val headers = request.getHeadersForOnionRequest().toMutableMap()
|
||||
val url = request.url()
|
||||
val url = request.url
|
||||
val urlAsString = url.toString()
|
||||
val body = request.getBodyForOnionRequest() ?: "null"
|
||||
val endpoint = when {
|
||||
@ -486,19 +486,19 @@ object OnionRequestAPI {
|
||||
else -> ""
|
||||
}
|
||||
return if (version == Version.V4) {
|
||||
if (request.body() != null &&
|
||||
if (request.body != null &&
|
||||
headers.keys.find { it.equals("Content-Type", true) } == null) {
|
||||
headers["Content-Type"] = "application/json"
|
||||
}
|
||||
val requestPayload = mapOf(
|
||||
"endpoint" to endpoint,
|
||||
"method" to request.method(),
|
||||
"method" to request.method,
|
||||
"headers" to headers
|
||||
)
|
||||
val requestData = JsonUtil.toJson(requestPayload).toByteArray()
|
||||
val prefixData = "l${requestData.size}:".toByteArray(Charsets.US_ASCII)
|
||||
val suffixData = "e".toByteArray(Charsets.US_ASCII)
|
||||
if (request.body() != null) {
|
||||
if (request.body != null) {
|
||||
val bodyData = if (body is ByteArray) body else body.toString().toByteArray()
|
||||
val bodyLengthData = "${bodyData.size}:".toByteArray(Charsets.US_ASCII)
|
||||
prefixData + requestData + bodyLengthData + bodyData + suffixData
|
||||
@ -509,7 +509,7 @@ object OnionRequestAPI {
|
||||
val payload = mapOf(
|
||||
"body" to body,
|
||||
"endpoint" to endpoint.removePrefix("/"),
|
||||
"method" to request.method(),
|
||||
"method" to request.method,
|
||||
"headers" to headers
|
||||
)
|
||||
JsonUtil.toJson(payload).toByteArray()
|
||||
|
@ -9,13 +9,13 @@ import java.util.Locale
|
||||
|
||||
internal fun Request.getHeadersForOnionRequest(): Map<String, Any> {
|
||||
val result = mutableMapOf<String, Any>()
|
||||
val contentType = body()?.contentType()
|
||||
val contentType = body?.contentType()
|
||||
if (contentType != null) {
|
||||
result["content-type"] = contentType.toString()
|
||||
}
|
||||
val headers = headers()
|
||||
val headers = headers
|
||||
for (name in headers.names()) {
|
||||
val value = headers.get(name)
|
||||
val value = headers[name]
|
||||
if (value != null) {
|
||||
if (value.toLowerCase(Locale.US) == "true" || value.toLowerCase(Locale.US) == "false") {
|
||||
result[name] = value.toBoolean()
|
||||
@ -33,7 +33,7 @@ internal fun Request.getBodyForOnionRequest(): Any? {
|
||||
try {
|
||||
val copyOfThis = newBuilder().build()
|
||||
val buffer = Buffer()
|
||||
val body = copyOfThis.body() ?: return null
|
||||
val body = copyOfThis.body ?: return null
|
||||
body.writeTo(buffer)
|
||||
val bodyAsData = buffer.readByteArray()
|
||||
if (body is MultipartBody) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.session.libsession.messaging.file_server.FileServerApi
|
||||
import org.session.libsignal.utilities.HTTP
|
||||
import org.session.libsignal.utilities.Log
|
||||
@ -36,8 +37,8 @@ object DownloadUtilities {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun downloadFile(outputStream: OutputStream, urlAsString: String) {
|
||||
val url = HttpUrl.parse(urlAsString)!!
|
||||
val fileID = url.pathSegments().last()
|
||||
val url = urlAsString.toHttpUrlOrNull()!!
|
||||
val fileID = url.pathSegments.last()
|
||||
try {
|
||||
FileServerApi.download(fileID).get().let {
|
||||
outputStream.write(it)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.session.libsession.messaging.open_groups.migrateLegacyServerUrl
|
||||
|
||||
object OpenGroupUrlParser {
|
||||
@ -19,14 +20,14 @@ object OpenGroupUrlParser {
|
||||
// URL has to start with 'http://'
|
||||
val urlWithPrefix = if (!string.startsWith("http")) "http://$string" else string
|
||||
// If the URL is malformed, throw an exception
|
||||
val url = HttpUrl.parse(urlWithPrefix) ?: throw Error.MalformedURL
|
||||
val url = urlWithPrefix.toHttpUrlOrNull() ?: throw Error.MalformedURL
|
||||
// Parse components
|
||||
val server = HttpUrl.Builder().scheme(url.scheme()).host(url.host()).port(url.port()).build().toString().removeSuffix(suffix).migrateLegacyServerUrl()
|
||||
val room = url.pathSegments().firstOrNull { !it.isNullOrEmpty() } ?: throw Error.NoRoom
|
||||
val server = HttpUrl.Builder().scheme(url.scheme).host(url.host).port(url.port).build().toString().removeSuffix(suffix).migrateLegacyServerUrl()
|
||||
val room = url.pathSegments.firstOrNull { !it.isNullOrEmpty() } ?: throw Error.NoRoom
|
||||
val publicKey = url.queryParameter(queryPrefix) ?: throw Error.NoPublicKey
|
||||
if (publicKey.length != 64) throw Error.InvalidPublicKey
|
||||
// Return
|
||||
return V2OpenGroupInfo(server,room,publicKey)
|
||||
return V2OpenGroupInfo(server, room, publicKey)
|
||||
}
|
||||
|
||||
fun trimQueryParameter(string: String): String {
|
||||
|
@ -9,7 +9,7 @@ import java.util.concurrent.atomic.AtomicReference
|
||||
* Not really a 'debouncer' but named to be similar to the current Debouncer
|
||||
* designed to queue tasks on a window (if not already queued) like a timer
|
||||
*/
|
||||
class WindowDebouncer(private val window: Long, private val timer: Timer) {
|
||||
class WindowDebouncer(private val timeWindowMilliseconds: Long, private val timer: Timer) {
|
||||
|
||||
private val atomicRef: AtomicReference<Runnable?> = AtomicReference(null)
|
||||
private val hasStarted = AtomicBoolean(false)
|
||||
@ -23,7 +23,7 @@ class WindowDebouncer(private val window: Long, private val timer: Timer) {
|
||||
|
||||
fun publish(runnable: Runnable) {
|
||||
if (hasStarted.compareAndSet(false, true)) {
|
||||
timer.scheduleAtFixedRate(recursiveRunnable, 0, window)
|
||||
timer.scheduleAtFixedRate(recursiveRunnable, 0, timeWindowMilliseconds)
|
||||
}
|
||||
atomicRef.compareAndSet(null, runnable)
|
||||
}
|
||||
|
@ -53,8 +53,6 @@
|
||||
|
||||
<attr name="emoji_tab_strip_background" format="color" />
|
||||
<attr name="emoji_tab_indicator" format="color" />
|
||||
<attr name="emoji_tab_underline" format="color" />
|
||||
<attr name="emoji_tab_seperator" format="color" />
|
||||
<attr name="emoji_drawer_background" format="color" />
|
||||
<attr name="emoji_text_color" format="color" />
|
||||
|
||||
@ -93,7 +91,6 @@
|
||||
|
||||
<attr name="dialog_info_icon" format="reference" />
|
||||
<attr name="dialog_alert_icon" format="reference" />
|
||||
<attr name="dialog_background_color" format="reference|color" />
|
||||
|
||||
<attr name="conversation_icon_attach_audio" format="reference"/>
|
||||
<attr name="conversation_icon_attach_video" format="reference" />
|
||||
|
@ -40,7 +40,6 @@
|
||||
<color name="gray27">#ffbbbbbb</color>
|
||||
<color name="gray50">#ff808080</color>
|
||||
<color name="gray65">#ff595959</color>
|
||||
<color name="gray70">#ff4d4d4d</color>
|
||||
<color name="gray78">#ff383838</color>
|
||||
|
||||
<color name="transparent_black_30">#30000000</color>
|
||||
|
@ -80,4 +80,7 @@
|
||||
<string name="clearDevice">Clear Device</string>
|
||||
<string name="clearDeviceOnly">Clear device only</string>
|
||||
<string name="clearDeviceAndNetwork">Clear device and network</string>
|
||||
|
||||
<string name="profileDisplayPictureRemoveError">Failed to remove display picture.</string>
|
||||
<string name="profileErrorUpdate">Failed to update profile.</string>
|
||||
</resources>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.session.libsignal.utilities
|
||||
|
||||
import okhttp3.MediaType
|
||||
import android.util.Log
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
@ -11,10 +12,12 @@ import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
|
||||
object HTTP {
|
||||
var isConnectedToNetwork: (() -> Boolean) = { false }
|
||||
|
||||
private val seedNodeConnection by lazy {
|
||||
|
||||
OkHttpClient().newBuilder()
|
||||
.callTimeout(timeout, TimeUnit.SECONDS)
|
||||
.connectTimeout(timeout, TimeUnit.SECONDS)
|
||||
@ -106,7 +109,7 @@ object HTTP {
|
||||
Verb.GET -> request.get()
|
||||
Verb.PUT, Verb.POST -> {
|
||||
if (body == null) { throw Exception("Invalid request body.") }
|
||||
val contentType = MediaType.get("application/json; charset=utf-8")
|
||||
val contentType = "application/json; charset=utf-8".toMediaType()
|
||||
@Suppress("NAME_SHADOWING") val body = RequestBody.create(contentType, body)
|
||||
if (verb == Verb.PUT) request.put(body) else request.post(body)
|
||||
}
|
||||
@ -114,7 +117,7 @@ object HTTP {
|
||||
}
|
||||
lateinit var response: Response
|
||||
try {
|
||||
val connection = if (timeout != HTTP.timeout) { // Custom timeout
|
||||
val connection: OkHttpClient = if (timeout != HTTP.timeout) { // Custom timeout
|
||||
if (useSeedNodeConnection) {
|
||||
throw IllegalStateException("Setting a custom timeout is only allowed for requests to snodes.")
|
||||
}
|
||||
@ -122,6 +125,7 @@ object HTTP {
|
||||
} else {
|
||||
if (useSeedNodeConnection) seedNodeConnection else defaultConnection
|
||||
}
|
||||
|
||||
response = connection.newCall(request.build()).execute()
|
||||
} catch (exception: Exception) {
|
||||
Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.")
|
||||
@ -131,9 +135,9 @@ object HTTP {
|
||||
// Override the actual error so that we can correctly catch failed requests in OnionRequestAPI
|
||||
throw HTTPRequestFailedException(0, null, "HTTP request failed due to: ${exception.message}")
|
||||
}
|
||||
return when (val statusCode = response.code()) {
|
||||
return when (val statusCode = response.code) {
|
||||
200 -> {
|
||||
response.body()?.bytes() ?: throw Exception("An error occurred.")
|
||||
response.body!!.bytes()
|
||||
}
|
||||
else -> {
|
||||
Log.d("Loki", "${verb.rawValue} request to $url failed with status code: $statusCode.")
|
||||
|
Loading…
x
Reference in New Issue
Block a user