diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 516993228..d4385d2a7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -163,6 +163,13 @@
+
+
.
+ */
+package org.linphone.core
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import androidx.annotation.MainThread
+import org.linphone.LinphoneApplication.Companion.coreContext
+import org.linphone.core.tools.Log
+
+@MainThread
+class CoreKeepAliveThirdPartyAccountsService : Service() {
+ companion object {
+ private const val TAG = "[Core Keep Alive Third Party Accounts Service]"
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.i("$TAG Created")
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ Log.i("$TAG onStartCommand")
+ coreContext.notificationsManager.onKeepAliveServiceStarted(this)
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ override fun onTaskRemoved(rootIntent: Intent?) {
+ Log.i("$TAG Task removed, doing nothing")
+ super.onTaskRemoved(rootIntent)
+ }
+
+ override fun onDestroy() {
+ Log.i("$TAG onDestroy")
+ coreContext.notificationsManager.onKeepAliveServiceDestroyed()
+ super.onDestroy()
+ }
+
+ override fun onBind(p0: Intent?): IBinder? {
+ return null
+ }
+}
diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt
index d005b8623..d6e3db597 100644
--- a/app/src/main/java/org/linphone/core/CorePreferences.kt
+++ b/app/src/main/java/org/linphone/core/CorePreferences.kt
@@ -71,6 +71,13 @@ class CorePreferences @UiThread constructor(private val context: Context) {
config.setBool("app", "publish_presence", value)
}
+ @get:WorkerThread @set:WorkerThread
+ var keepServiceAlive: Boolean
+ get() = config.getBool("app", "keep_service_alive", false)
+ set(value) {
+ config.setBool("app", "keep_service_alive", value)
+ }
+
// Calls settings
@get:WorkerThread @set:WorkerThread
@@ -104,6 +111,7 @@ class CorePreferences @UiThread constructor(private val context: Context) {
// Conversation settings
+ @get:WorkerThread @set:WorkerThread
var exportMediaToNativeGallery: Boolean // TODO: use it!
// Keep old name for backward compatibility
get() = config.getBool("app", "make_downloaded_images_public_in_gallery", true)
@@ -113,6 +121,7 @@ class CorePreferences @UiThread constructor(private val context: Context) {
/* Voice Recordings */
+ @get:WorkerThread @set:WorkerThread
var voiceRecordingMaxDuration: Int
get() = config.getInt("app", "voice_recording_max_duration", 600000) // in ms
set(value) = config.setInt("app", "voice_recording_max_duration", value)
diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt
index 596411849..c6f87966d 100644
--- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt
+++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt
@@ -61,6 +61,7 @@ import org.linphone.core.ChatMessageReaction
import org.linphone.core.ChatRoom
import org.linphone.core.Core
import org.linphone.core.CoreForegroundService
+import org.linphone.core.CoreKeepAliveThirdPartyAccountsService
import org.linphone.core.CoreListenerStub
import org.linphone.core.CorePreferences
import org.linphone.core.Friend
@@ -93,10 +94,12 @@ class NotificationsManager @MainThread constructor(private val context: Context)
const val CHAT_NOTIFICATIONS_GROUP = "CHAT_NOTIF_GROUP"
private const val INCOMING_CALL_ID = 1
+ private const val KEEP_ALIVE_FOR_THIRD_PARTY_ACCOUNTS_ID = 5
private const val MISSED_CALL_ID = 10
}
private var currentForegroundServiceNotificationId = -1
+ private var currentKeepAliveThirdPartyAccountsForegroundServiceNotificationId = -1
private var currentlyRingingCallRemoteAddress: Address? = null
@@ -137,7 +140,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
val notifiable = getNotifiableForCall(call)
if (notifiable.notificationId == currentForegroundServiceNotificationId) {
Log.i(
- "$TAG Update foreground service type in case video was enabled/disabled since last time"
+ "$TAG Update foreground Service type in case video was enabled/disabled since last time"
)
startCallForeground(call)
}
@@ -349,6 +352,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
}
private var coreService: CoreForegroundService? = null
+ private var keepAliveService: CoreKeepAliveThirdPartyAccountsService? = null
private val callNotificationsMap: HashMap = HashMap()
private val chatNotificationsMap: HashMap = HashMap()
@@ -397,7 +401,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
stopCallForeground()
} else if (currentForegroundServiceNotificationId == -1) {
Log.i(
- "$TAG At least a call is still running and no foreground service notification was found"
+ "$TAG At least a call is still running and no foreground Service notification was found"
)
val call = core.currentCall ?: core.calls.first()
startCallForeground(call)
@@ -411,6 +415,20 @@ class NotificationsManager @MainThread constructor(private val context: Context)
coreService = null
}
+ @MainThread
+ fun onKeepAliveServiceStarted(service: CoreKeepAliveThirdPartyAccountsService) {
+ Log.i("$TAG Keep app alive for third party accounts Service has been started")
+ keepAliveService = service
+ startKeepAliveServiceForeground()
+ }
+
+ @MainThread
+ fun onKeepAliveServiceDestroyed() {
+ Log.i("$TAG Keep app alive for third party accounts Service has been destroyed")
+ stopKeepAliveServiceForeground()
+ keepAliveService = null
+ }
+
@MainThread
private fun createChannels(clearPreviousChannels: Boolean) {
if (clearPreviousChannels) {
@@ -421,7 +439,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
}
}
- createServiceChannel()
+ createThirdPartyAccountKeepAliveServiceChannel()
createIncomingCallNotificationChannel()
createMissedCallNotificationChannel()
createActiveCallNotificationChannel()
@@ -608,7 +626,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
) {
mask = mask or Compatibility.FOREGROUND_SERVICE_TYPE_MICROPHONE
Log.i(
- "$TAG RECORD_AUDIO permission has been granted, adding FOREGROUND_SERVICE_TYPE_MICROPHONE to foreground service types mask"
+ "$TAG RECORD_AUDIO permission has been granted, adding FOREGROUND_SERVICE_TYPE_MICROPHONE to foreground Service types mask"
)
}
val isSendingVideo = when (call.currentParams.videoDirection) {
@@ -623,7 +641,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
) {
mask = mask or Compatibility.FOREGROUND_SERVICE_TYPE_CAMERA
Log.i(
- "$TAG CAMERA permission has been granted, adding FOREGROUND_SERVICE_TYPE_CAMERA to foreground service types mask"
+ "$TAG CAMERA permission has been granted, adding FOREGROUND_SERVICE_TYPE_CAMERA to foreground Service types mask"
)
}
}
@@ -651,13 +669,13 @@ class NotificationsManager @MainThread constructor(private val context: Context)
val service = coreService
if (service != null) {
Log.i(
- "$TAG Stopping foreground service (was using notification ID [$currentForegroundServiceNotificationId])"
+ "$TAG Stopping foreground Service (was using notification ID [$currentForegroundServiceNotificationId])"
)
service.stopForeground(STOP_FOREGROUND_REMOVE)
service.stopSelf()
currentForegroundServiceNotificationId = -1
} else {
- Log.w("$TAG Can't stop foreground service & notif, no service was found")
+ Log.w("$TAG Can't stop foreground Service & notif, no Service was found")
}
}
@@ -806,7 +824,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
if (coreService == null && tag == null) {
// We can't notify using CallStyle if there isn't a foreground service running
Log.w(
- "$TAG Foreground service hasn't started yet, can't display a CallStyle notification until then: $iae"
+ "$TAG Foreground Service hasn't started yet, can't display a CallStyle notification until then: $iae"
)
} else {
Log.e("$TAG Illegal Argument Exception occurred: $iae")
@@ -1245,6 +1263,66 @@ class NotificationsManager @MainThread constructor(private val context: Context)
.build()
}
+ @MainThread
+ private fun startKeepAliveServiceForeground() {
+ Log.i(
+ "$TAG Trying to start keep alive for third party accounts foreground Service using call notification"
+ )
+
+ val channelId = context.getString(R.string.notification_channel_service_id)
+ val channel = notificationManager.getNotificationChannel(channelId)
+ val importance = channel?.importance ?: NotificationManagerCompat.IMPORTANCE_NONE
+ if (importance == NotificationManagerCompat.IMPORTANCE_NONE) {
+ Log.e(
+ "$TAG Keep alive for third party accounts Service channel has been disabled, can't start foreground service!"
+ )
+ return
+ }
+
+ val service = keepAliveService
+ if (service != null) {
+ val builder = NotificationCompat.Builder(context, channelId)
+ .setContentTitle(context.getString(R.string.app_name))
+ .setSmallIcon(R.drawable.linphone_notification)
+ .setAutoCancel(false)
+ .setOngoing(true)
+ .setCategory(NotificationCompat.CATEGORY_SERVICE)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setShowWhen(false)
+ val notification = builder.build()
+
+ Log.i(
+ "$TAG Keep alive for third party accounts Service found, starting it as foreground using notification ID [$KEEP_ALIVE_FOR_THIRD_PARTY_ACCOUNTS_ID] with type DATA_SYNC"
+ )
+ Compatibility.startServiceForeground(
+ service,
+ KEEP_ALIVE_FOR_THIRD_PARTY_ACCOUNTS_ID,
+ notification,
+ Compatibility.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ )
+ currentKeepAliveThirdPartyAccountsForegroundServiceNotificationId = KEEP_ALIVE_FOR_THIRD_PARTY_ACCOUNTS_ID
+ } else {
+ Log.w("$TAG Keep alive for third party accounts Service hasn't started yet...")
+ }
+ }
+
+ @MainThread
+ private fun stopKeepAliveServiceForeground() {
+ val service = keepAliveService
+ if (service != null) {
+ Log.i(
+ "$TAG Stopping keep alive for third party accounts foreground Service (was using notification ID [$currentKeepAliveThirdPartyAccountsForegroundServiceNotificationId])"
+ )
+ service.stopForeground(STOP_FOREGROUND_REMOVE)
+ service.stopSelf()
+ currentKeepAliveThirdPartyAccountsForegroundServiceNotificationId = -1
+ } else {
+ Log.w(
+ "$TAG Can't stop keep alive for third party accounts foreground Service & notif, no Service was found"
+ )
+ }
+ }
+
@MainThread
private fun createIncomingCallNotificationChannel() {
val id = context.getString(R.string.notification_channel_incoming_call_id)
@@ -1302,12 +1380,12 @@ class NotificationsManager @MainThread constructor(private val context: Context)
}
@MainThread
- private fun createServiceChannel() {
+ private fun createThirdPartyAccountKeepAliveServiceChannel() {
val id = context.getString(R.string.notification_channel_service_id)
val name = context.getString(R.string.notification_channel_service_name)
val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW).apply {
- description = name
+ description = context.getString(R.string.notification_channel_service_desc)
}
notificationManager.createNotificationChannel(channel)
}
diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt
index dae692d9a..130f252d5 100644
--- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt
@@ -115,7 +115,9 @@ class SettingsViewModel @UiThread constructor() : ViewModel() {
)
val availableThemesValues = arrayListOf(-1, 0, 1)
- // Advanced setttings
+ // Advanced settings
+ val keepAliveThirdPartyAccountsService = MutableLiveData()
+
val remoteProvisioningUrl = MutableLiveData()
init {
@@ -159,6 +161,8 @@ class SettingsViewModel @UiThread constructor() : ViewModel() {
theme.postValue(corePreferences.darkMode)
+ keepAliveThirdPartyAccountsService.postValue(corePreferences.keepServiceAlive)
+
remoteProvisioningUrl.postValue(core.provisioningUri)
}
}
@@ -356,6 +360,21 @@ class SettingsViewModel @UiThread constructor() : ViewModel() {
}
}
+ @UiThread
+ fun toggleKeepAliveThirdPartyAccountService() {
+ val newValue = keepAliveThirdPartyAccountsService.value == false
+
+ coreContext.postOnCoreThread {
+ corePreferences.keepServiceAlive = newValue
+ keepAliveThirdPartyAccountsService.postValue(newValue)
+ if (newValue) {
+ coreContext.startKeepAliveService()
+ } else {
+ coreContext.stopKeepAliveService()
+ }
+ }
+ }
+
@UiThread
fun updateRemoteProvisioningUrl() {
coreContext.postOnCoreThread { core ->
diff --git a/app/src/main/res/layout/settings_advanced_fragment.xml b/app/src/main/res/layout/settings_advanced_fragment.xml
index 707de6ce8..1ac77ea8f 100644
--- a/app/src/main/res/layout/settings_advanced_fragment.xml
+++ b/app/src/main/res/layout/settings_advanced_fragment.xml
@@ -57,6 +57,33 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/keep_alive_service_switch"/>
+
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 6c2b5db2a..6c53affa9 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -44,6 +44,7 @@
&appName; notifications d\'appels entrants
&appName; notifications d\'appels manqués
&appName; notification de service
+ Ce service sera actif en permanence pour garder l\'app en vie et vous permettre de recevoir appels et messages sans recourir aux notifications push.
&appName; notifications des conversations
A réagi par %s à : %s
Marquer comme lu
@@ -256,7 +257,10 @@
Sombre
Clair
Auto
+
Paramètres avancés
+ Garder l\'app en vie via un Service
+ URL de configuration distante
Votre compte
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d8702fbb0..0992705e6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -79,6 +79,7 @@
&appName; incoming calls notifications
&appName; missed calls notifications
&appName; service notification
+ This service will run all the time to keep app alive and allow you to receive calls and messages without push notifications.
&appName; instant messages notifications
Reacted by %s to: %s
Mark as read
@@ -291,8 +292,9 @@
Dark theme
Light theme
Auto
- Advanced settings
+ Advanced settings
+ Keep app alive using Service
Remote provisioning URL