diff --git a/app/src/main/java/org/linphone/LinphoneApplication.kt b/app/src/main/java/org/linphone/LinphoneApplication.kt index f6c2c486f..81f84907b 100644 --- a/app/src/main/java/org/linphone/LinphoneApplication.kt +++ b/app/src/main/java/org/linphone/LinphoneApplication.kt @@ -21,6 +21,7 @@ package org.linphone import android.annotation.SuppressLint import android.app.Application +import android.content.ComponentCallbacks2 import androidx.annotation.MainThread import coil.ImageLoader import coil.ImageLoaderFactory @@ -28,6 +29,7 @@ import coil.decode.ImageDecoderDecoder import coil.decode.SvgDecoder import coil.decode.VideoFrameDecoder import coil.disk.DiskCache +import coil.imageLoader import coil.memory.MemoryCache import coil.request.CachePolicy import com.google.android.material.color.DynamicColors @@ -41,6 +43,8 @@ import org.linphone.core.tools.Log @MainThread class LinphoneApplication : Application(), ImageLoaderFactory { companion object { + private const val TAG = "[Linphone Application]" + @SuppressLint("StaticFieldLeak") lateinit var corePreferences: CorePreferences @@ -70,7 +74,7 @@ class LinphoneApplication : Application(), ImageLoaderFactory { Factory.instance().loggingService.setLogLevel(LogLevel.Message) Factory.instance().enableLogcatLogs(corePreferences.printLogsInLogcat) - Log.i("[Linphone Application] Report Core preferences initialized") + Log.i("$TAG Report Core preferences initialized") coreContext = CoreContext(context) coreContext.start() @@ -78,6 +82,26 @@ class LinphoneApplication : Application(), ImageLoaderFactory { DynamicColors.applyToActivitiesIfAvailable(this) } + override fun onLowMemory() { + super.onLowMemory() + Log.w("$TAG onLowMemory !") + } + + override fun onTrimMemory(level: Int) { + Log.w("$TAG onTrimMemory called with level [${trimLevelToString(level)}]($level) !") + when (level) { + ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, + ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL, + ComponentCallbacks2.TRIM_MEMORY_MODERATE, + ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> { + Log.i("$TAG Memory trim required, clearing imageLoader memory cache") + imageLoader.memoryCache?.clear() + } + else -> {} + } + super.onTrimMemory(level) + } + override fun newImageLoader(): ImageLoader { return ImageLoader.Builder(this) .crossfade(false) @@ -102,4 +126,17 @@ class LinphoneApplication : Application(), ImageLoaderFactory { .memoryCachePolicy(CachePolicy.ENABLED) .build() } + + private fun trimLevelToString(level: Int): String { + return when (level) { + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> "Hidden UI" + ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> "Moderate (Running)" + ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> "Low" + ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> "Critical" + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> "Background" + ComponentCallbacks2.TRIM_MEMORY_MODERATE -> "Moderate" + ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> "Complete" + else -> level.toString() + } + } } diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 554066080..5eb1d7cd7 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -262,27 +262,34 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C Log.i("$TAG Report Core created and started") } - @Deprecated("Deprecated in Java") @WorkerThread - override fun destroy() { - Log.i("$TAG Stopping Core") + private fun destroyCore() { + if (!::core.isInitialized) { + return + } - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - audioManager.unregisterAudioDeviceCallback(audioDeviceCallback) - - core.stop() - - contactsManager.onCoreStopped(core) - telecomManager.onCoreStopped(core) - notificationsManager.onCoreStopped(core) + val state = core.globalState + if (state != GlobalState.On) { + Log.w("$TAG Core is in state [$state], do not continue destroy process") + return + } + Log.w("$TAG Stopping Core and destroying context related objects") postOnMainThread { (context as Application).unregisterActivityLifecycleCallbacks(activityMonitor) } - Log.i("$TAG Core has been stopped, app can gracefully quit") - Factory.instance().loggingService.removeListener(loggingServiceListener) - quitSafely() + val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + audioManager.unregisterAudioDeviceCallback(audioDeviceCallback) + + core.stopAsync() + + contactsManager.onCoreStopped(core) + telecomManager.onCoreStopped(core) + notificationsManager.onCoreStopped(core) + + // It's very unlikely the process will survive until the Core reaches GlobalStateOff sadly + Log.w("$TAG Core is shutting down but probably won't reach Off state") } @AnyThread @@ -334,6 +341,14 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C } } + @UiThread + fun onAppDestroyed() { + postOnCoreThread { + Log.w("$TAG App has been destroyed, stopping Core") + destroyCore() + } + } + @WorkerThread fun isAddressMyself(address: Address): Boolean { val found = core.accountList.find { diff --git a/app/src/main/java/org/linphone/core/CoreForegroundService.kt b/app/src/main/java/org/linphone/core/CoreForegroundService.kt index 8d0c05a49..baaa61d6b 100644 --- a/app/src/main/java/org/linphone/core/CoreForegroundService.kt +++ b/app/src/main/java/org/linphone/core/CoreForegroundService.kt @@ -39,7 +39,6 @@ class CoreForegroundService : CoreService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.i("$TAG onStartCommand") - coreContext.notificationsManager.onServiceStarted(this) return super.onStartCommand(intent, flags, startId) diff --git a/app/src/main/java/org/linphone/utils/ActivityMonitor.kt b/app/src/main/java/org/linphone/utils/ActivityMonitor.kt index 657acc0ab..73d6179de 100644 --- a/app/src/main/java/org/linphone/utils/ActivityMonitor.kt +++ b/app/src/main/java/org/linphone/utils/ActivityMonitor.kt @@ -24,10 +24,15 @@ import android.app.Application.ActivityLifecycleCallbacks import android.os.Bundle import androidx.annotation.UiThread import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.tools.Log import org.linphone.core.tools.service.AndroidDispatcher @UiThread class ActivityMonitor : ActivityLifecycleCallbacks { + companion object { + private const val TAG = "[Activity Monitor]" + } + private val activities = ArrayList() private var mActive = false private var mRunningActivities = 0 @@ -35,14 +40,17 @@ class ActivityMonitor : ActivityLifecycleCallbacks { @Synchronized override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + Log.d("$TAG onActivityCreated [$activity]") if (!activities.contains(activity)) activities.add(activity) } override fun onActivityStarted(activity: Activity) { + Log.d("$TAG onActivityStarted [$activity]") } @Synchronized override fun onActivityResumed(activity: Activity) { + Log.d("$TAG onActivityResumed [$activity]") if (!activities.contains(activity)) { activities.add(activity) } @@ -52,6 +60,7 @@ class ActivityMonitor : ActivityLifecycleCallbacks { @Synchronized override fun onActivityPaused(activity: Activity) { + Log.d("$TAG onActivityPaused [$activity]") if (!activities.contains(activity)) { activities.add(activity) } else { @@ -61,11 +70,17 @@ class ActivityMonitor : ActivityLifecycleCallbacks { } override fun onActivityStopped(activity: Activity) { + Log.d("$TAG onActivityStopped [$activity]") } @Synchronized override fun onActivityDestroyed(activity: Activity) { + Log.d("$TAG onActivityDestroyed [$activity]") activities.remove(activity) + + if (activities.isEmpty()) { + onAppDestroyed() + } } private fun startInactivityChecker() { @@ -91,11 +106,18 @@ class ActivityMonitor : ActivityLifecycleCallbacks { } } + private fun onAppDestroyed() { + Log.w("$TAG onAppDestroyed()") + coreContext.onAppDestroyed() + } + private fun onBackgroundMode() { + Log.i("$TAG onBackgroundMode()") coreContext.onBackground() } private fun onForegroundMode() { + Log.i("$TAG onForegroundMode()") coreContext.onForeground() }