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