diff --git a/app/build.gradle b/app/build.gradle index c886b858bc..7ae925b437 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -304,7 +304,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 1b0f6a7b13..af7e8bfca5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,12 +37,14 @@ + + - + diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt index 9fdc6b1063..e6c91800e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -262,7 +262,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { Orientation.REVERSED_LANDSCAPE -> 90f else -> 0f } - +R.drawable.ic_baseline_call_24 userAvatar.animate().cancel() userAvatar.animate().rotation(rotation).start() contactAvatar.animate().cancel() 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 91caa7a878..8567644514 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.service +import android.app.ForegroundServiceStartNotAllowedException +import android.app.KeyguardManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -10,6 +12,7 @@ import android.content.pm.PackageManager import android.content.pm.ServiceInfo import android.media.AudioManager import android.os.Build +import android.os.PowerManager import android.os.ResultReceiver import android.telephony.TelephonyManager import androidx.core.app.ServiceCompat @@ -22,6 +25,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.utilities.Address import org.session.libsession.utilities.FutureTaskListener +import org.session.libsession.utilities.NonTranslatableStringConstants import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.calls.WebRtcCallActivity @@ -32,6 +36,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 @@ -328,6 +333,54 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener { registerUncaughtExceptionHandler() networkChangedReceiver = NetworkChangeReceiver(::networkChange) networkChangedReceiver!!.register(this) + + Log.w("ACL", "Lesssgo!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + //if (appIsBackground(this)) { +// ServiceCompat.startForeground(this, +// CallNotificationBuilder.WEBRTC_NOTIFICATION, +// CallNotificationBuilder.getFirstCallNotification(this, "Initialising"), +// if (Build.VERSION.SDK_INT >= 30) ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL else 0 +// ) + + // Get the KeyguardManager and PowerManager + val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + + // Check if the phone is locked + val isPhoneLocked = keyguardManager.isKeyguardLocked + + // Check if the screen is awake + val isScreenAwake = powerManager.isInteractive + + // If the screen is off or phone is locked, wake it up + if (!isScreenAwake || isPhoneLocked) { wakeUpDevice() } + + +// ServiceCompat.startForeground( +// this, +// CallNotificationBuilder.WEBRTC_NOTIFICATION, +// Not +// CallNotificationBuilder.getCallInProgressNotification(this, type, recipient), +// if (Build.VERSION.SDK_INT >= 30) ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL else 0 +// ) + } + + private fun wakeUpDevice() { + val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + val wakeLock = powerManager.newWakeLock( + PowerManager.FULL_WAKE_LOCK or + PowerManager.ACQUIRE_CAUSES_WAKEUP or + PowerManager.ON_AFTER_RELEASE, + "${NonTranslatableStringConstants.APP_NAME}:WakeLock" + ) + + // Acquire the wake lock to wake up the device + wakeLock.acquire(3000) // Wake up for 3 seconds + + // Dismiss the keyguard + val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val keyguardLock = keyguardManager.newKeyguardLock("MyApp:KeyguardLock") + keyguardLock.disableKeyguard() } private fun registerUncaughtExceptionHandler() { @@ -724,25 +777,57 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener { } } + // Over the course of setting up a phonecall 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_PHONE_CALL else 0 - ) - } catch (e: IllegalStateException) { - Log.e(TAG, "Failed to setCallInProgressNotification as a foreground service for type: ${type}, trying to update instead", e) + + val typeString = when (type) { + TYPE_INCOMING_RINGING -> "TYPE_INCOMING_RINGING" + TYPE_OUTGOING_RINGING -> "TYPE_OUTGOING_RINGING" + TYPE_ESTABLISHED -> "TYPE_ESTABLISHED" + TYPE_INCOMING_CONNECTING -> "TYPE_INCOMING_CONNECTING" + TYPE_INCOMING_PRE_OFFER -> "TYPE_INCOMING_PRE_OFFER" + WEBRTC_NOTIFICATION -> "WEBRTC_NOTIFICATION" + else -> "We have no idea!" + } + Log.w("ACL", "Hit setCallInProgressNotification with type: $typeString") + + // If notifications are enabled we'll try and start a foreground service to show the notification + var failedToStartForegroundService = false + if (CallNotificationBuilder.areNotificationsEnabled(this)) { + Log.w("ACL", "Notifications are ENABLED! About to try to call startForeground") + try { + ServiceCompat.startForeground( + this, + CallNotificationBuilder.WEBRTC_NOTIFICATION, + CallNotificationBuilder.getCallInProgressNotification(this, type, recipient), + if (Build.VERSION.SDK_INT >= 30) ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL else 0 + ) + Log.w("ACL", "Successfully called startForeground - about to bail.") + 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 { + Log.w("ACL", "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 && failedToStartForegroundService) { + + Log.w("ACL", "About to create foregroundIntent and try to start the WebRtcCallActivity with type TYPE_INCOMING_PRE_OFFER") + // 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) .setAction(WebRtcCallActivity.ACTION_FULL_SCREEN_INTENT) startActivity(foregroundIntent) + return } + + Log.w("ACL", "type wasn't TYPE_INCOMING_PRE_OFFER - doing nothing =/") } private fun getOptionalRemoteRecipient(intent: Intent): Recipient? = 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..88e147a720 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,15 @@ package org.thoughtcrime.securesms.webrtc import android.Manifest +import android.annotation.SuppressLint +import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope @@ -25,18 +31,37 @@ 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.thoughtcrime.securesms.calls.WebRtcCallActivity import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.service.WebRtcCallService -import org.thoughtcrime.securesms.util.CallNotificationBuilder import org.webrtc.IceCandidate +import network.loki.messenger.R + class CallMessageProcessor(private val context: Context, private val textSecurePreferences: TextSecurePreferences, lifecycle: Lifecycle, private val storage: StorageProtocol) { + + companion object { private const val VERY_EXPIRED_TIME = 15 * 60 * 1000L - fun safeStartService(context: Context, intent: Intent) { + + + // TODO: While fine if the app is in the foreground, you cannot do this in modern Android if the + // device is locked (i.e., if you get a call when the device is locked & attempt start the + // foreground service) it will throw an error like: + // Unable to start CallMessage intent: startForegroundService() not allowed due to mAllowStartForeground false: + // service network.loki.messenger/org.thoughtcrime.securesms.service.WebRtcCallService + fun safeStartForegroundService(context: Context, intent: Intent) { + Log.w("ACL", "Hit safeStartForegroundService for intent action: " + intent.action) + + // TODO: This is super-ugly - we're forcing a full-screen intent to wake the device up so we can + // TODO: successfully call `startForegroundService` in the second catch block below. This works + // TODO: even if the device is locked and Session has been closed down - but it's UUUUGLY. Need + // TODO: to find a better way. + showIncomingCallNotification(context) + // 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 @@ -49,7 +74,41 @@ class CallMessageProcessor(private val context: Context, private val textSecureP } } } - } + + fun createNotificationChannel(context: Context) { + val channel = NotificationChannel( + "WakeUpChannelID", // CHANNEL_ID, + "WakeUpChannelName", //CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Notifications for incoming calls" + } + val notificationManager = context.getSystemService(NotificationManager::class.java) + notificationManager?.createNotificationChannel(channel) + } + + @SuppressLint("MissingPermission") + fun showIncomingCallNotification(context: Context) { + createNotificationChannel(context) + + val notificationIntent = Intent(context, WebRtcCallActivity::class.java) + val pendingIntent = PendingIntent.getActivity( + context, + 0, + notificationIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val notificationBuilder = NotificationCompat.Builder(context, "WakeUpChannelID") + .setContentTitle("Incoming Call") + .setContentText("Tap to answer") + .setSmallIcon(R.drawable.ic_baseline_call_24) + .setPriority(NotificationCompat.PRIORITY_MAX) // Used for devices below API 26 + .setCategory(NotificationCompat.CATEGORY_CALL) + .setFullScreenIntent(pendingIntent, true) + + NotificationManagerCompat.from(context).notify(999, notificationBuilder.build()) + } } init { lifecycle.coroutineScope.launch(IO) { @@ -101,7 +160,7 @@ 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) { @@ -115,7 +174,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP callId = callId ) - safeStartService(context, answerIntent) + safeStartForegroundService(context, answerIntent) } private fun handleIceCandidates(callMessage: CallMessage) { @@ -131,7 +190,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 +203,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 +217,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 {