Merge pull request #1047 from session-foundation/release/1.21.3

Release/1.21.3
pull/1713/head
SessionHero01 4 weeks ago committed by GitHub
commit d8193c469e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -15,8 +15,8 @@ configurations.configureEach {
exclude module: "commons-logging"
}
def canonicalVersionCode = 398
def canonicalVersionName = "1.21.2"
def canonicalVersionCode = 401
def canonicalVersionName = "1.21.3"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
@ -350,7 +350,6 @@ dependencies {
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
implementation "nl.komponents.kovenant:kovenant-android:$kovenantVersion"
implementation "com.jakewharton.rxbinding3:rxbinding:3.1.0"
implementation "com.github.ybq:Android-SpinKit:1.4.0"
implementation "com.opencsv:opencsv:4.6"
testImplementation "junit:junit:$junitVersion"
testImplementation 'org.assertj:assertj-core:3.11.1'

@ -5,11 +5,8 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import com.squareup.phrase.Phrase
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewSearchBottomBarBinding
import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.TOTAL_COUNT_KEY
class SearchBottomBar : LinearLayout {
private lateinit var binding: ViewSearchBottomBarBinding

@ -66,7 +66,7 @@ class EnterCommunityUrlFragment : Fragment() {
groups.iterator().forEach { defaultGroup ->
val chip = layoutInflater.inflate(R.layout.default_group_chip, binding.defaultRoomsFlexboxLayout, false) as Chip
val drawable = defaultGroup.image?.let { bytes ->
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
val bitmap = BitmapFactory.decodeByteArray(bytes.data, bytes.offset, bytes.len)
RoundedBitmapDrawableFactory.create(resources, bitmap).apply {
isCircular = true
}

@ -299,6 +299,8 @@ class GroupManagerV2Impl @Inject constructor(
configs.groupInfo.getName().orEmpty()
}
Log.w(TAG, "Failed to invite members to group $group", e)
throw GroupInviteException(
isPromotion = false,
inviteeAccountIds = newMembers.map { it.hexString },

@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
@ -23,11 +24,13 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onErrorResume
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN
import org.session.libsession.utilities.ConfigUpdateNotification
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord
@ -78,9 +81,11 @@ class HomeViewModel @Inject constructor(
)
}
}
)
}
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
) as? Data?
}.catch { err ->
Log.e("HomeViewModel", "Error loading conversation list", err)
emit(null)
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
private fun hasHiddenMessageRequests() = TextSecurePreferences.events
.filter { it == TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS }

@ -91,6 +91,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
private ViewGroup playbackControlsContainer;
private TextView charactersLeft;
private View closeButton;
private View loader;
private ControllableViewPager fragmentPager;
private MediaSendFragmentPagerAdapter fragmentPagerAdapter;
@ -152,6 +153,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
playbackControlsContainer = view.findViewById(R.id.mediasend_playback_controls_container);
charactersLeft = view.findViewById(R.id.mediasend_characters_left);
closeButton = view.findViewById(R.id.mediasend_close_button);
loader = view.findViewById(R.id.loader);
View sendButtonBkg = view.findViewById(R.id.mediasend_send_button_bkg);
@ -423,19 +425,12 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
private Stopwatch renderTimer;
private Runnable progressTimer;
private AlertDialog dialog;
@Override
protected void onPreExecute() {
renderTimer = new Stopwatch("ProcessMedia");
progressTimer = () -> {
dialog = new AlertDialog.Builder(new ContextThemeWrapper(requireContext(), R.style.Theme_TextSecure_Dialog_MediaSendProgress))
.setView(R.layout.progress_dialog)
.setCancelable(false)
.create();
dialog.show();
dialog.getWindow().setLayout(getResources().getDimensionPixelSize(R.dimen.mediasend_progress_dialog_size),
getResources().getDimensionPixelSize(R.dimen.mediasend_progress_dialog_size));
loader.setVisibility(View.VISIBLE);
};
Util.runOnMainDelayed(progressTimer, 250);
}
@ -476,9 +471,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
protected void onPostExecute(List<Media> media) {
controller.onSendClicked(media, composeText.getTextTrimmed());
Util.cancelRunnableOnMain(progressTimer);
if (dialog != null) {
dialog.dismiss();
}
loader.setVisibility(View.GONE);
renderTimer.stop(TAG);
}
}.execute();

