Compare commits

...

9 commits

12 changed files with 94 additions and 35 deletions

View file

@ -12,6 +12,15 @@ Group changes to describe their impact on the project, as follows:
## [4.7.0] - Unreleased ## [4.7.0] - Unreleased
## [4.6.1] - 2022-02-14
### Fixed
- Quit button not working when background mode was enabled
- Crash when background mode was enabled and service notification channel was disabled
- Crashes while changing audio route
- Crash while fetching contacts
- Crash when rotating the device (SDK fix)
## [4.6.0] - 2022-02-09 ## [4.6.0] - 2022-02-09
### Added ### Added

View file

@ -5,8 +5,8 @@ plugins {
id 'org.jlleitschuh.gradle.ktlint' id 'org.jlleitschuh.gradle.ktlint'
} }
def appVersionName = "4.6.0" def appVersionName = "4.6.1"
def appVersionCode = 40600 // 4.06.00 def appVersionCode = 40601 // 4.06.01
static def getPackageName() { static def getPackageName() {
return "org.linphone" return "org.linphone"

View file

@ -24,7 +24,7 @@ import android.view.View
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import org.linphone.LinphoneApplication import org.linphone.LinphoneApplication
import org.linphone.R import org.linphone.R
import org.linphone.activities.assistant.AssistantActivity import org.linphone.activities.SnackBarActivity
import org.linphone.activities.assistant.viewmodels.* import org.linphone.activities.assistant.viewmodels.*
import org.linphone.activities.navigateToEchoCancellerCalibration import org.linphone.activities.navigateToEchoCancellerCalibration
import org.linphone.activities.navigateToPhoneAccountValidation import org.linphone.activities.navigateToPhoneAccountValidation
@ -99,7 +99,7 @@ class PhoneAccountLinkingFragment : AbstractPhoneFragment<AssistantPhoneAccountL
viewLifecycleOwner viewLifecycleOwner
) { ) {
it.consume { message -> it.consume { message ->
(requireActivity() as AssistantActivity).showSnackBar(message) (requireActivity() as SnackBarActivity).showSnackBar(message)
} }
} }

View file

@ -329,6 +329,11 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
} }
} }
} }
// Prevent this intent to be processed again
intent.action = null
intent.data = null
intent.extras?.clear()
} }
private fun handleTelOrSipUri(uri: Uri) { private fun handleTelOrSipUri(uri: Uri) {

View file

@ -308,6 +308,16 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
} }
chatSendingViewModel.messageSentEvent.observe(
viewLifecycleOwner
) {
it.consume {
Log.i("[Chat Room] Message sent")
// Reset this to ensure sent message will be visible
viewModel.isUserScrollingUp.value = false
}
}
listViewModel.events.observe( listViewModel.events.observe(
viewLifecycleOwner viewLifecycleOwner
) { events -> ) { events ->

View file

@ -76,6 +76,10 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
MutableLiveData<Event<Boolean>>() MutableLiveData<Event<Boolean>>()
} }
val messageSentEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val voiceRecordingProgressBarMax = 10000 val voiceRecordingProgressBarMax = 10000
val isPendingVoiceRecord = MutableLiveData<Boolean>() val isPendingVoiceRecord = MutableLiveData<Boolean>()
@ -261,6 +265,8 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
attachments.value.orEmpty().forEach(ChatMessageAttachmentData::destroy) attachments.value.orEmpty().forEach(ChatMessageAttachmentData::destroy)
attachments.value = arrayListOf() attachments.value = arrayListOf()
textToSend.value = "" textToSend.value = ""
messageSentEvent.value = Event(true)
} }
fun transferMessage(chatMessage: ChatMessage) { fun transferMessage(chatMessage: ChatMessage) {

View file

@ -31,7 +31,6 @@ import androidx.lifecycle.lifecycleScope
import java.io.File import java.io.File
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
import org.linphone.activities.GenericFragment import org.linphone.activities.GenericFragment
import org.linphone.activities.assistant.AssistantActivity import org.linphone.activities.assistant.AssistantActivity
@ -113,12 +112,9 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
Log.i("[Side Menu] Quitting app") Log.i("[Side Menu] Quitting app")
requireActivity().finishAndRemoveTask() requireActivity().finishAndRemoveTask()
if (!corePreferences.keepServiceAlive) { Log.i("[Side Menu] Stopping Core Context")
Log.i("[Side Menu] Stopping Core") coreContext.notificationsManager.stopForegroundNotification()
coreContext.stop() coreContext.stop()
} else {
Log.w("[Side Menu] Keep Service alive setting enabled, don't destroy the Core")
}
} }
onBackPressedCallback.isEnabled = false onBackPressedCallback.isEnabled = false

