Using annotations to check which method is called from which thread

This commit is contained in:
Sylvain Berfini 2023-08-19 09:11:43 +02:00
parent 9ad121f7d7
commit 78edc79fc2
55 changed files with 253 additions and 103 deletions

View file

@ -22,6 +22,7 @@ package org.linphone.contacts
import android.content.ContentUris
import android.net.Uri
import android.provider.ContactsContract
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import org.linphone.core.*
@ -33,6 +34,7 @@ class ContactData(val friend: Friend) {
val avatar = getAvatarUri()
private val friendListener = object : FriendListenerStub() {
@WorkerThread
override fun onPresenceReceived(fr: Friend) {
presenceStatus.postValue(fr.consolidatedPresence)
}
@ -47,10 +49,12 @@ class ContactData(val friend: Friend) {
presenceStatus.postValue(ConsolidatedPresence.Offline)
}
@WorkerThread
fun onDestroy() {
friend.removeListener(friendListener)
}
@WorkerThread
private fun getAvatarUri(): Uri? {
val refKey = friend.refKey
if (refKey != null) {

View file

@ -19,6 +19,8 @@
*/
package org.linphone.contacts
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.loader.app.LoaderManager
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Core
@ -34,13 +36,14 @@ class ContactsManager {
companion object {
const val TAG = "[Contacts Manager]"
}
val localFriends = arrayListOf<Friend>()
private val listeners = arrayListOf<ContactsListener>()
private val friendListListener: FriendListListenerStub = object : FriendListListenerStub() {
@WorkerThread
override fun onPresenceReceived(list: FriendList, friends: Array<Friend>) {
// Core thread
Log.i("$TAG Presence received")
for (listener in listeners) {
listener.onContactsLoaded()
@ -49,25 +52,25 @@ class ContactsManager {
}
private val coreListener: CoreListenerStub = object : CoreListenerStub() {
@WorkerThread
override fun onFriendListCreated(core: Core, friendList: FriendList) {
// Core thread
friendList.addListener(friendListListener)
}
@WorkerThread
override fun onFriendListRemoved(core: Core, friendList: FriendList) {
// Core thread
friendList.removeListener(friendListListener)
}
}
@UiThread
fun loadContacts(activity: MainActivity) {
// UI thread
val manager = LoaderManager.getInstance(activity)
manager.restartLoader(0, null, ContactLoader())
}
@UiThread
fun addListener(listener: ContactsListener) {
// UI thread
if (coreContext.isReady()) {
coreContext.postOnCoreThread {
listeners.add(listener)
@ -75,8 +78,8 @@ class ContactsManager {
}
}
@UiThread
fun removeListener(listener: ContactsListener) {
// UI thread
if (coreContext.isReady()) {
coreContext.postOnCoreThread {
listeners.remove(listener)
@ -84,28 +87,28 @@ class ContactsManager {
}
}
@UiThread
fun onContactsLoaded() {
// UI thread
coreContext.postOnCoreThread {
updateLocalContacts()
notifyContactsListChanged()
}
}
@WorkerThread
fun notifyContactsListChanged() {
// Core thread
for (listener in listeners) {
listener.onContactsLoaded()
}
}
@WorkerThread
fun findContactById(id: String): Friend? {
// Core thread
return coreContext.core.defaultFriendList?.findFriendByRefKey(id)
}
@WorkerThread
fun updateLocalContacts() {
// Core thread
Log.i("$TAG Updating local contact(s)")
localFriends.clear()
@ -123,8 +126,8 @@ class ContactsManager {
}
}
@WorkerThread
fun onCoreStarted() {
// Core thread
val core = coreContext.core
core.addListener(coreListener)
for (list in core.friendsLists) {
@ -134,8 +137,8 @@ class ContactsManager {
updateLocalContacts()
}
@WorkerThread
fun onCoreStopped() {
// Core thread
val core = coreContext.core
core.removeListener(coreListener)
for (list in core.friendsLists) {

View file

@ -26,6 +26,9 @@ import android.content.Intent
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import androidx.annotation.AnyThread
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.emoji2.text.EmojiCompat
import java.util.*
import org.linphone.BuildConfig
@ -55,10 +58,12 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
private lateinit var coreThread: Handler
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) {
Log.i("$TAG Global state changed: $state")
}
@WorkerThread
override fun onCallStateChanged(
core: Core,
call: Call,
@ -67,10 +72,14 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
) {
Log.i("$TAG Call state changed [$state]")
if (state == Call.State.OutgoingProgress) {
showCallActivity()
postOnMainThread {
showCallActivity()
}
} else if (state == Call.State.IncomingReceived) {
// TODO FIXME : remove when full screen intent notification
showCallActivity()
postOnMainThread {
showCallActivity()
}
}
}
}
@ -82,6 +91,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
(context as Application).registerActivityLifecycleCallbacks(activityMonitor)
}
@WorkerThread
override fun run() {
Looper.prepare()
@ -115,6 +125,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
Looper.loop()
}
@WorkerThread
override fun destroy() {
core.stop()
contactsManager.onCoreStopped()
@ -126,22 +137,26 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
quitSafely()
}
@AnyThread
fun isReady(): Boolean {
return ::core.isInitialized
}
@AnyThread
fun postOnCoreThread(lambda: (core: Core) -> Unit) {
coreThread.post {
lambda.invoke(core)
}
}
@AnyThread
fun postOnMainThread(lambda: () -> Unit) {
mainThread.post {
lambda.invoke()
}
}
@UiThread
fun onForeground() {
postOnCoreThread {
// We can't rely on defaultAccount?.params?.isPublishEnabled
@ -153,6 +168,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
}
}
@UiThread
fun onBackground() {
postOnCoreThread {
// We can't rely on defaultAccount?.params?.isPublishEnabled
@ -166,13 +182,13 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
}
}
@WorkerThread
fun startCall(
address: Address,
callParams: CallParams? = null,
forceZRTP: Boolean = false,
localAddress: Address? = null
) {
// Core thread
if (!core.isNetworkReachable) {
Log.e("$TAG Network unreachable, abort outgoing call")
return
@ -218,6 +234,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
Log.i("$TAG Starting call $call")
}
@WorkerThread
fun switchCamera() {
val currentDevice = core.videoDevice
Log.i("$TAG Current camera device is $currentDevice")
@ -238,10 +255,12 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
call.update(null)
}
@WorkerThread
fun showSwitchCameraButton(): Boolean {
return core.videoDevicesList.size > 2 // Count StaticImage camera
}
@UiThread
private fun showCallActivity() {
Log.i("$TAG Starting VoIP activity")
val intent = Intent(context, VoipActivity::class.java)
@ -252,6 +271,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
context.startActivity(intent)
}
@WorkerThread
private fun computeUserAgent() {
val deviceName = LinphoneUtils.getDeviceName(context)
val appName = context.getString(org.linphone.R.string.app_name)

View file

@ -21,10 +21,12 @@ package org.linphone.core
import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.WorkerThread
import java.io.File
import java.io.FileOutputStream
import org.linphone.LinphoneApplication.Companion.coreContext
@WorkerThread
class CorePreferences constructor(private val context: Context) {
private var _config: Config? = null
var config: Config

View file

@ -20,6 +20,7 @@
package org.linphone.ui.assistant
import android.os.Bundle
import androidx.annotation.UiThread
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.databinding.DataBindingUtil
@ -27,6 +28,7 @@ import org.linphone.LinphoneApplication
import org.linphone.R
import org.linphone.databinding.AssistantActivityBinding
@UiThread
class AssistantActivity : AppCompatActivity() {
private lateinit var binding: AssistantActivityBinding

View file

@ -23,6 +23,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import org.linphone.R
@ -30,6 +31,7 @@ import org.linphone.databinding.AssistantLoginFragmentBinding
import org.linphone.ui.assistant.viewmodel.AssistantViewModel
import org.linphone.ui.main.fragment.GenericFragment
@UiThread
class LoginFragment : GenericFragment() {
private lateinit var binding: AssistantLoginFragmentBinding

View file

@ -23,10 +23,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.navigation.fragment.findNavController
import org.linphone.databinding.AssistantRegisterFragmentBinding
import org.linphone.ui.main.fragment.GenericFragment
@UiThread
class RegisterFragment : GenericFragment() {
private lateinit var binding: AssistantRegisterFragmentBinding

View file

@ -19,6 +19,8 @@
*/
package org.linphone.ui.assistant.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -53,13 +55,13 @@ class AssistantViewModel : ViewModel() {
private lateinit var newlyCreatedAccount: Account
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onAccountRegistrationStateChanged(
core: Core,
account: Account,
state: RegistrationState?,
message: String
) {
// Core thread
if (account == newlyCreatedAccount) {
Log.i("$TAG Newly created account registration state is [$state] ($message)")
@ -97,8 +99,8 @@ class AssistantViewModel : ViewModel() {
}
}
@UiThread
fun login() {
// UI thread
coreContext.postOnCoreThread { core ->
core.loadConfigFromXml(corePreferences.linphoneDefaultValuesPath)
@ -128,11 +130,12 @@ class AssistantViewModel : ViewModel() {
}
}
@UiThread
fun toggleShowPassword() {
// UI thread
showPassword.value = showPassword.value == false
}
@UiThread
private fun isLoginButtonEnabled(): Boolean {
return username.value.orEmpty().isNotEmpty() && password.value.orEmpty().isNotEmpty()
}

View file

@ -30,6 +30,7 @@ import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.PopupWindow
import androidx.annotation.DrawableRes
import androidx.annotation.UiThread
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.databinding.DataBindingUtil
@ -44,6 +45,7 @@ import org.linphone.ui.assistant.AssistantActivity
import org.linphone.ui.main.viewmodel.DrawerMenuViewModel
import org.linphone.utils.slideInToastFromTopForDuration
@UiThread
class MainActivity : AppCompatActivity() {
companion object {
private const val CONTACTS_PERMISSION_REQUEST = 0

View file

@ -2,6 +2,7 @@ package org.linphone.ui.main.calls.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
@ -57,6 +58,7 @@ class CallsListAdapter(
inner class ViewHolder(
val binding: CallListCellBinding
) : RecyclerView.ViewHolder(binding.root) {
@UiThread
fun bind(callLogModel: CallLogModel) {
with(binding) {
model = callLogModel

View file

@ -28,6 +28,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.PopupWindow
import androidx.annotation.UiThread
import androidx.core.view.doOnPreDraw
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
@ -41,6 +42,7 @@ import org.linphone.ui.main.calls.viewmodel.CallLogViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.Event
@UiThread
class CallFragment : GenericFragment() {
private lateinit var binding: CallFragmentBinding

View file

@ -23,6 +23,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.core.view.doOnPreDraw
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
@ -33,6 +34,7 @@ import org.linphone.databinding.CallsFragmentBinding
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.SlidingPaneBackPressedCallback
@UiThread
class CallsFragment : GenericFragment() {
private lateinit var binding: CallsFragmentBinding

View file

@ -30,6 +30,7 @@ import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.PopupWindow
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
@ -46,6 +47,7 @@ import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.DialogUtils
import org.linphone.utils.Event
@UiThread
class CallsListFragment : GenericFragment() {
private lateinit var binding: CallsListFragmentBinding

View file

@ -24,9 +24,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.linphone.databinding.CallsListLongPressMenuBinding
@UiThread
class CallsListMenuDialogFragment(
private val onDismiss: (() -> Unit)? = null,
private val onCopyNumberOrAddressToClipboard: (() -> Unit)? = null,

View file

@ -23,6 +23,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
@ -61,8 +62,8 @@ class StartCallFragment : GenericFragment() {
private lateinit var suggestionsAdapter: ContactsListAdapter
private val listener = object : ContactNumberOrAddressClickListener {
@UiThread
override fun onClicked(address: Address?) {
// UI thread
if (address != null) {
coreContext.postOnCoreThread {
coreContext.startCall(address)
@ -70,8 +71,8 @@ class StartCallFragment : GenericFragment() {
}
}
@UiThread
override fun onLongPress(model: ContactNumberOrAddressModel) {
// UI thread
}
}

View file

@ -1,6 +1,7 @@
package org.linphone.ui.main.calls.model
import androidx.annotation.IntegerRes
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Call.Dir
@ -59,8 +60,8 @@ class CallLogModel(private val callLog: CallLog) {
iconResId.postValue(LinphoneUtils.getIconResId(callLog.status, callLog.dir))
}
@UiThread
fun delete() {
// UI thread
coreContext.postOnCoreThread { core ->
core.removeCallLog(callLog)
}

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.calls.model
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import org.linphone.utils.Event
@ -27,13 +28,13 @@ class ConfirmationDialogModel() {
val confirmRemovalEvent = MutableLiveData<Event<Boolean>>()
@UiThread
fun dismiss() {
// UI thread
dismissEvent.value = Event(true)
}
@UiThread
fun confirmRemoval() {
// UI thread
confirmRemovalEvent.value = Event(true)
}
}

View file

@ -1,5 +1,6 @@
package org.linphone.ui.main.calls.viewmodel
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
@ -24,8 +25,8 @@ class CallLogViewModel : ViewModel() {
private lateinit var address: Address
@UiThread
fun findCallLogByCallId(callId: String) {
// UI thread
coreContext.postOnCoreThread { core ->
val callLog = core.findCallLogFromCallId(callId)
if (callLog != null) {
@ -47,8 +48,8 @@ class CallLogViewModel : ViewModel() {
}
}
@UiThread
fun deleteHistory() {
// UI thread
coreContext.postOnCoreThread { core ->
for (model in historyCallLogs.value.orEmpty()) {
core.removeCallLog(model.callLog)
@ -57,8 +58,8 @@ class CallLogViewModel : ViewModel() {
}
}
@UiThread
fun startAudioCall() {
// UI thread
coreContext.postOnCoreThread { core ->
val params = core.createCallParams(null)
params?.isVideoEnabled = false
@ -66,8 +67,8 @@ class CallLogViewModel : ViewModel() {
}
}
@UiThread
fun startVideoCall() {
// UI thread
coreContext.postOnCoreThread { core ->
val params = core.createCallParams(null)
params?.isVideoEnabled = true
@ -75,8 +76,8 @@ class CallLogViewModel : ViewModel() {
}
}
@UiThread
fun sendMessage() {
// UI thread
// TODO
}
}

View file

@ -19,6 +19,8 @@
*/
package org.linphone.ui.main.calls.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
@ -51,6 +53,7 @@ class CallsListViewModel : ViewModel() {
}
}
@UiThread
override fun onCleared() {
super.onCleared()
@ -59,8 +62,8 @@ class CallsListViewModel : ViewModel() {
}
}
@UiThread
fun applyFilter(filter: String = currentFilter) {
// UI thread
currentFilter = filter
coreContext.postOnCoreThread {
@ -68,8 +71,8 @@ class CallsListViewModel : ViewModel() {
}
}
@UiThread
fun removeAllCallLogs() {
// UI thread
coreContext.postOnCoreThread { core ->
for (callLog in core.callLogs) {
core.removeCallLog(callLog)
@ -79,8 +82,8 @@ class CallsListViewModel : ViewModel() {
}
}
@WorkerThread
private fun computeCallLogsList(filter: String) {
// Core thread
val list = arrayListOf<CallLogModel>()
// TODO : filter depending on currently selected account

View file

@ -19,6 +19,8 @@
*/
package org.linphone.ui.main.calls.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.ArrayList
@ -46,16 +48,16 @@ class SuggestionsListViewModel : ViewModel() {
private lateinit var magicSearch: MagicSearch
private val magicSearchListener = object : MagicSearchListenerStub() {
@WorkerThread
override fun onSearchResultsReceived(magicSearch: MagicSearch) {
// Core thread
Log.i("$TAG Magic search contacts available")
processMagicSearchResults(magicSearch.lastSearch)
}
}
private val contactsListener = object : ContactsListener {
@WorkerThread
override fun onContactsLoaded() {
// Core thread
Log.i("$TAG Contacts have been (re)loaded, updating list")
applyFilter(
currentFilter,
@ -77,6 +79,7 @@ class SuggestionsListViewModel : ViewModel() {
applyFilter(currentFilter)
}
@UiThread
override fun onCleared() {
coreContext.postOnCoreThread {
magicSearch.removeListener(magicSearchListener)
@ -85,8 +88,8 @@ class SuggestionsListViewModel : ViewModel() {
super.onCleared()
}
@WorkerThread
fun processMagicSearchResults(results: Array<SearchResult>) {
// Core thread
Log.i("$TAG Processing ${results.size} results")
suggestionsList.value.orEmpty().forEach(ContactAvatarModel::destroy)
@ -113,8 +116,8 @@ class SuggestionsListViewModel : ViewModel() {
Log.i("$TAG Processed ${results.size} results")
}
@UiThread
fun applyFilter(filter: String) {
// UI thread
coreContext.postOnCoreThread {
applyFilter(
filter,
@ -125,13 +128,13 @@ class SuggestionsListViewModel : ViewModel() {
}
}
@WorkerThread
private fun applyFilter(
filter: String,
domain: String,
sources: Int,
aggregation: MagicSearch.Aggregation
) {
// Core thread
if (previousFilter.isNotEmpty() && (
previousFilter.length > filter.length ||
(previousFilter.length == filter.length && previousFilter != filter)
@ -153,8 +156,8 @@ class SuggestionsListViewModel : ViewModel() {
)
}
@WorkerThread
private fun createFriendFromSearchResult(searchResult: SearchResult): Friend {
// Core thread
val searchResultFriend = searchResult.friend
if (searchResultFriend != null) return searchResultFriend

View file

@ -2,6 +2,7 @@ package org.linphone.ui.main.contacts.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
@ -64,6 +65,7 @@ class ContactsListAdapter(
inner class ViewHolder(
val binding: ContactListCellBinding
) : RecyclerView.ViewHolder(binding.root) {
@UiThread
fun bind(contactModel: ContactAvatarModel) {
with(binding) {
model = contactModel
@ -91,6 +93,7 @@ class ContactsListAdapter(
inner class FavouriteViewHolder(
val binding: ContactFavouriteListCellBinding
) : RecyclerView.ViewHolder(binding.root) {
@UiThread
fun bind(contactModel: ContactAvatarModel) {
with(binding) {
model = contactModel

View file

@ -29,6 +29,7 @@ import android.provider.ContactsContract
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.core.content.FileProvider
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider
@ -46,6 +47,7 @@ import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.DialogUtils
import org.linphone.utils.Event
@UiThread
class ContactFragment : GenericFragment() {
companion object {
const val TAG = "[Contact Fragment]"

View file

@ -24,9 +24,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.linphone.databinding.ContactNumberOrAddressLongPressMenuBinding
@UiThread
class ContactNumberOrAddressMenuDialogFragment(
private val isSip: Boolean,
private val onDismiss: (() -> Unit)? = null,

View file

@ -23,6 +23,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.core.view.doOnPreDraw
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
@ -33,6 +34,7 @@ import org.linphone.databinding.ContactsFragmentBinding
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.SlidingPaneBackPressedCallback
@UiThread
class ContactsFragment : GenericFragment() {
private lateinit var binding: ContactsFragmentBinding

View file

@ -25,6 +25,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import androidx.annotation.UiThread
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
@ -37,6 +38,7 @@ import org.linphone.ui.main.contacts.viewmodel.ContactsListViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.Event
@UiThread
class ContactsListFragment : GenericFragment() {
private lateinit var binding: ContactsListFragmentBinding

View file

@ -24,11 +24,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.linphone.databinding.ContactsListLongPressMenuBinding
@UiThread
class ContactsListMenuDialogFragment(
val isFavourite: Boolean,
private val isFavourite: Boolean,
private val onDismiss: (() -> Unit)? = null,
private val onFavourite: (() -> Unit)? = null,
private val onShare: (() -> Unit)? = null,

View file

@ -26,6 +26,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.lifecycleScope
@ -45,6 +46,7 @@ import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.DialogUtils
import org.linphone.utils.FileUtils
@UiThread
class EditContactFragment : GenericFragment() {
companion object {
const val TAG = "[Edit Contact Fragment]"

View file

@ -26,6 +26,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.lifecycleScope
@ -45,6 +46,7 @@ import org.linphone.utils.DialogUtils
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
@UiThread
class NewContactFragment : GenericFragment() {
companion object {
const val TAG = "[New Contact Fragment]"

View file

@ -22,6 +22,7 @@ package org.linphone.ui.main.contacts.model
import android.content.ContentUris
import android.net.Uri
import android.provider.ContactsContract
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import org.linphone.core.ConsolidatedPresence
import org.linphone.core.Friend
@ -51,6 +52,7 @@ class ContactAvatarModel(val friend: Friend) {
val noAlphabet = MutableLiveData<Boolean>()
private val friendListener = object : FriendListenerStub() {
@WorkerThread
override fun onPresenceReceived(fr: Friend) {
Log.d(
"$TAG Presence received for friend [${fr.name}]: [${friend.consolidatedPresence}]"
@ -69,13 +71,13 @@ class ContactAvatarModel(val friend: Friend) {
avatar.postValue(getAvatarUri())
}
@WorkerThread
fun destroy() {
// Core thread
friend.removeListener(friendListener)
}
@WorkerThread
private fun getAvatarUri(): Uri? {
// Core thread
val picturePath = friend.photo
if (!picturePath.isNullOrEmpty()) {
return Uri.parse(picturePath)

View file

@ -19,12 +19,14 @@
*/
package org.linphone.ui.main.contacts.model
import androidx.annotation.UiThread
class ContactDeviceModel(
val name: String,
val trusted: Boolean
) {
@UiThread
fun startCallToDevice() {
// UI thread
// TODO
}
}

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.contacts.model
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import org.linphone.core.Address
@ -31,10 +32,12 @@ class ContactNumberOrAddressModel(
) {
val selected = MutableLiveData<Boolean>()
@UiThread
fun onClicked() {
listener.onClicked(address)
}
@UiThread
fun onLongPress(): Boolean {
selected.value = true
listener.onLongPress(this)

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.contacts.model
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
class NewOrEditNumberOrAddressModel(
@ -37,16 +38,16 @@ class NewOrEditNumberOrAddressModel(
showRemoveButton.postValue(defaultValue.isNotEmpty())
}
@UiThread
fun onValueChanged(newValue: String) {
// UI thread
if (newValue.isNotEmpty() && showRemoveButton.value == false) {
onValueNoLongerEmpty?.invoke()
showRemoveButton.value = true
}
}
@UiThread
fun remove() {
// Core thread
onRemove?.invoke(this)
}
}

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.contacts.model
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import org.linphone.utils.Event
@ -31,6 +32,7 @@ class NumberOrAddressPickerDialogModel(list: List<ContactNumberOrAddressModel>)
sipAddressesAndPhoneNumbers.value = list
}
@UiThread
fun dismiss() {
dismissEvent.value = Event(true)
}

View file

@ -19,6 +19,8 @@
*/
package org.linphone.ui.main.contacts.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
@ -61,8 +63,8 @@ class ContactNewOrEditViewModel() : ViewModel() {
val removeNewNumberOrAddressFieldEvent = MutableLiveData<Event<NewOrEditNumberOrAddressModel>>()
@UiThread
fun findFriendByRefKey(refKey: String?) {
// UI thread
coreContext.postOnCoreThread { core ->
friend = if (refKey.isNullOrEmpty()) {
core.createFriend()
@ -104,8 +106,8 @@ class ContactNewOrEditViewModel() : ViewModel() {
}
}
@UiThread
fun saveChanges() {
// UI thread
coreContext.postOnCoreThread { core ->
var status = Status.OK
@ -179,8 +181,8 @@ class ContactNewOrEditViewModel() : ViewModel() {
}
}
@WorkerThread
private fun addSipAddress(address: String = "", requestFieldToBeAddedInUi: Boolean = false) {
// Core thread
val newModel = NewOrEditNumberOrAddressModel(address, true, {
if (address.isEmpty()) {
addSipAddress(requestFieldToBeAddedInUi = true)
@ -195,8 +197,8 @@ class ContactNewOrEditViewModel() : ViewModel() {
}
}
@WorkerThread
private fun addPhoneNumber(number: String = "", requestFieldToBeAddedInUi: Boolean = false) {
// Core thread
val newModel = NewOrEditNumberOrAddressModel(number, false, {
if (number.isEmpty()) {
addPhoneNumber(requestFieldToBeAddedInUi = true)
@ -211,8 +213,8 @@ class ContactNewOrEditViewModel() : ViewModel() {
}
}
@UiThread
private fun removeModel(model: NewOrEditNumberOrAddressModel) {
// UI thread
if (model.isSip) {
sipAddresses.remove(model)
} else {

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.contacts.viewmodel
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -82,8 +83,8 @@ class ContactViewModel : ViewModel() {
}
private val listener = object : ContactNumberOrAddressClickListener {
@UiThread
override fun onClicked(address: Address?) {
// UI thread
if (address != null) {
coreContext.postOnCoreThread {
coreContext.startCall(address)
@ -91,8 +92,8 @@ class ContactViewModel : ViewModel() {
}
}
@UiThread
override fun onLongPress(model: ContactNumberOrAddressModel) {
// UI thread
showLongPressMenuForNumberOrAddressEvent.value = Event(model)
}
}
@ -104,8 +105,8 @@ class ContactViewModel : ViewModel() {
showDevicesTrust.value = false // TODO FIXME: set it to true when it will work for real
}
@UiThread
fun findContactByRefKey(refKey: String) {
// UI thread
coreContext.postOnCoreThread { core ->
val friend = coreContext.contactsManager.findContactById(refKey)
if (friend != null) {
@ -183,18 +184,18 @@ class ContactViewModel : ViewModel() {
}
}
@UiThread
fun toggleNumbersAndAddressesVisibility() {
// UI thread
showNumbersAndAddresses.value = showNumbersAndAddresses.value == false
}
@UiThread
fun toggleDevicesTrustVisibility() {
// UI thread
showDevicesTrust.value = showDevicesTrust.value == false
}
@UiThread
fun editContact() {
// UI thread
coreContext.postOnCoreThread {
if (::friend.isInitialized) {
val uri = friend.nativeUri
@ -207,8 +208,8 @@ class ContactViewModel : ViewModel() {
}
}
@UiThread
fun exportContactAsVCard() {
// UI thread
coreContext.postOnCoreThread {
if (::friend.isInitialized) {
val vCard = friend.vcard?.asVcard4String()
@ -233,8 +234,8 @@ class ContactViewModel : ViewModel() {
}
}
@UiThread
fun deleteContact() {
// UI thread
coreContext.postOnCoreThread { core ->
if (::friend.isInitialized) {
Log.i("$TAG Deleting friend [$friend]")
@ -244,8 +245,8 @@ class ContactViewModel : ViewModel() {
}
}
@UiThread
fun toggleFavourite() {
// UI thread
coreContext.postOnCoreThread {
friend.edit()
friend.starred = !friend.starred
@ -255,8 +256,8 @@ class ContactViewModel : ViewModel() {
}
}
@UiThread
fun startAudioCall() {
// UI thread
val numbersAndAddresses = sipAddressesAndPhoneNumbers.value.orEmpty()
if (numbersAndAddresses.size == 1) {
val address = numbersAndAddresses.first().address
@ -272,8 +273,8 @@ class ContactViewModel : ViewModel() {
}
}
@UiThread
fun startVideoCall() {
// UI thread
val numbersAndAddresses = sipAddressesAndPhoneNumbers.value.orEmpty()
if (numbersAndAddresses.size == 1) {
val address = numbersAndAddresses.first().address
@ -289,8 +290,8 @@ class ContactViewModel : ViewModel() {
}
}
@UiThread
fun sendMessage() {
// UI thread
if (sipAddressesAndPhoneNumbers.value.orEmpty().size == 1) {
// TODO
} else {

View file

@ -19,6 +19,8 @@
*/
package org.linphone.ui.main.contacts.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.ArrayList
@ -52,16 +54,16 @@ class ContactsListViewModel : ViewModel() {
private lateinit var magicSearch: MagicSearch
private val magicSearchListener = object : MagicSearchListenerStub() {
@WorkerThread
override fun onSearchResultsReceived(magicSearch: MagicSearch) {
// Core thread
Log.i("$TAG Magic search contacts available")
processMagicSearchResults(magicSearch.lastSearch)
}
}
private val contactsListener = object : ContactsListener {
@WorkerThread
override fun onContactsLoaded() {
// Core thread
Log.i("$TAG Contacts have been (re)loaded, updating list")
applyFilter(
currentFilter,
@ -85,6 +87,7 @@ class ContactsListViewModel : ViewModel() {
applyFilter(currentFilter)
}
@UiThread
override fun onCleared() {
coreContext.postOnCoreThread {
magicSearch.removeListener(magicSearchListener)
@ -93,13 +96,13 @@ class ContactsListViewModel : ViewModel() {
super.onCleared()
}
@UiThread
fun toggleFavouritesVisibility() {
// UI thread
showFavourites.value = showFavourites.value == false
}
@WorkerThread
fun processMagicSearchResults(results: Array<SearchResult>) {
// Core thread
Log.i("$TAG Processing ${results.size} results")
contactsList.value.orEmpty().forEach(ContactAvatarModel::destroy)
@ -140,8 +143,8 @@ class ContactsListViewModel : ViewModel() {
Log.i("$TAG Processed ${results.size} results")
}
@UiThread
fun applyFilter(filter: String) {
// UI thread
isListFiltered.value = filter.isNotEmpty()
coreContext.postOnCoreThread {
applyFilter(
@ -153,13 +156,13 @@ class ContactsListViewModel : ViewModel() {
}
}
@WorkerThread
private fun applyFilter(
filter: String,
domain: String,
sources: Int,
aggregation: MagicSearch.Aggregation
) {
// Core thread
if (previousFilter.isNotEmpty() && (
previousFilter.length > filter.length ||
(previousFilter.length == filter.length && previousFilter != filter)
@ -181,8 +184,8 @@ class ContactsListViewModel : ViewModel() {
)
}
@WorkerThread
private fun createFriendFromSearchResult(searchResult: SearchResult): Friend {
// Core thread
val searchResultFriend = searchResult.friend
if (searchResultFriend != null) return searchResultFriend

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.conversations.viewmodel
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
@ -103,6 +104,7 @@ class NewConversationViewModel : ViewModel() {
groupEnabled.value = true
}
@WorkerThread
private fun processMagicSearchResults(results: Array<SearchResult>) {
Log.i("[New Conversation ViewModel] [${results.size}] matching results")
contactsList.value.orEmpty().forEach(ContactData::onDestroy)

View file

@ -24,6 +24,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import org.linphone.LinphoneApplication.Companion.corePreferences
@ -33,6 +34,7 @@ import org.linphone.ui.main.viewmodel.SharedMainViewModel
import org.linphone.utils.Event
import org.linphone.utils.setKeyboardInsetListener
@UiThread
class BottomNavBarFragment : Fragment() {
private lateinit var binding: BottomNavBarBinding

View file

@ -25,12 +25,14 @@ import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import androidx.annotation.UiThread
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.transition.AutoTransition
import org.linphone.R
import org.linphone.databinding.EmptyFragmentBinding
@UiThread
class EmptyFragment : Fragment() {
private lateinit var binding: EmptyFragmentBinding

View file

@ -23,6 +23,7 @@ import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.activity.OnBackPressedCallback
import androidx.annotation.UiThread
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
@ -30,6 +31,7 @@ import org.linphone.R
import org.linphone.core.tools.Log
import org.linphone.ui.main.viewmodel.SharedMainViewModel
@UiThread
abstract class GenericFragment : Fragment() {
protected lateinit var sharedViewModel: SharedMainViewModel

View file

@ -23,6 +23,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import org.linphone.R
@ -34,6 +35,7 @@ import org.linphone.utils.Event
import org.linphone.utils.hideKeyboard
import org.linphone.utils.showKeyboard
@UiThread
class TopBarFragment : Fragment() {
private lateinit var binding: TopSearchBarBinding

View file

@ -20,6 +20,8 @@
package org.linphone.ui.main.model
import android.view.View
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Account
@ -45,6 +47,7 @@ class AccountModel(
val isDefault = MutableLiveData<Boolean>()
private val accountListener = object : AccountListenerStub() {
@WorkerThread
override fun onRegistrationStateChanged(
account: Account,
state: RegistrationState?,
@ -72,33 +75,33 @@ class AccountModel(
updateRegistrationState()
}
@WorkerThread
fun destroy() {
// Core thread
account.removeListener(accountListener)
}
@UiThread
fun setAsDefault() {
// UI thread
coreContext.postOnCoreThread { core ->
core.defaultAccount = account
isDefault.postValue(true)
}
}
@UiThread
fun openMenu(view: View) {
// UI thread
onMenuClicked?.invoke(view, account)
}
@UiThread
fun refreshRegister() {
// UI thread
coreContext.postOnCoreThread { core ->
core.refreshRegisters()
}
}
@WorkerThread
private fun updateRegistrationState() {
// Core thread
val state = when (account.state) {
RegistrationState.None, RegistrationState.Cleared -> "Disabled"
RegistrationState.Progress -> "Connection..."

View file

@ -20,6 +20,8 @@
package org.linphone.ui.main.viewmodel
import android.view.View
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
@ -46,13 +48,13 @@ class DrawerMenuViewModel : ViewModel() {
}
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onAccountRegistrationStateChanged(
core: Core,
account: Account,
state: RegistrationState?,
message: String
) {
// Core thread
computeAccountsList()
}
}
@ -64,6 +66,7 @@ class DrawerMenuViewModel : ViewModel() {
}
}
@UiThread
override fun onCleared() {
super.onCleared()
@ -72,18 +75,18 @@ class DrawerMenuViewModel : ViewModel() {
}
}
@UiThread
fun closeDrawerMenu() {
// UI thread
closeDrawerEvent.value = Event(true)
}
@UiThread
fun addAccount() {
// UI thread
startAssistantEvent.value = Event(true)
}
@WorkerThread
private fun computeAccountsList() {
// Core thread
accounts.value.orEmpty().forEach(AccountModel::destroy)
val list = arrayListOf<AccountModel>()

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.viewmodel
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
@ -53,6 +54,7 @@ class TopBarViewModel : ViewModel() {
}
}
@UiThread
override fun onCleared() {
super.onCleared()
@ -61,26 +63,26 @@ class TopBarViewModel : ViewModel() {
}
}
@UiThread
fun openDrawerMenu() {
// UI thread
openDrawerMenuEvent.value = Event(true)
}
@UiThread
fun openSearchBar() {
// UI thread
searchBarVisible.value = true
focusSearchBarEvent.value = Event(true)
}
@UiThread
fun closeSearchBar() {
// UI thread
clearFilter()
searchBarVisible.value = false
focusSearchBarEvent.value = Event(false)
}
@UiThread
fun clearFilter() {
// UI thread
searchFilter.value = ""
}
}

View file

@ -20,6 +20,7 @@
package org.linphone.ui.voip
import android.os.Bundle
import androidx.annotation.UiThread
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
@ -35,6 +36,7 @@ import org.linphone.ui.voip.fragment.IncomingCallFragmentDirections
import org.linphone.ui.voip.fragment.OutgoingCallFragmentDirections
import org.linphone.ui.voip.viewmodel.CallsViewModel
@UiThread
class VoipActivity : AppCompatActivity() {
private lateinit var binding: VoipActivityBinding

View file

@ -25,6 +25,7 @@ import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.lifecycle.ViewModelProvider
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
@ -35,6 +36,7 @@ import org.linphone.ui.voip.viewmodel.CurrentCallViewModel
import org.linphone.utils.DialogUtils
import org.linphone.utils.slideInToastFromTop
@UiThread
class ActiveCallFragment : GenericFragment() {
private lateinit var binding: VoipActiveCallFragmentBinding

View file

@ -24,11 +24,13 @@ import android.os.SystemClock
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.lifecycle.ViewModelProvider
import org.linphone.databinding.VoipIncomingCallFragmentBinding
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.ui.voip.viewmodel.CurrentCallViewModel
@UiThread
class IncomingCallFragment : GenericFragment() {
private lateinit var binding: VoipIncomingCallFragmentBinding

View file

@ -24,11 +24,13 @@ import android.os.SystemClock
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.lifecycle.ViewModelProvider
import org.linphone.databinding.VoipOutgoingCallFragmentBinding
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.ui.voip.viewmodel.CurrentCallViewModel
@UiThread
class OutgoingCallFragment : GenericFragment() {
private lateinit var binding: VoipOutgoingCallFragmentBinding

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.voip.model
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.Random
@ -77,10 +78,12 @@ class ZrtpSasConfirmationDialogModel(
letters4.value = if (correctLetters == 3) authTokenToListen else randomLetters4
}
@UiThread
fun dismiss() {
dismissEvent.value = Event(true)
}
@UiThread
fun lettersClicked(letters: MutableLiveData<String>) {
val verified = letters.value == authTokenToListen
Log.i(

View file

@ -25,9 +25,11 @@ import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import androidx.annotation.UiThread
import org.linphone.R
import org.linphone.mediastream.video.capture.CaptureTextureView
@UiThread
class RoundCornersTextureView : CaptureTextureView {
private var mRadius: Float = 0f

View file

@ -19,6 +19,8 @@
*/
package org.linphone.ui.voip.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
@ -38,19 +40,19 @@ class CallsViewModel : ViewModel() {
val noMoreCallEvent = MutableLiveData<Event<Boolean>>()
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onLastCallEnded(core: Core) {
// Core thread
Log.i("[Calls ViewModel] No more call, leaving VoIP activity")
noMoreCallEvent.postValue(Event(true))
}
@WorkerThread
override fun onCallStateChanged(
core: Core,
call: Call,
state: Call.State,
message: String
) {
// Core thread
if (call == core.currentCall || core.currentCall == null) {
when (call.state) {
Call.State.Connected -> {
@ -89,6 +91,7 @@ class CallsViewModel : ViewModel() {
}
}
@UiThread
override fun onCleared() {
super.onCleared()

View file

@ -20,6 +20,8 @@
package org.linphone.ui.voip.viewmodel
import android.animation.ValueAnimator
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.Locale
@ -91,10 +93,12 @@ class CurrentCallViewModel() : ViewModel() {
private lateinit var call: Call
private val callListener = object : CallListenerStub() {
@WorkerThread
override fun onEncryptionChanged(call: Call, on: Boolean, authenticationToken: String?) {
updateEncryption()
}
@WorkerThread
override fun onStateChanged(call: Call, state: Call.State?, message: String) {
if (LinphoneUtils.isCallOutgoing(call.state)) {
isVideoEnabled.postValue(call.params.isVideoEnabled)
@ -124,6 +128,7 @@ class CurrentCallViewModel() : ViewModel() {
}
}
@UiThread
override fun onCleared() {
super.onCleared()
@ -134,24 +139,24 @@ class CurrentCallViewModel() : ViewModel() {
}
}
@UiThread
fun answer() {
// UI thread
coreContext.postOnCoreThread {
Log.i("$TAG Answering call [$call]")
call.accept()
}
}
@UiThread
fun hangUp() {
// UI thread
coreContext.postOnCoreThread {
Log.i("$TAG Terminating call [$call]")
call.terminate()
}
}
@UiThread
fun updateZrtpSas(verified: Boolean) {
// UI thread
coreContext.postOnCoreThread {
if (::call.isInitialized) {
call.authenticationTokenVerified = verified
@ -159,8 +164,8 @@ class CurrentCallViewModel() : ViewModel() {
}
}
@UiThread
fun toggleMuteMicrophone() {
// UI thread
// TODO: check record audio permission
coreContext.postOnCoreThread {
call.microphoneMuted = !call.microphoneMuted
@ -168,13 +173,13 @@ class CurrentCallViewModel() : ViewModel() {
}
}
@UiThread
fun changeAudioOutputDevice() {
// UI thread
// TODO: display list of all output devices
}
@UiThread
fun toggleVideo() {
// UI thread
// TODO: check video permission
coreContext.postOnCoreThread { core ->
@ -202,17 +207,21 @@ class CurrentCallViewModel() : ViewModel() {
}
}
@UiThread
fun switchCamera() {
coreContext.switchCamera()
coreContext.postOnCoreThread {
coreContext.switchCamera()
}
}
@UiThread
fun toggleFullScreen() {
if (fullScreenMode.value == false && isVideoEnabled.value == false) return
fullScreenMode.value = fullScreenMode.value != true
}
@UiThread
fun toggleExpandActionsMenu() {
// UI thread
isActionsMenuExpanded.value = isActionsMenuExpanded.value == false
if (isActionsMenuExpanded.value == true) {
@ -220,9 +229,9 @@ class CurrentCallViewModel() : ViewModel() {
} else {
extraButtonsMenuAnimator.reverse()
}
// toggleExtraActionMenuVisibilityEvent.value = Event(isActionsMenuExpanded.value == true)
}
@WorkerThread
fun forceShowZrtpSasDialog() {
val authToken = call.authenticationToken
if (authToken.orEmpty().isNotEmpty()) {
@ -230,6 +239,7 @@ class CurrentCallViewModel() : ViewModel() {
}
}
@WorkerThread
private fun showZrtpSasDialog(authToken: String) {
val toRead: String
val toListen: String
@ -246,8 +256,8 @@ class CurrentCallViewModel() : ViewModel() {
showZrtpSasDialogEvent.postValue(Event(Pair(toRead, toListen)))
}
@WorkerThread
private fun updateEncryption() {
// Core thread
when (call.currentParams.mediaEncryption) {
MediaEncryption.ZRTP -> {
val authToken = call.authenticationToken
@ -269,8 +279,8 @@ class CurrentCallViewModel() : ViewModel() {
}
}
@WorkerThread
private fun configureCall(call: Call) {
// Core thread
call.addListener(callListener)
if (LinphoneUtils.isCallOutgoing(call.state)) {

View file

@ -28,6 +28,7 @@ import android.view.ViewGroup
import android.view.Window
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import androidx.annotation.UiThread
import androidx.appcompat.widget.AppCompatEditText
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.view.ViewCompat
@ -52,6 +53,7 @@ import org.linphone.ui.main.contacts.model.ContactAvatarModel
* This file contains all the data binding necessary for the app
*/
@UiThread
@BindingAdapter("entries", "layout")
fun <T> setEntries(
viewGroup: ViewGroup,
@ -80,6 +82,7 @@ fun <T> setEntries(
}
}
@UiThread
fun View.showKeyboard(window: Window) {
this.requestFocus()
/*val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
@ -87,11 +90,13 @@ fun View.showKeyboard(window: Window) {
WindowCompat.getInsetsController(window, this).show(WindowInsetsCompat.Type.ime())
}
@UiThread
fun View.hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(this.windowToken, 0)
}
@UiThread
fun View.setKeyboardInsetListener(lambda: (visible: Boolean) -> Unit) {
doOnLayout {
var isKeyboardVisible = ViewCompat.getRootWindowInsets(this)?.isVisible(
@ -115,16 +120,19 @@ fun View.setKeyboardInsetListener(lambda: (visible: Boolean) -> Unit) {
}
}
@UiThread
@BindingAdapter("android:src")
fun ImageView.setSourceImageResource(resource: Int) {
this.setImageResource(resource)
}
@UiThread
@BindingAdapter("android:textStyle")
fun AppCompatTextView.setTypeface(typeface: Int) {
this.setTypeface(null, typeface)
}
@UiThread
@BindingAdapter("android:drawableTint")
fun AppCompatTextView.setDrawableTint(color: Int) {
for (drawable in compoundDrawablesRelative) {
@ -132,9 +140,9 @@ fun AppCompatTextView.setDrawableTint(color: Int) {
}
}
@UiThread
@BindingAdapter("coil")
fun loadPictureWithCoil(imageView: ImageView, file: String?) {
// UI thread
if (file != null) {
imageView.load(file) {
transformations(CircleCropTransformation())
@ -142,9 +150,9 @@ fun loadPictureWithCoil(imageView: ImageView, file: String?) {
}
}
@UiThread
@BindingAdapter("coilContact")
fun loadContactPictureWithCoil2(imageView: ImageView, contact: ContactData?) {
// UI thread
if (contact == null) {
imageView.load(R.drawable.contact_avatar)
} else {
@ -155,9 +163,9 @@ fun loadContactPictureWithCoil2(imageView: ImageView, contact: ContactData?) {
}
}
@UiThread
@BindingAdapter("presenceIcon")
fun ImageView.setPresenceIcon(presence: ConsolidatedPresence?) {
// UI thread
val icon = when (presence) {
ConsolidatedPresence.Online -> R.drawable.led_online
ConsolidatedPresence.DoNotDisturb -> R.drawable.led_do_not_disturb
@ -167,9 +175,9 @@ fun ImageView.setPresenceIcon(presence: ConsolidatedPresence?) {
setImageResource(icon)
}
@UiThread
@BindingAdapter("contactAvatar")
fun AvatarView.loadContactAvatar(contact: ContactAvatarModel?) {
// UI thread
if (contact == null) {
loadImage(R.drawable.contact_avatar)
} else {
@ -188,6 +196,7 @@ fun AvatarView.loadContactAvatar(contact: ContactAvatarModel?) {
}
}
@UiThread
@BindingAdapter("onValueChanged")
fun AppCompatEditText.editTextSetting(lambda: () -> Unit) {
addTextChangedListener(object : TextWatcher {

View file

@ -24,6 +24,7 @@ import android.content.Context
import android.os.Build
import android.provider.Settings
import androidx.annotation.IntegerRes
import androidx.annotation.WorkerThread
import androidx.emoji2.text.EmojiCompat
import java.util.Locale
import org.linphone.LinphoneApplication.Companion.coreContext
@ -52,7 +53,7 @@ class LinphoneUtils {
for (i in split.indices) {
if (split[i].isNotEmpty()) {
try {
if (emoji?.loadState == EmojiCompat.LOAD_STATE_SUCCEEDED && emoji.hasEmojiGlyph(
if (emoji.loadState == EmojiCompat.LOAD_STATE_SUCCEEDED && emoji.hasEmojiGlyph(
split[i]
)
) {
@ -78,6 +79,7 @@ class LinphoneUtils {
return initials
}
@WorkerThread
fun getDisplayName(address: Address?): String {
if (address == null) return "[null]"
if (address.displayName == null) {
@ -86,7 +88,7 @@ class LinphoneUtils {
}
val localDisplayName = account?.params?.identityAddress?.displayName
// Do not return an empty local display name
if (localDisplayName != null && localDisplayName.isNotEmpty()) {
if (!localDisplayName.isNullOrEmpty()) {
return localDisplayName
}
}
@ -130,6 +132,7 @@ class LinphoneUtils {
}
}
@WorkerThread
private fun getChatRoomId(localAddress: Address, remoteAddress: Address): String {
val localSipUri = localAddress.clone()
localSipUri.clean()
@ -138,6 +141,7 @@ class LinphoneUtils {
return "${localSipUri.asStringUriOnly()}~${remoteSipUri.asStringUriOnly()}"
}
@WorkerThread
fun getChatRoomId(chatRoom: ChatRoom): String {
return getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress)
}

View file

@ -23,7 +23,7 @@ import java.text.DateFormat
import java.text.Format
import java.text.SimpleDateFormat
import java.util.*
import org.linphone.LinphoneApplication
import org.linphone.LinphoneApplication.Companion.coreContext
class TimestampUtils {
companion object {
@ -58,7 +58,7 @@ class TimestampUtils {
fun dateToString(date: Long, timestampInSecs: Boolean = true): String {
val dateFormat: Format = android.text.format.DateFormat.getDateFormat(
LinphoneApplication.coreContext.context
coreContext.context
)
val pattern = (dateFormat as SimpleDateFormat).toLocalizedPattern()
@ -73,7 +73,7 @@ class TimestampUtils {
fun timeToString(hour: Int, minutes: Int): String {
val use24hFormat = android.text.format.DateFormat.is24HourFormat(
LinphoneApplication.coreContext.context
coreContext.context
)
val calendar = Calendar.getInstance()
calendar.set(Calendar.HOUR_OF_DAY, hour)
@ -88,7 +88,7 @@ class TimestampUtils {
fun timeToString(time: Long, timestampInSecs: Boolean = true): String {
val use24hFormat = android.text.format.DateFormat.is24HourFormat(
LinphoneApplication.coreContext.context
coreContext.context
)
val calendar = Calendar.getInstance()
calendar.timeInMillis = if (timestampInSecs) time * 1000 else time