diff --git a/app/build.gradle b/app/build.gradle
index f705480ae5..3bbd411593 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -13,8 +13,8 @@ configurations.forEach {
it.exclude module: "commons-logging"
}
-def canonicalVersionCode = 389
-def canonicalVersionName = "1.20.7"
+def canonicalVersionCode = 390
+def canonicalVersionName = "1.20.8"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
@@ -299,7 +299,7 @@ dependencies {
implementation 'androidx.media3:media3-ui:1.4.0'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
implementation 'org.signal:aesgcmprovider:0.0.3'
- implementation 'io.github.webrtc-sdk:android:125.6422.04'
+ implementation 'io.github.webrtc-sdk:android:125.6422.06.1'
implementation "me.leolin:ShortcutBadger:1.1.16"
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eac4ef3300..de523ceff1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,38 +29,49 @@
android:name="android.hardware.touchscreen"
android:required="false" />
-
+
+
+
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
-
-
-
+
-
-
-
-
-
-
-
+
+
@@ -304,8 +315,10 @@
-
0) {
+ } else if (notificationState.notificationCount > 0) {
sendSingleThreadNotification(context, notificationState, playNotificationAudio, false)
} else {
cancelActiveNotifications(context)
}
cancelOrphanedNotifications(context, notificationState)
- updateBadge(context, notificationState.messageCount)
+ updateBadge(context, notificationState.notificationCount)
if (playNotificationAudio) {
scheduleReminder(context, reminderCount)
@@ -267,7 +262,7 @@ class DefaultMessageNotifier : MessageNotifier {
val builder = SingleRecipientNotificationBuilder(context, getNotificationPrivacy(context))
val notifications = notificationState.notifications
- val recipient = notifications[0].recipient
+ val messageOriginator = notifications[0].recipient
val notificationId = (SUMMARY_NOTIFICATION_ID + (if (bundled) notifications[0].threadId else 0)).toInt()
val messageIdTag = notifications[0].timestamp.toString()
@@ -285,12 +280,12 @@ class DefaultMessageNotifier : MessageNotifier {
builder.putStringExtra(LATEST_MESSAGE_ID_TAG, messageIdTag)
- val text = notifications[0].text
+ val notificationText = notifications[0].text
builder.setThread(notifications[0].recipient)
- builder.setMessageCount(notificationState.messageCount)
+ builder.setMessageCount(notificationState.notificationCount)
- val builderCS = text ?: ""
+ val builderCS = notificationText ?: ""
val ss = highlightMentions(
builderCS,
false,
@@ -301,7 +296,7 @@ class DefaultMessageNotifier : MessageNotifier {
)
builder.setPrimaryMessageBody(
- recipient,
+ messageOriginator,
notifications[0].individualRecipient,
ss,
notifications[0].slideDeck
@@ -313,12 +308,12 @@ class DefaultMessageNotifier : MessageNotifier {
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
builder.setAutoCancel(true)
- val replyMethod = ReplyMethod.forRecipient(context, recipient)
+ val replyMethod = ReplyMethod.forRecipient(context, messageOriginator)
- val canReply = canUserReplyToNotification(recipient)
+ val canReply = canUserReplyToNotification(messageOriginator)
- val quickReplyIntent = if (canReply) notificationState.getQuickReplyIntent(context, recipient) else null
- val remoteReplyIntent = if (canReply) notificationState.getRemoteReplyIntent(context, recipient, replyMethod) else null
+ val quickReplyIntent = if (canReply) notificationState.getQuickReplyIntent(context, messageOriginator) else null
+ val remoteReplyIntent = if (canReply) notificationState.getRemoteReplyIntent(context, messageOriginator, replyMethod) else null
builder.addActions(
notificationState.getMarkAsReadIntent(context, notificationId),
@@ -329,14 +324,13 @@ class DefaultMessageNotifier : MessageNotifier {
if (canReply) {
builder.addAndroidAutoAction(
- notificationState.getAndroidAutoReplyIntent(context, recipient),
+ notificationState.getAndroidAutoReplyIntent(context, messageOriginator),
notificationState.getAndroidAutoHeardIntent(context, notificationId),
notifications[0].timestamp
)
}
val iterator: ListIterator = notifications.listIterator(notifications.size)
-
while (iterator.hasPrevious()) {
val item = iterator.previous()
builder.addMessageBody(item.recipient, item.individualRecipient, item.text)
@@ -368,6 +362,7 @@ class DefaultMessageNotifier : MessageNotifier {
// for ActivityCompat#requestPermissions for more details.
return
}
+
NotificationManagerCompat.from(context).notify(notificationId, notification)
Log.i(TAG, "Posted notification. $notification")
}
@@ -383,7 +378,7 @@ class DefaultMessageNotifier : MessageNotifier {
val builder = MultipleRecipientNotificationBuilder(context, getNotificationPrivacy(context))
val notifications = notificationState.notifications
- builder.setMessageCount(notificationState.messageCount, notificationState.threadCount)
+ builder.setMessageCount(notificationState.notificationCount, notificationState.threadCount)
builder.setMostRecentSender(notifications[0].individualRecipient, notifications[0].recipient)
builder.setGroup(NOTIFICATION_GROUP)
builder.setDeleteIntent(notificationState.getDeleteIntent(context))
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java
index 7bea5594ed..bc85ff0874 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java
@@ -3,20 +3,18 @@ package org.thoughtcrime.securesms.notifications;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
-import org.session.libsession.utilities.recipients.Recipient;
-import org.session.libsession.utilities.recipients.Recipient.VibrateState;
-import org.session.libsignal.utilities.Log;
-import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
-
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import org.session.libsession.utilities.recipients.Recipient.VibrateState;
+import org.session.libsession.utilities.recipients.Recipient;
+import org.session.libsignal.utilities.Log;
+import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
public class NotificationState {
@@ -36,68 +34,47 @@ public class NotificationState {
}
public void addNotification(NotificationItem item) {
+ // Add this new notification at the beginning of the list
notifications.addFirst(item);
- if (threads.contains(item.getThreadId())) {
- threads.remove(item.getThreadId());
- }
-
+ // Put a notification at the front by removing it then re-adding it?
+ threads.remove(item.getThreadId());
threads.add(item.getThreadId());
+
notificationCount++;
}
public @Nullable Uri getRingtone(@NonNull Context context) {
if (!notifications.isEmpty()) {
Recipient recipient = notifications.getFirst().getRecipient();
-
- if (recipient != null) {
- return NotificationChannels.getMessageRingtone(context, recipient);
- }
+ return NotificationChannels.getMessageRingtone(context, recipient);
}
- return null;
+ return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
}
public VibrateState getVibrate() {
if (!notifications.isEmpty()) {
Recipient recipient = notifications.getFirst().getRecipient();
-
- if (recipient != null) {
- return recipient.resolve().getMessageVibrate();
- }
+ return recipient.resolve().getMessageVibrate();
}
-
return VibrateState.DEFAULT;
}
- public boolean hasMultipleThreads() {
- return threads.size() > 1;
- }
-
- public LinkedHashSet getThreads() {
- return threads;
- }
-
- public int getThreadCount() {
- return threads.size();
- }
-
- public int getMessageCount() {
- return notificationCount;
- }
-
- public List getNotifications() {
- return notifications;
- }
+ public boolean hasMultipleThreads() { return threads.size() > 1; }
+ public LinkedHashSet getThreads() { return threads; }
+ public int getThreadCount() { return threads.size(); }
+ public int getNotificationCount() { return notificationCount; }
+ public List getNotifications() { return notifications; }
public List getNotificationsForThread(long threadId) {
- LinkedList list = new LinkedList<>();
+ LinkedList notificationsInThread = new LinkedList<>();
for (NotificationItem item : notifications) {
- if (item.getThreadId() == threadId) list.addFirst(item);
+ if (item.getThreadId() == threadId) notificationsInThread.addFirst(item);
}
- return list;
+ return notificationsInThread;
}
public PendingIntent getMarkAsReadIntent(Context context, int notificationId) {
@@ -111,7 +88,7 @@ public class NotificationState {
Intent intent = new Intent(MarkReadReceiver.CLEAR_ACTION);
intent.setClass(context, MarkReadReceiver.class);
- intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
+ intent.setData((Uri.parse("custom://" + System.currentTimeMillis())));
intent.putExtra(MarkReadReceiver.THREAD_IDS_EXTRA, threadArray);
intent.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, notificationId);
@@ -171,7 +148,7 @@ public class NotificationState {
Intent intent = new Intent(AndroidAutoHeardReceiver.HEARD_ACTION);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.setClass(context, AndroidAutoHeardReceiver.class);
- intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
+ intent.setData((Uri.parse("custom://" + System.currentTimeMillis())));
intent.putExtra(AndroidAutoHeardReceiver.THREAD_IDS_EXTRA, threadArray);
intent.putExtra(AndroidAutoHeardReceiver.NOTIFICATION_ID_EXTRA, notificationId);
intent.setPackage(context.getPackageName());
@@ -223,6 +200,4 @@ public class NotificationState {
return PendingIntent.getBroadcast(context, 0, intent, intentFlags);
}
-
-
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt
deleted file mode 100644
index d094644c07..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.thoughtcrime.securesms.notifications
-
-interface PushManager {
- fun refresh(force: Boolean)
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java
index 9d95ac5d49..9f31c617e5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java
@@ -248,8 +248,6 @@ public class KeyCachingService extends Service {
.put(APP_NAME_KEY, c.getString(R.string.app_name))
.format().toString();
builder.setContentTitle(unlockedTxt);
-
- builder.setContentText(getString(R.string.lockAppUnlock));
builder.setSmallIcon(R.drawable.icon_cached);
builder.setWhen(0);
builder.setPriority(Notification.PRIORITY_MIN);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt
index 78f1a55470..db98a00c8e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
+import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.IntentFilter
import android.content.pm.PackageManager
@@ -19,11 +19,18 @@ import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import dagger.hilt.android.AndroidEntryPoint
+import java.util.UUID
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.FutureTaskListener
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.notifications.BackgroundPollWorker
import org.thoughtcrime.securesms.util.CallNotificationBuilder
@@ -32,6 +39,7 @@ import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_IN
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_PRE_OFFER
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_RINGING
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING
+import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.WEBRTC_NOTIFICATION
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
import org.thoughtcrime.securesms.webrtc.CallManager
import org.thoughtcrime.securesms.webrtc.CallViewModel
@@ -44,6 +52,7 @@ import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager
import org.thoughtcrime.securesms.webrtc.WiredHeadsetStateReceiver
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
import org.thoughtcrime.securesms.webrtc.data.Event
+import org.thoughtcrime.securesms.webrtc.data.State as CallState
import org.thoughtcrime.securesms.webrtc.locks.LockManager
import org.webrtc.DataChannel
import org.webrtc.IceCandidate
@@ -54,13 +63,6 @@ import org.webrtc.PeerConnection.IceConnectionState.DISCONNECTED
import org.webrtc.PeerConnection.IceConnectionState.FAILED
import org.webrtc.RtpReceiver
import org.webrtc.SessionDescription
-import java.util.UUID
-import java.util.concurrent.ExecutionException
-import java.util.concurrent.Executors
-import java.util.concurrent.ScheduledFuture
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-import org.thoughtcrime.securesms.webrtc.data.State as CallState
@AndroidEntryPoint
class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
@@ -631,6 +633,20 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
}
}
+ /**
+ * Handles remote ICE candidates received from a signaling server.
+ *
+ * This function is called when a new ICE candidate is received for a specific call.
+ * It extracts the candidate information from the intent, creates IceCandidate objects,
+ * and passes them to the CallManager to be added to the PeerConnection.
+ *
+ * @param intent The intent containing the remote ICE candidate information.
+ * The intent should contain the following extras:
+ * - EXTRA_CALL_ID: The ID of the call.
+ * - EXTRA_ICE_SDP_MID: An array of SDP media stream identification strings.
+ * - EXTRA_ICE_SDP_LINE_INDEX: An array of SDP media line indexes.
+ * - EXTRA_ICE_SDP: An array of SDP candidate strings.
+ */
private fun handleRemoteIceCandidate(intent: Intent) {
val callId = getCallId(intent)
val sdpMids = intent.getStringArrayExtra(EXTRA_ICE_SDP_MID) ?: return
@@ -724,24 +740,40 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener {
}
}
+
+
+ // Over the course of setting up a phone call this method is called multiple times with `types`
+ // of PRE_OFFER -> RING_INCOMING -> ICE_MESSAGE
private fun setCallInProgressNotification(type: Int, recipient: Recipient?) {
- try {
- ServiceCompat.startForeground(
- this,
- CallNotificationBuilder.WEBRTC_NOTIFICATION,
- CallNotificationBuilder.getCallInProgressNotification(this, type, recipient),
- if (Build.VERSION.SDK_INT >= 30) ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE else 0
- )
- } catch (e: IllegalStateException) {
- Log.e(TAG, "Failed to setCallInProgressNotification as a foreground service for type: ${type}, trying to update instead", e)
+ // Wake the device if needed
+ (applicationContext as ApplicationContext).wakeUpDeviceAndDismissKeyguardIfRequired()
+
+ // If notifications are enabled we'll try and start a foreground service to show the notification
+ var failedToStartForegroundService = false
+ if (CallNotificationBuilder.areNotificationsEnabled(this)) {
+ try {
+ ServiceCompat.startForeground(
+ this,
+ WEBRTC_NOTIFICATION,
+ CallNotificationBuilder.getCallInProgressNotification(this, type, recipient),
+ if (Build.VERSION.SDK_INT >= 30) ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL else 0
+ )
+ return
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, "Failed to setCallInProgressNotification as a foreground service for type: ${type}, trying to update instead", e)
+ failedToStartForegroundService = true
+ }
+ } else {
+ // Notifications are NOT enabled! Skipped attempt at startForeground and going straight to fullscreen intent attempt!
}
- if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) {
+ if ((type == TYPE_INCOMING_PRE_OFFER || type == TYPE_INCOMING_RINGING) && failedToStartForegroundService) {
// Start an intent for the fullscreen call activity
val foregroundIntent = Intent(this, WebRtcCallActivity::class.java)
- .setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_BROUGHT_TO_FRONT or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+ .setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP)
.setAction(WebRtcCallActivity.ACTION_FULL_SCREEN_INTENT)
startActivity(foregroundIntent)
+ return
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt
index 4ffe12d006..30d28ad6cb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/CallNotificationBuilder.kt
@@ -25,11 +25,11 @@ class CallNotificationBuilder {
companion object {
const val WEBRTC_NOTIFICATION = 313388
- const val TYPE_INCOMING_RINGING = 1
- const val TYPE_OUTGOING_RINGING = 2
- const val TYPE_ESTABLISHED = 3
+ const val TYPE_INCOMING_RINGING = 1
+ const val TYPE_OUTGOING_RINGING = 2
+ const val TYPE_ESTABLISHED = 3
const val TYPE_INCOMING_CONNECTING = 4
- const val TYPE_INCOMING_PRE_OFFER = 5
+ const val TYPE_INCOMING_PRE_OFFER = 5
@JvmStatic
fun areNotificationsEnabled(context: Context): Boolean {
@@ -37,31 +37,6 @@ class CallNotificationBuilder {
return notificationManager.areNotificationsEnabled()
}
- @JvmStatic
- fun getFirstCallNotification(context: Context, callerName: String): Notification {
- val contentIntent = Intent(context, SettingsActivity::class.java)
-
- val pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
-
- val titleTxt = context.getSubbedString(R.string.callsMissedCallFrom, NAME_KEY to callerName)
- val bodyTxt = context.getSubbedCharSequence(
- R.string.callsYouMissedCallPermissions,
- NAME_KEY to callerName
- )
-
- val builder = NotificationCompat.Builder(context, NotificationChannels.CALLS)
- .setSound(null)
- .setSmallIcon(R.drawable.ic_baseline_call_24)
- .setContentIntent(pendingIntent)
- .setPriority(NotificationCompat.PRIORITY_HIGH)
- .setContentTitle(titleTxt)
- .setContentText(bodyTxt)
- .setStyle(NotificationCompat.BigTextStyle().bigText(bodyTxt))
- .setAutoCancel(true)
-
- return builder.build()
- }
-
@JvmStatic
fun getCallInProgressNotification(context: Context, type: Int, recipient: Recipient?): Notification {
val contentIntent = Intent(context, WebRtcCallActivity::class.java)
@@ -98,9 +73,7 @@ class CallNotificationBuilder {
R.string.decline
))
// If notifications aren't enabled, we will trigger the intent from WebRtcCallService
- builder.setFullScreenIntent(getFullScreenPendingIntent(
- context
- ), true)
+ builder.setFullScreenIntent(getFullScreenPendingIntent(context), true)
builder.addAction(getActivityNotificationAction(
context,
if (type == TYPE_INCOMING_PRE_OFFER) WebRtcCallActivity.ACTION_PRE_OFFER else WebRtcCallActivity.ACTION_ANSWER,
@@ -143,9 +116,10 @@ class CallNotificationBuilder {
private fun getFullScreenPendingIntent(context: Context): PendingIntent {
val intent = Intent(context, WebRtcCallActivity::class.java)
- .setFlags(FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+ // When launching the call activity do NOT keep it in the history when finished, as it does not pass through CALL_DISCONNECTED
+ // if the call was denied outright, and without this the "dead" activity will sit around in the history when the device is unlocked.
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
.setAction(WebRtcCallActivity.ACTION_FULL_SCREEN_INTENT)
-
return PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt
index c99c063d8c..60899a9c17 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt
@@ -1,9 +1,10 @@
package org.thoughtcrime.securesms.webrtc
import android.Manifest
-import android.app.NotificationManager
+import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
+import android.os.PowerManager
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
@@ -16,6 +17,7 @@ import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.utilities.WebRtcUtils
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address
+import org.session.libsession.utilities.NonTranslatableStringConstants
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ANSWER
@@ -25,27 +27,34 @@ import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.OFFER
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PROVISIONAL_ANSWER
import org.session.libsignal.utilities.Log
+import org.session.libsignal.utilities.ThreadUtils
+import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.service.WebRtcCallService
-import org.thoughtcrime.securesms.util.CallNotificationBuilder
import org.webrtc.IceCandidate
-
class CallMessageProcessor(private val context: Context, private val textSecurePreferences: TextSecurePreferences, lifecycle: Lifecycle, private val storage: StorageProtocol) {
companion object {
+ private const val TAG = "CallMessageProcessor"
private const val VERY_EXPIRED_TIME = 15 * 60 * 1000L
- fun safeStartService(context: Context, intent: Intent) {
- // If the foreground service crashes then it's possible for one of these intents to
- // be started in the background (in which case 'startService' will throw a
- // 'BackgroundServiceStartNotAllowedException' exception) so catch that case and try
- // to re-start the service in the foreground
- try { context.startService(intent) }
- catch(e: Exception) {
- try { ContextCompat.startForegroundService(context, intent) }
- catch (e2: Exception) {
- Log.e("Loki", "Unable to start CallMessage intent: ${e2.message}")
+ fun safeStartForegroundService(context: Context, intent: Intent) {
+ // Wake up the device (if required) before attempting to start any services - otherwise on Android 12 and above we get
+ // a BackgroundServiceStartNotAllowedException such as:
+ // Unable to start CallMessage intent: startForegroundService() not allowed due to mAllowStartForeground false:
+ // service network.loki.messenger/org.thoughtcrime.securesms.service.WebRtcCallService
+ (context as ApplicationContext).wakeUpDeviceAndDismissKeyguardIfRequired()
+
+ // Attempt to start the call service..
+ try {
+ context.startService(intent)
+ } catch (e: Exception) {
+ Log.e("Loki", "Unable to start service: ${e.message}", e)
+ try {
+ ContextCompat.startForegroundService(context, intent)
+ } catch (e2: Exception) {
+ Log.e(TAG, "Unable to start CallMessage intent: ${e2.message}", e2)
}
}
}
@@ -61,8 +70,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
Log.i("Loki", "Contact is approved?: $approvedContact")
if (!approvedContact && storage.getUserPublicKey() != sender) continue
- // if the user has not enabled voice/video calls
- // or if the user has not granted audio/microphone permissions
+ // If the user has not enabled voice/video calls or if the user has not granted audio/microphone permissions
if (
!textSecurePreferences.isCallNotificationsEnabled() ||
!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO)
@@ -101,21 +109,20 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
private fun incomingHangup(callMessage: CallMessage) {
val callId = callMessage.callId ?: return
val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId)
- safeStartService(context, hangupIntent)
+ safeStartForegroundService(context, hangupIntent)
}
private fun incomingAnswer(callMessage: CallMessage) {
- val recipientAddress = callMessage.sender ?: return
- val callId = callMessage.callId ?: return
- val sdp = callMessage.sdps.firstOrNull() ?: return
+ val recipientAddress = callMessage.sender ?: return Log.w(TAG, "Cannot answer incoming call without sender")
+ val callId = callMessage.callId ?: return Log.w(TAG, "Cannot answer incoming call without callId" )
+ val sdp = callMessage.sdps.firstOrNull() ?: return Log.w(TAG, "Cannot answer incoming call without sdp")
val answerIntent = WebRtcCallService.incomingAnswer(
context = context,
address = Address.fromSerialized(recipientAddress),
sdp = sdp,
callId = callId
)
-
- safeStartService(context, answerIntent)
+ safeStartForegroundService(context, answerIntent)
}
private fun handleIceCandidates(callMessage: CallMessage) {
@@ -131,7 +138,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
callId = callId,
address = Address.fromSerialized(sender)
)
- safeStartService(context, iceIntent)
+ safeStartForegroundService(context, iceIntent)
}
private fun incomingPreOffer(callMessage: CallMessage) {
@@ -144,7 +151,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
callId = callId,
callTime = callMessage.sentTimestamp!!
)
- safeStartService(context, incomingIntent)
+ safeStartForegroundService(context, incomingIntent)
}
private fun incomingCall(callMessage: CallMessage) {
@@ -158,7 +165,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
callId = callId,
callTime = callMessage.sentTimestamp!!
)
- safeStartService(context, incomingIntent)
+ safeStartForegroundService(context, incomingIntent)
}
private fun CallMessage.iceCandidates(): List {
diff --git a/libsession-util/libsession-util b/libsession-util/libsession-util
index 0193c36e0d..43b1c6c341 160000
--- a/libsession-util/libsession-util
+++ b/libsession-util/libsession-util
@@ -1 +1 @@
-Subproject commit 0193c36e0dad461385d6407a00f33b7314e6d740
+Subproject commit 43b1c6c341ee8739a8678c631d0713136dbfd05f
diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt
index 41db054d59..4ef861603b 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt
@@ -9,6 +9,10 @@ import network.loki.messenger.libsession_util.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig
import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile
+import java.util.Timer
+import java.util.TimerTask
+import kotlin.time.Duration.Companion.days
+import kotlinx.coroutines.GlobalScope
import nl.komponents.kovenant.Deferred
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
@@ -28,9 +32,6 @@ import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace
import org.session.libsignal.utilities.Snode
import org.session.libsignal.utilities.Util.SECURE_RANDOM
-import java.util.Timer
-import java.util.TimerTask
-import kotlin.time.Duration.Companion.days
private const val TAG = "Poller"
@@ -44,8 +45,9 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
// region Settings
companion object {
- private const val retryInterval: Long = 2 * 1000
- private const val maxInterval: Long = 15 * 1000
+ private const val RETRY_INTERVAL_MS: Long = 2 * 1000
+ private const val MAX_RETRY_INTERVAL_MS: Long = 15 * 1000
+ private const val NEXT_RETRY_MULTIPLIER: Float = 1.2f // If we fail to poll we multiply our current retry interval by this (up to the above max) then try again
}
// endregion
@@ -54,7 +56,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
if (hasStarted) { return }
Log.d(TAG, "Started polling.")
hasStarted = true
- setUpPolling(retryInterval)
+ setUpPolling(RETRY_INTERVAL_MS)
}
fun stopIfNeeded() {
@@ -67,9 +69,11 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
Log.d(TAG, "Retrieving user profile.")
SnodeAPI.getSwarm(userPublicKey).bind {
usedSnodes.clear()
- deferred().also {
- pollNextSnode(userProfileOnly = true, it)
+ deferred().also { exception ->
+ pollNextSnode(userProfileOnly = true, exception)
}.promise
+ }.fail { exception ->
+ Log.e(TAG, "Failed to retrieve user profile.", exception)
}
}
// endregion
@@ -84,14 +88,14 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
pollNextSnode(deferred = deferred)
deferred.promise
}.success {
- val nextDelay = if (isCaughtUp) retryInterval else 0
+ val nextDelay = if (isCaughtUp) RETRY_INTERVAL_MS else 0
Timer().schedule(object : TimerTask() {
override fun run() {
- thread.run { setUpPolling(retryInterval) }
+ thread.run { setUpPolling(RETRY_INTERVAL_MS) }
}
}, nextDelay)
}.fail {
- val nextDelay = minOf(maxInterval, (delay * 1.2).toLong())
+ val nextDelay = minOf(MAX_RETRY_INTERVAL_MS, (delay * NEXT_RETRY_MULTIPLIER).toLong())
Timer().schedule(object : TimerTask() {
override fun run() {
thread.run { setUpPolling(nextDelay) }
diff --git a/libsession/src/main/res/values-b+ar+SA/strings.xml b/libsession/src/main/res/values-b+ar+SA/strings.xml
index e5392ac690..d97683835a 100644
--- a/libsession/src/main/res/values-b+ar+SA/strings.xml
+++ b/libsession/src/main/res/values-b+ar+SA/strings.xml
@@ -9,7 +9,7 @@
معرف الحساب هذا غير صالح. يرجى التحقق والمحاولة مرة أخرى.
أدخل معرف الحساب أو ONS
دعوة باستخدام معرف الحساب أو ONS
- Hey, I\'ve been using {app_name} to chat with complete privacy and security. Come join me! My Account ID is\n\n{account_id}\n\nDownload it at {session_download_url}
+ مرحبًا، لقد كنت أستخدم {app_name} للدردشة مع خصوصية وأمان كاملين. انضم إليّ! معرف حسابي هو\n\n{account_id}\n\nقم بتحميله من {session_download_url}
معرف حسابك
هذا معرف الحساب الخاص بك. يمكن للمستخدمين الآخرين مسحه ضوئيا لبدء محادثة معك.
الحجم الحقيقي
@@ -53,7 +53,7 @@
فاتح كلاسيكي
محيطي داكن
محيطي فاتح
- كبِر
+ تكبير
تكبير
تصغير
مرفق
@@ -68,7 +68,7 @@
اضغط لتنزيل {file_type}
إغلاق خيارات المرفق
جارٍ جمع المرفقات...
- نَزِل المرفق
+ تنزيل المرفق
المدة:
خطأ في إرفاق الملف
فشل في تحديد المرفق
@@ -97,7 +97,7 @@
N/A
{emoji} مرفق
{author}: {emoji} مرفق
- الدقة أو الأبعاد:
+ دقة الشاشة:
تعذر حفظ الملف.
إرسال إلى {name}
انضغط لتنزيل {file_type}
@@ -107,7 +107,7 @@
صوت
لا يوجد ميكروفون
لا يوجد سماعات أو مكبر صوت
- غير قادر على تشغيل ملف الصوت.
+ تعذّر تشغيل الملف الصوتي
تعذر تسجيل الصوت.
فشل في المصادقة
عدد كبير جدًا من محاولات التحقق الفاشلة. يرجى المحاولة مرة أخرى لاحقًا.
@@ -118,11 +118,11 @@
فشل المنع
لقد فشل الغاء المنع
الغاء منع المستخدم
- تم رفع الحظر عن المستخدم
+ تم رفع المنع عن المستخدم
حظر المستخدم
- تم حظر المستخدم
+ تم منع المستخدم
حظر
- فك حظر هذه جهة الإتصال لإرسال رسالة
+ إلغاء حظر جهة الإتصال لإرسال رسالة
لا توجد جهات اتصال محظورة
تم حظر {name}
هل أنت متيقِّن من حظر {name}؟ المستخدمين المحظورين لايمكنهم إرسال طلبات الرسائل، دعوات المجموعات أو الإتصال بك.
@@ -241,7 +241,7 @@
مراسلة جديدة
لا تملك أي محادثات حتى الآن
ارسل مع مفتاح الدخول
- النقر على مفتاح الدخول سوف يرسل الرسالة بدلا من بدء سطر جديد.
+ النقر على Enter سوف يرسل الرسالة بدلاَ من بدء سطر جديد.
جميع الوسائط
التدقيق الإملائي
تفعيل التحقق الإملائي عند كتابة الرسائل.
@@ -256,7 +256,7 @@
تحسين قاعدة البيانات
سجل تصحيح الأخطاء
أرفض
- أحذف
+ حذف
بعض أجهزتك تستخدم إصدارات قديمة. قد تكون المزامنة غير موثوقة حتى يتم تحديثها.
حظر هذا المستخدم
حظر مستخدم
@@ -318,7 +318,7 @@
لن تختفي الرسائل التي ترسلها بعد الآن. هل أنت متأكد أنك تريد إيقاف إيقاف الرسائل المختفية؟
تعيين رسائلك لتختفي {time} بعد أن تكون {disappearing_messages_type} ؟
{name} يستخدم عميل قديم. قد لا تعمل الرسائل المختفية على النحو المتوقع.
- فقط المسؤولين يمكنهم تغيير هذا الإعداد.
+ يمكن لمشرفين المجموعة فقط تغيير هذا الإعداد.
تم الإرسال
{name} قام بتعيين الرسائل لتختفي بعد {time} من {disappearing_messages_type}.
أنت قمت بتعيين الرسائل لتختفي بعد {time} من {disappearing_messages_type}.
@@ -346,16 +346,16 @@
جارٍ التنزيل...
مسودة
تعديل
- إيموجي & رموز
+ إيموجي و رموز
نشاطات
حيوانات & و طبيعة
أعلام
- مأكولات & و مشروبات
+ مأكولات و مشروبات
أجسام
مستخدمة حديثًا
- ابتسامات & وأشخاص
+ ابتسامات وأشخاص
رموز
- السفر & و أماكن
+ السفر و أماكن
هل أنت متيقِّن من أنك تريد مسح كافة {emoji}؟
أبطأ! لقد أرسلت الكثير من ردود الفعل الرموز التعبيرية. حاول مرة أخرى قريبا
{name} تفاعل بـ {emoji_name}
@@ -364,7 +364,7 @@
تفاعلت مع {emoji_name}
تفاعلت أنت و{count} آخرين مع {emoji_name}
تفاعلت أنت و{name} مع {emoji_name}
- تفاعل مع رسالتك {emoji}
+ تفاعل مع رسالتك بـ {emoji}
تفعيل
الرجاء التحقق من اتصالك بالإنترنت وحاول مرة أخرى.
نسخ الخطأ والخروج
@@ -376,7 +376,7 @@
طابق إعدادات النظام
مِن
تحويل الشاشة كاملة
- صورة GIF
+ GIF
Giphy
{app_name} سيتصل بمنصة Giphy لتقديم نتائج البحث. لن يكون لديك حماية كاملة للبيانات الوصفية عند إرسال الصور المتحركة (GIFs).
تضم المجموعات بحد أقصى 100 عضو
@@ -397,7 +397,7 @@
فشل دعوة {name} و {other_name} إلى {group_name}
فشل دعوة {name} إلى {group_name}
تم إرسال الدعوة
- الدعوة إلى المجموعة ناجحة
+ تمت دعوة المجموعة بنجاح
يجب أن يمتلك المستخدمون الإصدار الأحدث لتلقي الدعوات
أنت تمت دعوتك للانضمام إلى المجموعة.
أنت و{count} آخرين انضموا للمجموعة.
@@ -425,7 +425,7 @@
أدخل اسم المجموعة
الرجاء إدخال اسم للمجموعة.
الرجاء إدخال اسم مجموعة أقصر.
- أسم المجموعة الآن \'{group_name}.
+ اسم المجموعة الآن \'{group_name}.
تم تحديث اسم المجموعة.
ليس لديك رسائل من {group_name}. أرسل رسالة لبدء المحادثة!
أنت تم ترقيتك إلى مشرف.
@@ -464,7 +464,7 @@
الإبلاغ عن خطأ
شارك بعض التفاصيل لمساعدتنا في حل مشكلتك. صدّر السجلات الخاصة بك، ثم قم بتحميل الملف عبر مكتب المساعدة الخاص بـ {app_name}.
تصدير السجلات
- اصدر السجلات الخاصة بك، ثم ارفع الملف عبر مكتب المساعدة الخاص بـ{app_name}.
+ إصدار السجلات الخاصة بك، ثم رفع الملف عبر مكتب المساعدة الخاص بـ{app_name}.
حفظ على سطح المكتب
احفظ هذا الملف على سطح المكتب، ثم شاركه مع مطوري {app_name}.
الدعم
@@ -521,15 +521,15 @@
- %1$d عضو
- - %1$d عضو
- - %1$d عضو
- - %1$d عضو
- - %1$d عضو
- - %1$d عضو
+ - %1$d عضو نشط
+ - %1$d عضو نشط
+ - %1$d عضو نشط
+ - %1$d عضو نشط
+ - %1$d عضو نشط
- %1$d عضو نشط
أضف معرف الحساب أو ONS
- دعوة المتصلين
+ دعوة جهات الاتصال
- إرسال دعوات
- إرسال دعوة
@@ -551,8 +551,8 @@
تلقينا رسالة مشفرة باستخدام إصدار قديم من {app_name} لم يعد مدعومًا. يرجى مطالبة المرسل بتحديث إلى أحدث إصدار وإعادة إرسال الرسالة.
لم يتم العثور على الرسالة الأصلية
معلومات الرسالة
- اعتبرها مقروءة
- اجعله/ها غير مقروءة
+ تحديد كـ \"مقروء\"
+ تحديد كـ \"غير مقروء\"
- رسائل جديدة
- رسالة جديدة
@@ -573,32 +573,32 @@
الرد على
{name} دعاك للانضمام إلى {group_name}.
- إرسال رسالة إلى هذه المجموعة سوف يقبل تلقائيًا دعوة المجموعة.
+ بإرسال رسالة إلى هذه المجموعة سوف يقبل تلقائيًا دعوة المجموعة.
طلب رسالتك قيد الانتظار.
ستتمكن من إرسال الرسائل الصوتية والمرفقات بمجرد موافقة المستلم على طلب الرسالة هذا.
لقد وافقتَ على طلب الرسالة من {name}.
- إرسال رسالة إلى هذا المستخدم سوف يقبل تلقائيًا طلب الرسالة الخاص به ويكشف عن معرف حسابك.
+ بإرسال رسالة إلى هذا المستخدم سوف يقبل تلقائيًا طلب الرسالة الخاص به ويكشف عن معرف حسابك.
تم قبول طلب الرسائل الخاص بك.
هل أنت متأكد من أنك تريد مسح كافة طلبات الرسائل ودعوات المجموعات؟
طلبات رسائل المجتمع
السماح بطلبات الرسائل من محادثات المجتمع.
هل أنت متأكد من أنك تريد حذف طلب الرسالة هذا؟
لديك طلب مراسلة جديدة
- لا توجد طلبات رسالة معلقة
+ لا توجد طلبات مراسلة معلقة
تم إيقاف طلبات الرسائل من محادثات المجتمع من طرف {name}، لذا لا يمكنك إرسال الرسالة إليه.
- حدد الرسالة
+ تحديد رسالة
{author}: {message_snippet}
فشل الإرسال
فشلت المزامنة
جارٍ المزامنة
الرسائل غير المقروءة
رسالة صوتية
- اضغط باستمرار لتسجيل رسالة صوتية
+ اضغط مع الاستمرار لتسجيل رسالة صوتية
اسحب للإلغاء
{emoji} رسالة صوتية
{author}: {emoji} رسالة صوتية
الرسائل
- صغِّر
+ تصغير
التالي
اختر اسم مستعار لـ {name}. سيظهر لك في محادثاتك الفردية والجماعية.
أدخل اسم مستعار
@@ -610,21 +610,21 @@
ليس الآن
ملاحظة لنفسي
ليس لديك أي رسائل في ملاحظة لنفسي أو بمعنى آخر في الرسائل المحفوظة.
- إخفاء ملاحظة لنفسي
+ إخفاء \"ملاحظة لنفسي\"
هل أنت متأكد من أنك تريد إخفاء الملاحظة لنفسي؟
جميع الرسائل
محتوى الإشعارات
- المعلومات معروضة في الإشعارات.
+ المعلومات المعروضة في الإشعارات.
الاسم والمحتوى
الاسم فقط
بدون اسم او محتوى
الوضع السريع
ستتم إعلامك بالرسائل الجديدة بشكل موثوق وفوري باستخدام خوادم إشعارات جوجل.
سوف يتم إعلامك برسائل جديدة بشكل موثوق وفوري باستخدام خوادم إشعارات Apple.
- اذهب إلى إعدادات تنبيهات الجهاز
- التنبيهات - الكل
- التنبيهات - الإشعارات فقط
- التنبيهات - مكتومة
+ اذهب إلى إعدادات إشعارات الجهاز
+ الإشعارات - الكل
+ الإشعارات- الإشارات فقط
+ الإشعارات - مكتومة
{name} إلى {conversation_name}
ربما تلقيت رسائل أثناء إعادة تشغيل {device} الخاص بك.
لون ضوء التنبيه LED
@@ -645,7 +645,7 @@
رسالة جديدة {message_count} في {conversation_count} محادثات
الاهتزاز
مغلق
- نعم
+ حسناً
يعمل
إنشاء حساب
تم إنشاء الحساب
@@ -680,8 +680,8 @@
كلمة المرور الحالية غير صحيحة.
يتطلب كلمة السر لفتح {app_name}.
أدخل كلمة السر
- يرجى إدخال كلمة السر الحالية
- يرجى إدخال كلمة السر الجديدة
+ الرجاء إدخال كلمة السر الحالية
+ الرجاء إدخال كلمة السر الجديدة
كلمة المرور يجب ان تحتوي فقط على الاحرف, الارقام و الرموز
كلمة المرور يجب ان تكون بين 6 و 64 عنصر
كلمتا المرور لا تتطابقان
@@ -715,9 +715,9 @@
{app_name} يحتاج إذن الوصول إلى التخزين لحفظ الصور ومقاطع الفيديو، ولكن تم رفضه نهائيًا. يرجى الانتقال إلى إعدادات التطبيق، واختيار \"الأذونات\"، وتفعيل \"التخزين\".
{app_name} يحتاج إذن الوصول إلى التخزين لإرسال الصور ومقاطع الفيديو.
ًًًُُثَبت
- ثَبِت المحادثة
+ تثبيت المحادثة
الغ التثبيت
- ألغِي تثبيت المحادثة
+ إلغاء تثبيت المحادثة
معاينة
الملف الشخصي
صورة العرض
@@ -727,9 +727,9 @@
فشل تحديث الملف الشخصي.
ترقية
رمز QR
- رمز QR هذا لا يحتوي على معرف حساب
+ رمز QR هذا لا يحتوي على مُعرف حساب
رمز QR هذا لا يحتوي على عبارة استرداد
- امسح رمز الاستجابة السريعة
+ امسح رمز الاستجابة السريعة QR
عرض QR
يمكن للأصدقاء إرسال رسائل إليك عن طريق مسح رمز QR الخاص بك.
انهاء {app_name}
@@ -752,7 +752,7 @@
إخفاء كلمة مرور الاسترداد بشكل دائم
بدون كلمة المرور الاستردادية، لا يمكنك تحميل حسابك على الأجهزة الجديدة. \n\nنوصيك بشدة بحفظ كلمة المرور الاستردادية في مكان آمن قبل المتابعة.
هل أنت متأكد من أنك تريد إخفاء كلمة مرور الاسترداد الخاصة بك على هذا الجهاز نهائيًا؟ لا يمكن التراجع عن هذا.
- إخفاء كلمة المرور للاسترجاع
+ إخفاء كلمة مرور الاسترداد
إخفاء كلمة المرور الخاصة بالاسترداد على هذا الجهاز بشكل دائم.
أدخل كلمة مرور الاسترجاع لتحميل حسابك. إذا لم تقم بحفظها، يمكنك العثور عليها في إعدادات التطبيق.
عرض كلمة المرور
@@ -778,17 +778,17 @@
بحث
ابحث في جهات الاتصال
بحث عن محادثة
- الرجاء إدخال كملة بحث.
+ الرجاء إدخال كلمة للبحث.
- %1$d من %2$d مطابقة
- - %1$d من %2$d إجابة
- - %1$d من %2$d مطابقات
+ - %1$d من %2$d مطابقة
+ - %1$d من %2$d مطابقتين
- %1$d من %2$d مطابقات
- %1$d من %2$d مطابقات
- %1$d من %2$d مطابقات
لم يتم العثور على أي نتيجة.
- لم يتم العثور على أية نتيجة لـ {query}
+ لم يتم العثور على نتائج لـ {query}
بحث عن الأعضاء
جاري البحث...
حدد
@@ -800,7 +800,7 @@
مسح البيانات
المحادثات
المساعدة
- أُدعُ صديق
+ دعوة صديق
طلبات المُراسلة
الإشعارات
الصلاحيات
@@ -818,8 +818,8 @@
إظهار الكل
عرض أقل
الملصقات
- اِذهب اِلى صفحة الدعم
- System Information: {information}
+ الذهاب لصفحة الدعم
+ معلومات النظام: {information}
التالي
افتراضي
خطأ
@@ -845,7 +845,7 @@
هل أنت متأكد من أنك تريد فتح هذا الرابط في متصفحك؟\n\n{url}
استخدم الوضع السريع
فيديو
- غير قادر على تشغيل الفيديو.
+ تعذر تشغيل الفيديو
عرض
قد يستغرق ذلك بضع دقائق.
لحظة واحدة من فضلك...
diff --git a/libsession/src/main/res/values/strings.xml b/libsession/src/main/res/values/strings.xml
index 861076f3e7..8a3b99d97b 100644
--- a/libsession/src/main/res/values/strings.xml
+++ b/libsession/src/main/res/values/strings.xml
@@ -412,6 +412,7 @@
Please pick at least one other group member.
Delete Group
Are you sure you want to delete {group_name}? This will remove all members and delete all group content.
+ Are you sure you want to delete {group_name}?
{group_name} has been deleted by a group admin. You will not be able to send any more messages.
Enter a group description
Group display picture updated.