View file

@ -259,10 +259,16 @@ class Api26Compatibility {
fun changeAudioRouteForTelecomManager(connection: NativeCallWrapper, route: Int): Boolean { fun changeAudioRouteForTelecomManager(connection: NativeCallWrapper, route: Int): Boolean {
Log.i("[Telecom Helper] Changing audio route [$route] on connection ${connection.callId}") Log.i("[Telecom Helper] Changing audio route [$route] on connection ${connection.callId}")
if (connection.callAudioState.route == route) {
val audioState = connection.callAudioState
if (audioState != null && audioState.route == route) {
Log.w("[Telecom Helper] Connection is already using this route") Log.w("[Telecom Helper] Connection is already using this route")
return false return false
} else if (audioState == null) {
Log.w("[Telecom Helper] Failed to retrieve connection's call audio state!")
return false
} }
connection.setAudioRoute(route) connection.setAudioRoute(route)
return true return true
} }

View file

@ -109,14 +109,20 @@ class AsyncContactsLoader(private val context: Context) :
Log.i("[Contacts Loader] Only fetching contacts in default directory") Log.i("[Contacts Loader] Only fetching contacts in default directory")
selection = ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1" selection = ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1"
} }
val cursor: Cursor? = context.contentResolver
.query( val cursor: Cursor? = try {
ContactsContract.Data.CONTENT_URI, context.contentResolver
projection, .query(
selection, ContactsContract.Data.CONTENT_URI,
null, projection,
null selection,
) null,
null
)
} catch (e: Exception) {
Log.e("[Contacts Loader] Failed to get contacts cursor: $e")
null
}
if (cursor != null) { if (cursor != null) {
Log.i("[Contacts Loader] Found ${cursor.count} entries in cursor") Log.i("[Contacts Loader] Found ${cursor.count} entries in cursor")

View file

@ -22,8 +22,11 @@ package org.linphone.core
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver() {
@ -32,18 +35,27 @@ class BootReceiver : BroadcastReceiver() {
val autoStart = corePreferences.autoStart val autoStart = corePreferences.autoStart
Log.i("[Boot Receiver] Device is starting, autoStart is $autoStart") Log.i("[Boot Receiver] Device is starting, autoStart is $autoStart")
if (autoStart) { if (autoStart) {
val serviceIntent = Intent(Intent.ACTION_MAIN).setClass(context, CoreService::class.java) startService(context)
serviceIntent.putExtra("StartForeground", true)
ContextCompat.startForegroundService(context, serviceIntent)
} }
} else if (intent.action.equals(Intent.ACTION_MY_PACKAGE_REPLACED, ignoreCase = true)) { } else if (intent.action.equals(Intent.ACTION_MY_PACKAGE_REPLACED, ignoreCase = true)) {
val autoStart = corePreferences.autoStart val autoStart = corePreferences.autoStart
Log.i("[Boot Receiver] App has been updated, autoStart is $autoStart") Log.i("[Boot Receiver] App has been updated, autoStart is $autoStart")
if (autoStart) { if (autoStart) {
val serviceIntent = Intent(Intent.ACTION_MAIN).setClass(context, CoreService::class.java) startService(context)
serviceIntent.putExtra("StartForeground", true)
ContextCompat.startForegroundService(context, serviceIntent)
} }
} }
} }
private fun startService(context: Context) {
val serviceChannel = context.getString(R.string.notification_channel_service_id)
val notificationManager = NotificationManagerCompat.from(context)
if (Compatibility.getChannelImportance(notificationManager, serviceChannel) == NotificationManagerCompat.IMPORTANCE_NONE) {
Log.w("[Boot Receiver] Service channel is disabled!")
return
}
val serviceIntent = Intent(Intent.ACTION_MAIN).setClass(context, CoreService::class.java)
serviceIntent.putExtra("StartForeground", true)
ContextCompat.startForegroundService(context, serviceIntent)
}
} }

View file

