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 @@ جارٍ التنزيل... مسودة تعديل - إيموجي &amp; رموز + إيموجي و رموز نشاطات حيوانات &amp; و طبيعة أعلام - مأكولات &amp; و مشروبات + مأكولات و مشروبات أجسام مستخدمة حديثًا - ابتسامات &amp; وأشخاص + ابتسامات وأشخاص رموز - السفر &amp; و أماكن + السفر و أماكن هل أنت متيقِّن من أنك تريد مسح كافة {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.