@ -109,7 +109,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
case GroupMessage: {
OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null, expiresInMillis, 0);
try {
mmsDatabase.insertMessageOutbox(reply, threadId, false, null, true);
message.setId(mmsDatabase.insertMessageOutbox(reply, threadId, false, null, true));
MessageSender.send(message, address);
} catch (MmsException e) {
Log.w(TAG, e);
@ -118,7 +118,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
}
case SecureMessage: {
OutgoingTextMessage reply = OutgoingTextMessage.from(message, recipient, expiresInMillis, expireStartedAt);
smsDatabase.insertMessageOutbox(threadId, reply, false, System.currentTimeMillis(), null, true);
message.setId(smsDatabase.insertMessageOutbox(threadId, reply, false, System.currentTimeMillis(), null, true));
MessageSender.send(message, address);
break;
}

@ -19,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import com.squareup.phrase.Phrase
import network.loki.messenger.BuildConfig
import network.loki.messenger.R
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
import org.thoughtcrime.securesms.onboarding.OnboardingBackPressAlertDialog
@ -76,7 +77,8 @@ internal fun MessageNotificationsScreen(
NotificationRadioButton(
R.string.notificationsFastMode,
R.string.notificationsFastModeDescription,
if(BuildConfig.FLAVOR == "huawei") R.string.notificationsFastModeDescriptionHuawei
else R.string.notificationsFastModeDescription,
modifier = Modifier.contentDescription(R.string.AccessibilityId_notificationsFastMode),
tag = R.string.recommended,
checked = state.pushEnabled,

@ -12,6 +12,7 @@ import android.text.TextUtils
import androidx.preference.ListPreference
import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.BuildConfig
import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.ApplicationContext
@ -38,6 +39,11 @@ class NotificationsPreferenceFragment : CorrectedPreferenceFragment() {
true
}
fcmPreference.summary = when (BuildConfig.FLAVOR) {
"huawei" -> getString(R.string.notificationsFastModeDescriptionHuawei)
else -> getString(R.string.notificationsFastModeDescription)
}
prefs.setNotificationRingtone(
NotificationChannels.getMessageRingtone(requireContext()).toString()
)

@ -292,13 +292,13 @@
android:clickable="true"
android:visibility="gone">
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp"
app:SpinKit_Color="@android:color/white" />
android:layout_gravity="center"
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</FrameLayout>

@ -162,13 +162,13 @@
android:visibility="gone"
android:alpha="0">
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_centerInParent="true"
app:SpinKit_Color="@android:color/white" />
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</RelativeLayout>

@ -34,13 +34,13 @@
android:orientation="vertical"
android:layout_centerInParent="true" />
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
<ProgressBar
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:SpinKit_Color="?android:textColorPrimary" />
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</RelativeLayout>

@ -127,13 +127,13 @@
android:background="#A4000000"
android:visibility="gone">
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="8dp"
app:SpinKit_Color="@android:color/white" />
android:layout_centerInParent="true"
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</RelativeLayout>

@ -74,18 +74,17 @@
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<com.github.ybq.android.spinkit.SpinKitView
<ProgressBar
android:id="@+id/remote_loading_view"
style="@style/SpinKitView.ThreeBounce"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:foregroundGravity="center"
android:visibility="gone"
app:SpinKit_Color="@color/core_white"
android:layout_width="@dimen/very_large_spacing"
android:layout_height="@dimen/large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
app:layout_constraintEnd_toEndOf="@+id/contactAvatar"
app:layout_constraintStart_toStartOf="@+id/contactAvatar"
app:layout_constraintTop_toBottomOf="@id/contactAvatar"
android:visibility="gone"
android:indeterminateTint="?android:textColorPrimary"
android:indeterminate="true"
tools:visibility="visible" />
<TextView
@ -146,13 +145,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<com.github.ybq.android.spinkit.SpinKitView
<ProgressBar
android:id="@+id/local_loading_view"
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:SpinKit_Color="?android:textColorPrimary"
android:layout_width="@dimen/large_spacing"
android:layout_height="@dimen/large_spacing"
android:layout_gravity="center"
android:indeterminateTint="?android:textColorPrimary"
android:indeterminate="true"
tools:visibility="visible"
android:visibility="gone" />

@ -31,13 +31,12 @@
tools:visibility="visible"
tools:listitem="@layout/view_user"/>
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
<ProgressBar
android:id="@+id/loader"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:SpinKit_Color="?android:textColorPrimary" />
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</FrameLayout>

@ -39,7 +39,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="@dimen/dialog_button_height"
android:orientation="horizontal">
<Button
@ -58,14 +58,17 @@
android:layout_weight="1"
android:text="@string/cancel" />
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Small.ThreeBounce"
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_height="@dimen/large_spacing"
android:layout_gravity="center_vertical"
android:layout_weight="1"
app:SpinKit_Color="?colorAccent"
android:visibility="gone" />
android:indeterminateTint="?colorAccent"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>

@ -163,13 +163,13 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="8dp"
app:SpinKit_Color="@android:color/white" />
android:layout_centerInParent="true"
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</RelativeLayout>

@ -39,19 +39,20 @@
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginTop="56dp"
android:visibility="gone"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/communityUrlEditText">
<com.github.ybq.android.spinkit.SpinKitView
<ProgressBar
android:id="@+id/defaultRoomsLoader"
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_width="@dimen/large_spacing"
android:layout_height="@dimen/large_spacing"
android:visibility="gone"
app:SpinKit_Color="?android:textColorPrimary" />
tools:visibility="visible"
android:layout_centerInParent="true"
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</RelativeLayout>

@ -77,13 +77,13 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="8dp"
app:SpinKit_Color="@android:color/white" />
android:layout_centerInParent="true"
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</RelativeLayout>

@ -11,15 +11,14 @@
android:layout_height="match_parent"
android:scrollbars="vertical"/>
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
<ProgressBar
android:id="@+id/loading_progress"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_gravity="center"
android:visibility="visible"
app:SpinKit_Color="?android:textColorPrimary" />
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
<TextView
android:id="@+id/no_results"

@ -168,4 +168,24 @@
</FrameLayout>
<FrameLayout
android:id="@+id/loader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#A4000000"
android:focusable="true"
android:clickable="true"
android:visibility="gone"
tools:visibility="visible">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_gravity="center"
android:indeterminateTint="?colorAccent"
android:indeterminate="true"/>
</FrameLayout>
</FrameLayout>

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/medium_spacing">
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:SpinKit_Color="?colorAccent" />
</RelativeLayout>

@ -61,12 +61,13 @@
</RelativeLayout>
<com.github.ybq.android.spinkit.SpinKitView
<ProgressBar
android:id="@+id/linkPreviewDraftLoader"
style="@style/SpinKitView.Large.ThreeBounce"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:SpinKit_Color="?android:textColorPrimary"
android:layout_centerInParent="true" />
android:layout_width="@dimen/large_spacing"
android:layout_height="@dimen/large_spacing"
android:layout_marginTop="8dp"
android:layout_centerInParent="true"
android:indeterminateTint="?android:textColorPrimary"
android:indeterminate="true"/>
</RelativeLayout>

@ -55,15 +55,17 @@
android:text="37 of 73"
android:textStyle="bold"/>
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.DoubleBounce"
<ProgressBar
android:id="@+id/searchProgressWheel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:padding="@dimen/medium_spacing"
android:layout_centerInParent="true"
android:indeterminateTint="?colorAccent"
android:indeterminate="true"
android:background="?backgroundSecondary"
app:SpinKit_Color="?android:textColorPrimary"
android:visibility="gone"/>
android:visibility="gone"
tools:visibility="visible"/>
</RelativeLayout>

@ -0,0 +1,44 @@
package org.session.libsignal.utilities
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Test
import org.session.libsignal.utilities.ByteArraySlice.Companion.view
class ByteArraySliceTest {
@Test
fun `view works`() {
val sliced = byteArrayOf(1, 2, 3, 4, 5).view(1..3)
assertEquals(listOf<Byte>(2, 3, 4), sliced.asList())
}
@Test
fun `re-view works`() {
val sliced = byteArrayOf(1, 2, 3, 4, 5).view(1..3)
val resliced = sliced.view(1..2)
assertEquals(listOf<Byte>(3, 4), resliced.asList())
}
@Test
fun `decodeToString works`() {
assertEquals(
"hel",
"hello, world".toByteArray().view(0..2).decodeToString()
)
}
@Test
fun `inputStream works`() {
assertArrayEquals(
"hello, world".toByteArray(),
"hello, world".toByteArray().inputStream().readBytes()
)
}
@Test
fun `able to view empty array`() {
val sliced = byteArrayOf().view()
assertEquals(0, sliced.len)
assertEquals(0, sliced.offset)
}
}

@ -15,6 +15,7 @@ import org.session.libsession.snode.utilities.await
import org.session.libsignal.utilities.HTTP
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ByteArraySlice
import org.session.libsignal.utilities.toHexString
import kotlin.time.Duration.Companion.milliseconds
@ -51,7 +52,7 @@ object FileServerApi {
return RequestBody.create("application/json".toMediaType(), parametersAsJSON)
}
private fun send(request: Request): Promise<ByteArray, Exception> {
private fun send(request: Request): Promise<ByteArraySlice, Exception> {
val url = server.toHttpUrlOrNull() ?: return Promise.ofFail(Error.InvalidURL)
val urlBuilder = HttpUrl.Builder()
.scheme(url.scheme)
@ -106,7 +107,7 @@ object FileServerApi {
}
}
fun download(file: String): Promise<ByteArray, Exception> {
fun download(file: String): Promise<ByteArraySlice, Exception> {
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
return send(request)
}

@ -19,8 +19,10 @@ import org.session.libsignal.exceptions.NonRetryableException
import org.session.libsignal.streams.AttachmentCipherInputStream
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ByteArraySlice.Companion.write
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job {
@ -139,8 +141,8 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
Log.d("AttachmentDownloadJob", "downloading open group attachment")
val url = attachment.url.toHttpUrlOrNull()!!
val fileID = url.pathSegments.last()
OpenGroupApi.download(fileID, openGroup.room, openGroup.server).await().let {
tempFile.writeBytes(it)
OpenGroupApi.download(fileID, openGroup.room, openGroup.server).await().let { data ->
FileOutputStream(tempFile).use { output -> output.write(data) }
}
}
Log.d("AttachmentDownloadJob", "getting input stream")

@ -44,7 +44,7 @@ class GroupAvatarDownloadJob(val server: String, val room: String, val imageId:
}
val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray())
storage.updateProfilePicture(groupId, bytes)
storage.updateProfilePicture(groupId, bytes.copyToBytes())
storage.updateTimestampUpdated(groupId, SnodeAPI.nowWithOffset)
delegate?.handleJobSucceeded(this, dispatcherName)
} catch (e: Exception) {

@ -21,6 +21,7 @@ import org.session.libsession.utilities.getGroup
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteMessage
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage
import org.session.libsignal.utilities.AccountId
import org.session.libsignal.utilities.Log
class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<String>) : Job {
@ -100,7 +101,11 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<
val groupName = configs.withGroupConfigs(sessionId) { it.groupInfo.getName() }
?: configs.getGroup(sessionId)?.name
val failures = results.filter { it.second.isFailure }
// Gather all the exceptions, while keeping track of the invitee account IDs
val failures = results.mapNotNull { (id, result) ->
result.exceptionOrNull()?.let { err -> id to err }
}
// if there are failed invites, display a message
// assume job "success" even if we fail, the state of invites is tracked outside of this job
if (failures.isNotEmpty()) {
@ -108,11 +113,20 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<
val storage = MessagingModuleConfiguration.shared.storage
val toaster = MessagingModuleConfiguration.shared.toaster
val (_, firstError) = failures.first()
// Add the rest of the exceptions as suppressed
for ((_, suppressed) in failures.asSequence().drop(1)) {
firstError.addSuppressed(suppressed)
}
Log.w("InviteContactsJob", "Failed to invite contacts", firstError)
GroupInviteException(
isPromotion = false,
inviteeAccountIds = failures.map { it.first },
groupName = groupName.orEmpty(),
underlying = failures.first().second.exceptionOrNull()!!,
underlying = firstError,
).format(MessagingModuleConfiguration.shared.context, storage).let {
withContext(Dispatchers.Main) {
toaster.toast(it, Toast.LENGTH_LONG)

@ -10,7 +10,6 @@ import com.goterl.lazysodium.interfaces.GenericHash
import com.goterl.lazysodium.interfaces.Sign
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.map
import okhttp3.Headers.Companion.toHeaders
@ -39,6 +38,7 @@ import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ByteArraySlice
import org.session.libsignal.utilities.removingIdPrefixIfNeeded
import org.whispersystems.curve25519.Curve25519
import java.util.concurrent.TimeUnit
@ -79,7 +79,7 @@ object OpenGroupApi {
object NoEd25519KeyPair : Error("Couldn't find ed25519 key pair.")
}
data class DefaultGroup(val id: String, val name: String, val image: ByteArray?) {
data class DefaultGroup(val id: String, val name: String, val image: ByteArraySlice?) {
val joinURL: String get() = "$defaultServer/$id?public_key=$defaultServerPublicKey"
}
@ -290,7 +290,7 @@ object OpenGroupApi {
return RequestBody.create("application/json".toMediaType(), parametersAsJSON)
}
private fun getResponseBody(request: Request): Promise<ByteArray, Exception> {
private fun getResponseBody(request: Request): Promise<ByteArraySlice, Exception> {
return send(request).map { response ->
response.body ?: throw Error.ParsingFailed
}
@ -417,7 +417,7 @@ object OpenGroupApi {
server: String,
roomID: String,
imageId: String
): Promise<ByteArray, Exception> {
): Promise<ByteArraySlice, Exception> {
val request = Request(
verb = GET,
room = roomID,
@ -445,7 +445,7 @@ object OpenGroupApi {
}
}
fun download(fileId: String, room: String, server: String): Promise<ByteArray, Exception> {
fun download(fileId: String, room: String, server: String): Promise<ByteArraySlice, Exception> {
val request = Request(
verb = GET,
room = room,

@ -430,7 +430,8 @@ object MessageSender {
// Result Handling
fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) {
if (message is VisibleMessage) MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(message.threadID!!, openGroupSentTimestamp)
val threadId by lazy { requireNotNull(message.threadID) { "threadID for the message is null" } }
if (message is VisibleMessage) MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(threadId, openGroupSentTimestamp)
val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()!!
val timestamp = message.sentTimestamp!!
@ -439,7 +440,7 @@ object MessageSender {
storage.getMessageIdInDatabase(timestamp, userPublicKey)?.let { (messageID, mms) ->
if (openGroupSentTimestamp != -1L && message is VisibleMessage) {
storage.addReceivedMessageTimestamp(openGroupSentTimestamp)
storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!)
storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, threadId)
message.sentTimestamp = openGroupSentTimestamp
}
@ -472,9 +473,9 @@ object MessageSender {
}
}
val encoded = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray())
val threadID = storage.getThreadId(Address.fromSerialized(encoded))
if (threadID != null && threadID >= 0) {
storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage())
val communityThreadID = storage.getThreadId(Address.fromSerialized(encoded))
if (communityThreadID != null && communityThreadID >= 0) {
storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, communityThreadID, !(message as VisibleMessage).isMediaMessage())
}
}
@ -488,8 +489,11 @@ object MessageSender {
// Fixed in: https://optf.atlassian.net/browse/SES-1567
if (messageIsAddressedToCommunity)
{
storage.markAsSentToCommunity(message.threadID!!, message.id!!)
storage.markUnidentifiedInCommunity(message.threadID!!, message.id!!)
val messageId = message.id
if (messageId != null) {
storage.markAsSentToCommunity(threadId, messageId)
storage.markUnidentifiedInCommunity(threadId, messageId)
}
}
else
{

@ -1,6 +1,5 @@
package org.session.libsession.snode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@ -27,15 +26,15 @@ import org.session.libsignal.crypto.secureRandom
import org.session.libsignal.crypto.secureRandomOrNull
import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Broadcaster
import org.session.libsignal.utilities.ForkInfo
import org.session.libsignal.utilities.HTTP
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ByteArraySlice
import org.session.libsignal.utilities.ByteArraySlice.Companion.view
import org.session.libsignal.utilities.Snode
import org.session.libsignal.utilities.recover
import org.session.libsignal.utilities.toHexString
import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.set
private typealias Path = List<Snode>
@ -629,7 +628,7 @@ object OnionRequestAPI {
)
return deferred.reject(exception)
}
deferred.resolve(OnionResponse(body, JsonUtil.toJson(body).toByteArray()))
deferred.resolve(OnionResponse(body, JsonUtil.toJson(body).toByteArray().view()))
}
else -> {
if (statusCode != 200) {
@ -640,7 +639,7 @@ object OnionRequestAPI {
)
return deferred.reject(exception)
}
deferred.resolve(OnionResponse(json, JsonUtil.toJson(json).toByteArray()))
deferred.resolve(OnionResponse(json, JsonUtil.toJson(json).toByteArray().view()))
}
}
} catch (exception: Exception) {
@ -652,17 +651,16 @@ object OnionRequestAPI {
}
}
private fun ByteArray.getBody(infoLength: Int, infoEndIndex: Int): ByteArray {
private fun ByteArray.getBody(infoLength: Int, infoEndIndex: Int): ByteArraySlice {
// If there is no data in the response, i.e. only `l123:jsone`, then just return the ResponseInfo
val infoLengthStringLength = infoLength.toString().length
if (size <= infoLength + infoLengthStringLength + 2/*l and e bytes*/) {
return byteArrayOf()
return ByteArraySlice.EMPTY
}
// Extract the response data as well
val dataSlice = slice(infoEndIndex + 1 until size - 1)
val dataSepIdx = dataSlice.indexOfFirst { byteArrayOf(it).contentEquals(":".toByteArray()) }
val responseBody = dataSlice.slice(dataSepIdx + 1 until dataSlice.size)
return responseBody.toByteArray()
val dataSlice = view(infoEndIndex + 1 until size - 1)
val dataSepIdx = dataSlice.asList().indexOfFirst { it.toInt() == ':'.code }
return dataSlice.view(dataSepIdx + 1 until dataSlice.len)
}
// endregion
@ -676,7 +674,7 @@ enum class Version(val value: String) {
data class OnionResponse(
val info: Map<*, *>,
val body: ByteArray? = null
val body: ByteArraySlice? = null
) {
val code: Int? get() = info["code"] as? Int
val message: String? get() = info["message"] as? String

@ -2,15 +2,14 @@ package org.session.libsession.utilities
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.session.libsession.messaging.file_server.FileServerApi
import org.session.libsession.snode.utilities.await
import org.session.libsignal.exceptions.NonRetryableException
import org.session.libsignal.utilities.HTTP
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ByteArraySlice.Companion.write
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
object DownloadUtilities {

@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import org.session.libsession.R
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.utilities.TextSecurePreferences.Companion.AUTOPLAY_AUDIO_MESSAGES
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
@ -214,7 +215,8 @@ interface TextSecurePreferences {
// This is a stop-gap solution for static access to shared preference.
internal lateinit var preferenceInstance: TextSecurePreferences
val preferenceInstance: TextSecurePreferences
get() = MessagingModuleConfiguration.shared.preferences
const val DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase"
const val LANGUAGE_PREF = "pref_language"
@ -994,11 +996,6 @@ interface TextSecurePreferences {
class AppTextSecurePreferences @Inject constructor(
@ApplicationContext private val context: Context
): TextSecurePreferences {
init {
// Should remove once all static access to the companion objects is removed
TextSecurePreferences.preferenceInstance = this
}
private val localNumberState = MutableStateFlow(getStringPreference(TextSecurePreferences.LOCAL_NUMBER_PREF, null))
override var migratedToGroupV2Config: Boolean

@ -47,6 +47,20 @@
<string name="adminTwoPromotedToAdmin"><b>{name}</b> and <b>{other_name}</b> were promoted to Admin.</string>
<string name="andMore">+{count}</string>
<string name="anonymous">Anonymous</string>
<string name="appIcon">App Icon</string>
<string name="appIconAndNameDescription">Alternate app icon and name is displayed on home screen and app drawer.</string>
<string name="appIconAndNameSelectionTitle">Icon and name</string>
<string name="appIconDescription">Alternate app icon is displayed on home screen and app library. App name will still appear as \'{app_name}\'.</string>
<string name="appIconEnableIcon">Use alternate app icon</string>
<string name="appIconEnableIconAndName">Use alternate app icon and name</string>
<string name="appIconSelect">Select alternate app icon</string>
<string name="appIconSelectionTitle">Icon</string>
<string name="appNameCalculator">Calculator</string>
<string name="appNameMeetingSE">MeetingSE</string>
<string name="appNameNews">News</string>
<string name="appNameNotes">Notes</string>
<string name="appNameStocks">Stocks</string>
<string name="appNameWeather">Weather</string>
<string name="appearanceAutoDarkMode">Auto dark-mode</string>
<string name="appearanceHideMenuBar">Hide Menu Bar</string>
<string name="appearanceLanguage">Language</string>
@ -262,6 +276,7 @@
<string name="copied">Copied</string>
<string name="copy">Copy</string>
<string name="create">Create</string>
<string name="creatingCall">Creating Call</string>
<string name="cut">Cut</string>
<string name="databaseErrorGeneric">A database error occurred.\n\nExport your application logs to share for troubleshooting. If this is unsuccessful, reinstall {app_name} and restore your account.\n\nWarning: This will result in loss of all messages, attachments, and account data older than two weeks.</string>
<string name="databaseErrorTimeout">We\'ve noticed {app_name} is taking a long time to start.\n\nYou can continue to wait, export your device logs to share for troubleshooting, or try restarting {app_name}.</string>
@ -400,6 +415,7 @@
<string name="errorCopyAndQuit">Copy Error and Quit</string>
<string name="errorDatabase">Database Error</string>
<string name="errorUnknown">An unknown error occurred.</string>
<string name="failedToDownload">Failed to download</string>
<string name="failures">Failures</string>
<string name="file">File</string>
<string name="files">Files</string>
@ -496,6 +512,7 @@
<string name="groupSetDisplayPicture">Set Group Display Picture</string>
<string name="groupUnknown">Unknown Group</string>
<string name="groupUpdated">Group updated</string>
<string name="handlingConnectionCandidates">Handling Connection Candidates</string>
<string name="helpFAQ">FAQ</string>
<string name="helpHelpUsTranslateSession">Help us translate {app_name}</string>
<string name="helpReportABug">Report a bug</string>
@ -510,6 +527,7 @@
<string name="hideMenuBarDescription">Toggle system menu bar visibility</string>
<string name="hideOthers">Hide Others</string>
<string name="image">Image</string>
<string name="images">images</string>
<string name="incognitoKeyboard">Incognito Keyboard</string>
<string name="incognitoKeyboardDescription">Request incognito mode if available. Depending on the keyboard you are using, your keyboard may ignore this request.</string>
<string name="info">Info</string>
@ -655,6 +673,7 @@
<string name="notificationsContentShowNoNameOrContent">No Name or Content</string>
<string name="notificationsFastMode">Fast Mode</string>
<string name="notificationsFastModeDescription">You\'ll be notified of new messages reliably and immediately using Google\'s notification Servers.</string>
<string name="notificationsFastModeDescriptionHuawei">You\'ll be notified of new messages reliably and immediately using Huaweis notification servers.</string>
<string name="notificationsFastModeDescriptionIos">You\'ll be notified of new messages reliably and immediately using Apple\'s notification Servers.</string>
<string name="notificationsGoToDevice">Go to device notification settings</string>
<string name="notificationsHeaderAllMessages">Notifications - All</string>
@ -728,20 +747,30 @@
<string name="passwordSet">Set Password</string>
<string name="passwordSetDescription">Your password has been set. Please keep it safe.</string>
<string name="paste">Paste</string>
<string name="permissionChange">Permission Change</string>
<string name="permissionMusicAudioDenied">{app_name} needs music and audio access in order to send files, music and audio, but it has been permanently denied. Tap Settings → Permissions, and turn \"Music and audio\" on.</string>
<string name="permissionsAppleMusic">{app_name} needs to use Apple Music to play media attachments.</string>
<string name="permissionsAutoUpdate">Auto Update</string>
<string name="permissionsAutoUpdateDescription">Automatically check for updates on startup.</string>
<string name="permissionsCameraAccessRequiredCallsIos">Camera access is required to make video calls. Toggle the \"Camera\" permission in Settings to continue.</string>
<string name="permissionsCameraDenied">{app_name} needs camera access to take photos and videos, but it has been permanently denied. Tap Settings → Permissions, and turn \"Camera\" on.</string>
<string name="permissionsCameraDescriptionIos">Allow access to camera for video calls.</string>
<string name="permissionsFaceId">The screen lock feature on {app_name} uses Face ID.</string>
<string name="permissionsKeepInSystemTray">Keep in System Tray</string>
<string name="permissionsKeepInSystemTrayDescription">{app_name} continues running in the background when you close the window</string>
<string name="permissionsLibrary">{app_name} needs photo library access to continue. You can enable access in the iOS settings.</string>
<string name="permissionsLocalNetworkAccessRequiredCallsIos">Local Network access is required to facilitate calls. Toggle the \"Local Network\" permission in Settings to continue.</string>
<string name="permissionsLocalNetworkAccessRequiredIos">{app_name} needs access to local network to make voice and video calls.</string>
<string name="permissionsLocalNetworkChangeDescriptionIos">Local Network access is currently enabled. To disable it, toggle the “Local Network” permission in Settings.</string>
<string name="permissionsLocalNetworkDescriptionIos">Allow access to local network to facilitate voice and video calls.</string>
<string name="permissionsLocalNetworkIos">Local Network</string>
<string name="permissionsMicrophone">Microphone</string>
<string name="permissionsMicrophoneAccessRequired">{app_name} needs microphone access to make calls and send audio messages, but it has been permanently denied. Tap settings → Permissions, and turn \"Microphone\" on.</string>
<string name="permissionsMicrophoneAccessRequiredCallsIos">Microphone access is required to make calls and record audio messages. Toggle the \"Microphone\" permission in Settings to continue.</string>
<string name="permissionsMicrophoneAccessRequiredDesktop">You can enable microphone access in {app_name}\'s privacy settings</string>
<string name="permissionsMicrophoneAccessRequiredIos">{app_name} needs microphone access to make calls and record audio messages.</string>
<string name="permissionsMicrophoneDescription">Allow access to microphone.</string>
<string name="permissionsMicrophoneDescriptionIos">Allow access to microphone for voice calls and audio messages.</string>
<string name="permissionsMusicAudio">{app_name} needs music and audio access in order to send files, music and audio.</string>
<string name="permissionsRequired">Permission Required</string>
<string name="permissionsStorageDenied">{app_name} needs photo library access so you can send photos and videos, but it has been permanently denied. Tap Settings → Permissions, and turn \"Photos and videos\" on.</string>
@ -781,12 +810,15 @@
<string name="readReceipts">Read Receipts</string>
<string name="readReceiptsDescription">Show read receipts for all messages you send and receive.</string>
<string name="received">Received:</string>
<string name="receivedAnswer">Received Answer</string>
<string name="receivingCallOffer">Receiving Call Offer</string>
<string name="receivingPreOffer">Receiving Pre Offer</string>
<string name="recommended">Recommended</string>
<string name="recoveryPasswordBannerDescription">Save your recovery password to make sure you don\'t lose access to your account.</string>
<string name="recoveryPasswordBannerTitle">Save your recovery password</string>
<string name="recoveryPasswordDescription">Use your recovery password to load your account on new devices.\n\nYour account cannot be recovered without your recovery password. Make sure it\'s stored somewhere safe and secure — and don\'t share it with anyone.</string>
<string name="recoveryPasswordEnter">Enter your recovery password</string>
<string name="recoveryPasswordErrorLoad">An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session\'s Help Desk to help resolve this issue.</string>
<string name="recoveryPasswordErrorLoad">An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file through the {app_name} Help Desk to help resolve this issue.</string>
<string name="recoveryPasswordErrorMessageGeneric">Please check your recovery password and try again.</string>
<string name="recoveryPasswordErrorMessageIncorrect">Some of the words in your Recovery Password are incorrect. Please check and try again.</string>
<string name="recoveryPasswordErrorMessageShort">The Recovery Password you entered is not long enough. Please check and try again.</string>
@ -835,6 +867,8 @@
<string name="selectAll">Select All</string>
<string name="send">Send</string>
<string name="sending">Sending</string>
<string name="sendingCallOffer">Sending Call Offer</string>
<string name="sendingConnectionCandidates">Sending Connection Candidates</string>
<string name="sent">Sent:</string>
<string name="sessionAppearance">Appearance</string>
<string name="sessionClearData">Clear Data</string>
@ -861,6 +895,7 @@
<string name="stickers">Stickers</string>
<string name="supportGoTo">Go to Support Page</string>
<string name="systemInformationDesktop">System Information: {information}</string>
<string name="tapToRetry">Tap to retry</string>
<string name="theContinue">Continue</string>
<string name="theDefault">Default</string>
<string name="theError">Error</string>
@ -875,7 +910,7 @@
<string name="updateError">Cannot Update</string>
<string name="updateErrorDescription">{app_name} failed to update. Please go to {session_download_url} and install the new version manually, then contact our Help Center to let us know about this problem.</string>
<string name="updateNewVersion">A new version of {app_name} is available, tap to update</string>
<string name="updateNewVersionDescription">A new version of {app_name} is available.</string>
<string name="updateNewVersionDescription">A new version ({version}) of {app_name} is available.</string>
<string name="updateReleaseNotes">Go to Release Notes</string>
<string name="updateSession">{app_name} Update</string>
<string name="updateVersion">Version {version}</string>

@ -0,0 +1,94 @@
package org.session.libsignal.utilities
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.io.OutputStream
/**
* A view of a byte array with a range. This is useful for avoiding copying data when slicing a byte array.
*/
class ByteArraySlice private constructor(
val data: ByteArray,
val offset: Int,
val len: Int,
) {
init {
check(offset in 0..data.size) { "Offset $offset is not within [0..${data.size}]" }
check(len in 0..data.size) { "Length $len is not within [0..${data.size}]" }
}
fun view(range: IntRange): ByteArraySlice {
val newOffset = offset + range.first
val newLength = range.last + 1 - range.first
return ByteArraySlice(
data = data,
offset = newOffset,
len = newLength
)
}
fun copyToBytes(): ByteArray {
return data.copyOfRange(offset, offset + len)
}
operator fun get(index: Int): Byte {
return data[offset + index]
}
fun asList(): List<Byte> {
return object : AbstractList<Byte>() {
override val size: Int
get() = this@ByteArraySlice.len
override fun get(index: Int) = this@ByteArraySlice[index]
}
}
fun decodeToString(): String {
return data.decodeToString(offset, offset + len)
}
fun inputStream(): InputStream {
return ByteArrayInputStream(data, offset, len)
}
fun isEmpty(): Boolean = len == 0
fun isNotEmpty(): Boolean = len != 0
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ByteArraySlice) return false
if (offset != other.offset) return false
if (len != other.len) return false
if (!data.contentEquals(other.data)) return false
return true
}
override fun hashCode(): Int {
var result = offset
result = 31 * result + len
result = 31 * result + data.contentHashCode()
return result
}
companion object {
val EMPTY = ByteArraySlice(byteArrayOf(), 0, 0)
/**
* Create a view of a byte array
*/
fun ByteArray.view(range: IntRange = indices): ByteArraySlice {
return ByteArraySlice(
data = this,
offset = range.first,
len = range.last + 1 - range.first
)
}
fun OutputStream.write(view: ByteArraySlice) {
write(view.data, view.offset, view.len)
}
}
}

@ -31,6 +31,10 @@ public class JsonUtil {
return fromJson(new String(serialized), clazz);
}
public static <T> T fromJson(ByteArraySlice serialized, Class<T> clazz) throws IOException {
return objectMapper.readValue(serialized.getData(), serialized.getOffset(), serialized.getLen(), clazz);
}
public static <T> T fromJson(String serialized, TypeReference<T> typeReference) throws IOException {
return objectMapper.readValue(serialized, typeReference);
}

Loading…
Cancel
Save