Found a way to force the device to wake up & accept a call if the foreground service is stopped - but it's ugly

pull/1706/head
alansley 5 months ago
parent 5ed4492aab
commit af602df969

@ -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'

@ -37,12 +37,14 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" tools:node="remove" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" /> <!-- For video calls -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" /> <!-- For calls that get audio from bluetooth headsets -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" /> <!-- We cannot declare this is good fair because we don't implement `ConnectionService` - see: https://developer.android.com/reference/android/telecom/ConnectionService -->
<uses-permission android:name="android.app.role.DIALER" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

@ -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()

@ -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? =

@ -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<IceCandidate> {

Loading…
Cancel
Save