@ -281,6 +281,12 @@ class NotificationsManager(private val context: Context) {
/* Service related */ /* Service related */
fun startForeground() { fun startForeground() {
val serviceChannel = context.getString(R.string.notification_channel_service_id)
if (Compatibility.getChannelImportance(notificationManager, serviceChannel) == NotificationManagerCompat.IMPORTANCE_NONE) {
Log.w("[Notifications Manager] Service channel is disabled!")
return
}
val coreService = service val coreService = service
if (coreService != null) { if (coreService != null) {
startForeground(coreService, useAutoStartDescription = false) startForeground(coreService, useAutoStartDescription = false)
@ -327,6 +333,10 @@ class NotificationsManager(private val context: Context) {
fun startForeground(coreService: CoreService, useAutoStartDescription: Boolean = true) { fun startForeground(coreService: CoreService, useAutoStartDescription: Boolean = true) {
if (serviceNotification == null) { if (serviceNotification == null) {
createServiceNotification(useAutoStartDescription) createServiceNotification(useAutoStartDescription)
if (serviceNotification == null) {
Log.e("[Notifications Manager] Failed to create service notification, aborting foreground service!")
return
}
} }
currentForegroundServiceNotificationId = SERVICE_NOTIF_ID currentForegroundServiceNotificationId = SERVICE_NOTIF_ID
Log.i("[Notifications Manager] Starting service as foreground [$currentForegroundServiceNotificationId]") Log.i("[Notifications Manager] Starting service as foreground [$currentForegroundServiceNotificationId]")
@ -342,7 +352,7 @@ class NotificationsManager(private val context: Context) {
} }
} }
private fun stopForegroundNotification() { fun stopForegroundNotification() {
if (service != null) { if (service != null) {
Log.i("[Notifications Manager] Stopping service as foreground [$currentForegroundServiceNotificationId]") Log.i("[Notifications Manager] Stopping service as foreground [$currentForegroundServiceNotificationId]")
service?.stopForeground(true) service?.stopForeground(true)

View file

@ -96,11 +96,10 @@ class AudioRouteUtils {
types: List<AudioDevice.Type>, types: List<AudioDevice.Type>,
skipTelecom: Boolean = false skipTelecom: Boolean = false
) { ) {
val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull()
if ((call != null || currentCall != null) && !skipTelecom && TelecomHelper.exists()) { if (currentCall != null && !skipTelecom && TelecomHelper.exists()) {
val callToUse = call ?: currentCall
Log.i("[Audio Route Helper] Call provided & Telecom Helper exists, trying to dispatch audio route change through Telecom API") Log.i("[Audio Route Helper] Call provided & Telecom Helper exists, trying to dispatch audio route change through Telecom API")
val connection = TelecomHelper.get().findConnectionForCallId(callToUse.callLog.callId) val connection = TelecomHelper.get().findConnectionForCallId(currentCall.callLog.callId)
if (connection != null) { if (connection != null) {
val route = when (types.first()) { val route = when (types.first()) {
AudioDevice.Type.Earpiece -> CallAudioState.ROUTE_EARPIECE AudioDevice.Type.Earpiece -> CallAudioState.ROUTE_EARPIECE
@ -114,13 +113,13 @@ class AudioRouteUtils {
// but this time with skipTelecom = true // but this time with skipTelecom = true
if (!Compatibility.changeAudioRouteForTelecomManager(connection, route)) { if (!Compatibility.changeAudioRouteForTelecomManager(connection, route)) {
Log.w("[Audio Route Helper] Connection is already using this route internally, make the change!") Log.w("[Audio Route Helper] Connection is already using this route internally, make the change!")
applyAudioRouteChange(callToUse, types) applyAudioRouteChange(currentCall, types)
changeCaptureDeviceToMatchAudioRoute(callToUse, types) changeCaptureDeviceToMatchAudioRoute(currentCall, types)
} }
} else { } else {
Log.w("[Audio Route Helper] Telecom Helper found but no matching connection!") Log.w("[Audio Route Helper] Telecom Helper found but no matching connection!")
applyAudioRouteChange(callToUse, types) applyAudioRouteChange(currentCall, types)
changeCaptureDeviceToMatchAudioRoute(callToUse, types) changeCaptureDeviceToMatchAudioRoute(currentCall, types)
} }
} else { } else {
applyAudioRouteChange(call, types) applyAudioRouteChange(call, types)