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