diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt
index e604428f7..bab8564ec 100644
--- a/app/src/main/java/org/linphone/core/CoreContext.kt
+++ b/app/src/main/java/org/linphone/core/CoreContext.kt
@@ -20,6 +20,7 @@
package org.linphone.core
import android.annotation.SuppressLint
+import android.app.Application
import android.content.Context
import android.content.Intent
import android.os.Handler
@@ -32,15 +33,22 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.contacts.ContactsManager
import org.linphone.core.tools.Log
import org.linphone.ui.voip.VoipActivity
+import org.linphone.utils.ActivityMonitor
import org.linphone.utils.LinphoneUtils
class CoreContext(val context: Context) : HandlerThread("Core Thread") {
+ companion object {
+ const val TAG = "[Core Context]"
+ }
+
lateinit var core: Core
val emojiCompat: EmojiCompat
val contactsManager = ContactsManager()
+ private val activityMonitor = ActivityMonitor()
+
private val mainThread = Handler(Looper.getMainLooper())
@SuppressLint("HandlerLeak")
@@ -48,7 +56,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
private val coreListener = object : CoreListenerStub() {
override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) {
- Log.i("[Context] Global state changed: $state")
+ Log.i("$TAG Global state changed: $state")
}
override fun onCallStateChanged(
@@ -57,7 +65,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
state: Call.State?,
message: String
) {
- Log.i("[Context] Call state changed [$state]")
+ Log.i("$TAG Call state changed [$state]")
if (state == Call.State.OutgoingProgress) {
showCallActivity()
} else if (state == Call.State.IncomingReceived) {
@@ -70,6 +78,8 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
init {
EmojiCompat.init(context)
emojiCompat = EmojiCompat.get()
+
+ (context as Application).registerActivityLifecycleCallbacks(activityMonitor)
}
override fun run() {
@@ -109,6 +119,10 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
core.stop()
contactsManager.onCoreStopped()
+ postOnMainThread {
+ (context as Application).unregisterActivityLifecycleCallbacks(activityMonitor)
+ }
+
quitSafely()
}
@@ -128,6 +142,30 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
}
}
+ fun onForeground() {
+ postOnCoreThread {
+ // We can't rely on defaultAccount?.params?.isPublishEnabled
+ // as it will be modified by the SDK when changing the presence status
+ if (corePreferences.publishPresence) {
+ Log.i("$TAG App is in foreground, PUBLISHING presence as Online")
+ core.consolidatedPresence = ConsolidatedPresence.Online
+ }
+ }
+ }
+
+ fun onBackground() {
+ postOnCoreThread {
+ // We can't rely on defaultAccount?.params?.isPublishEnabled
+ // as it will be modified by the SDK when changing the presence status
+ if (corePreferences.publishPresence) {
+ Log.i("$TAG App is in background, un-PUBLISHING presence info")
+ // We don't use ConsolidatedPresence.Busy but Offline to do an unsubscribe,
+ // Flexisip will handle the Busy status depending on other devices
+ core.consolidatedPresence = ConsolidatedPresence.Offline
+ }
+ }
+ }
+
fun startCall(
address: Address,
callParams: CallParams? = null,
@@ -136,14 +174,14 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
) {
// Core thread
if (!core.isNetworkReachable) {
- Log.e("[Context] Network unreachable, abort outgoing call")
+ Log.e("$TAG Network unreachable, abort outgoing call")
return
}
val params = callParams ?: core.createCallParams(null)
if (params == null) {
val call = core.inviteAddress(address)
- Log.w("[Context] Starting call $call without params")
+ Log.w("$TAG Starting call $call without params")
return
}
@@ -163,11 +201,11 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
if (account != null) {
params.account = account
Log.i(
- "[Context] Using account matching address ${localAddress.asStringUriOnly()} as From"
+ "$TAG Using account matching address ${localAddress.asStringUriOnly()} as From"
)
} else {
Log.e(
- "[Context] Failed to find account matching address ${localAddress.asStringUriOnly()}"
+ "$TAG Failed to find account matching address ${localAddress.asStringUriOnly()}"
)
}
}
@@ -177,16 +215,16 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
}*/
val call = core.inviteAddressWithParams(address, params)
- Log.i("[Context] Starting call $call")
+ Log.i("$TAG Starting call $call")
}
fun switchCamera() {
val currentDevice = core.videoDevice
- Log.i("[Context] Current camera device is $currentDevice")
+ Log.i("$TAG Current camera device is $currentDevice")
for (camera in core.videoDevicesList) {
if (camera != currentDevice && camera != "StaticImage: Static picture") {
- Log.i("[Context] New camera device will be $camera")
+ Log.i("$TAG New camera device will be $camera")
core.videoDevice = camera
break
}
@@ -194,7 +232,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
val call = core.currentCall
if (call == null) {
- Log.w("[Context] Switching camera while not in call")
+ Log.w("$TAG Switching camera while not in call")
return
}
call.update(null)
@@ -205,7 +243,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
}
private fun showCallActivity() {
- Log.i("[Context] Starting VoIP activity")
+ Log.i("$TAG Starting VoIP activity")
val intent = Intent(context, VoipActivity::class.java)
// This flag is required to start an Activity from a Service context
intent.addFlags(
diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt
index 999c07bef..6ef49b00a 100644
--- a/app/src/main/java/org/linphone/core/CorePreferences.kt
+++ b/app/src/main/java/org/linphone/core/CorePreferences.kt
@@ -51,6 +51,12 @@ class CorePreferences constructor(private val context: Context) {
editor.apply()
}
+ var publishPresence: Boolean
+ get() = config.getBool("app", "publish_presence", true)
+ set(value) {
+ config.setBool("app", "publish_presence", value)
+ }
+
val defaultDomain: String
get() = config.getString("app", "default_domain", "sip.linphone.org")!!
diff --git a/app/src/main/java/org/linphone/utils/ActivityMonitor.kt b/app/src/main/java/org/linphone/utils/ActivityMonitor.kt
new file mode 100644
index 000000000..fca1e7329
--- /dev/null
+++ b/app/src/main/java/org/linphone/utils/ActivityMonitor.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2010-2021 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.linphone.utils
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import org.linphone.LinphoneApplication.Companion.coreContext
+import org.linphone.core.tools.service.AndroidDispatcher
+
+class ActivityMonitor : ActivityLifecycleCallbacks {
+ private val activities = ArrayList()
+ private var mActive = false
+ private var mRunningActivities = 0
+ private var mLastChecker: InactivityChecker? = null
+
+ @Synchronized
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+ if (!activities.contains(activity)) activities.add(activity)
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+ }
+
+ @Synchronized
+ override fun onActivityResumed(activity: Activity) {
+ if (!activities.contains(activity)) {
+ activities.add(activity)
+ }
+ mRunningActivities++
+ checkActivity()
+ }
+
+ @Synchronized
+ override fun onActivityPaused(activity: Activity) {
+ if (!activities.contains(activity)) {
+ activities.add(activity)
+ } else {
+ mRunningActivities--
+ checkActivity()
+ }
+ }
+
+ override fun onActivityStopped(activity: Activity) {
+ }
+
+ @Synchronized
+ override fun onActivityDestroyed(activity: Activity) {
+ activities.remove(activity)
+ }
+
+ private fun startInactivityChecker() {
+ if (mLastChecker != null) mLastChecker!!.cancel()
+ AndroidDispatcher.dispatchOnUIThreadAfter(
+ InactivityChecker().also { mLastChecker = it },
+ 2000
+ )
+ }
+
+ private fun checkActivity() {
+ if (mRunningActivities == 0) {
+ if (mActive) startInactivityChecker()
+ } else if (mRunningActivities > 0) {
+ if (!mActive) {
+ mActive = true
+ onForegroundMode()
+ }
+ if (mLastChecker != null) {
+ mLastChecker!!.cancel()
+ mLastChecker = null
+ }
+ }
+ }
+
+ private fun onBackgroundMode() {
+ coreContext.onBackground()
+ }
+
+ private fun onForegroundMode() {
+ coreContext.onForeground()
+ }
+
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
+ internal inner class InactivityChecker : Runnable {
+ private var isCanceled = false
+ fun cancel() {
+ isCanceled = true
+ }
+
+ override fun run() {
+ if (!isCanceled) {
+ if (mRunningActivities == 0 && mActive) {
+ mActive = false
+ onBackgroundMode()
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/layout/call_fragment.xml b/app/src/main/res/layout/call_fragment.xml
index 937a5db9e..dc68843eb 100644
--- a/app/src/main/res/layout/call_fragment.xml
+++ b/app/src/main/res/layout/call_fragment.xml
@@ -73,7 +73,6 @@
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginBottom="10dp"
android:background="@color/gray_7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -241,6 +240,7 @@
android:layout_marginTop="45dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
+ android:layout_marginBottom="24dp"
android:paddingBottom="16dp"
android:background="@drawable/shape_round_white_background"
android:orientation="vertical"
diff --git a/app/src/main/res/layout/call_start_fragment.xml b/app/src/main/res/layout/call_start_fragment.xml
index 0af41ff30..25537fb3a 100644
--- a/app/src/main/res/layout/call_start_fragment.xml
+++ b/app/src/main/res/layout/call_start_fragment.xml
@@ -48,7 +48,6 @@
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginBottom="10dp"
android:background="@color/gray_7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/contact_fragment.xml b/app/src/main/res/layout/contact_fragment.xml
index 551ae1d50..93003216e 100644
--- a/app/src/main/res/layout/contact_fragment.xml
+++ b/app/src/main/res/layout/contact_fragment.xml
@@ -73,7 +73,6 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="5dp"
- android:layout_marginBottom="10dp"
android:background="@color/gray_7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -463,7 +462,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/actions"
- app:layout_constraintBottom_toBottomOf="parent" />
+ app:layout_constraintBottom_toBottomOf="@id/action_delete" />
+
+
diff --git a/app/src/main/res/layout/contact_new_or_edit_fragment.xml b/app/src/main/res/layout/contact_new_or_edit_fragment.xml
index 3ba1c8d8e..5113ec1e4 100644
--- a/app/src/main/res/layout/contact_new_or_edit_fragment.xml
+++ b/app/src/main/res/layout/contact_new_or_edit_fragment.xml
@@ -67,7 +67,6 @@
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginBottom="10dp"
android:background="@color/gray_7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -257,6 +256,7 @@
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="5dp"
+ android:layout_marginBottom="24dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:text="@={viewModel.jobTitle, default=`Android dev`}"
@@ -265,6 +265,7 @@
android:maxLines="1"
android:background="@drawable/shape_edit_text_background"
android:inputType="text"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/job_title_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>