Improved startup reactivity

This commit is contained in:
Sylvain Berfini 2024-05-16 21:47:45 +02:00
parent f6545f5641
commit 26e30c6060
4 changed files with 422 additions and 406 deletions

View file

@ -25,6 +25,7 @@ import android.os.Bundle
import android.provider.ContactsContract import android.provider.ContactsContract
import android.util.Patterns import android.util.Patterns
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
import androidx.loader.content.CursorLoader import androidx.loader.content.CursorLoader
import androidx.loader.content.Loader import androidx.loader.content.Loader
@ -56,6 +57,8 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
private const val MIN_INTERVAL_TO_WAIT_BEFORE_REFRESH = 300000L // 5 minutes private const val MIN_INTERVAL_TO_WAIT_BEFORE_REFRESH = 300000L // 5 minutes
} }
val friends = HashMap<String, Friend>()
@MainThread @MainThread
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
val mimeType = ContactsContract.Data.MIMETYPE val mimeType = ContactsContract.Data.MIMETYPE
@ -92,29 +95,71 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
} }
Log.i("$TAG Load finished, found ${cursor.count} entries in cursor") Log.i("$TAG Load finished, found ${cursor.count} entries in cursor")
coreContext.postOnCoreThread { core -> coreContext.postOnCoreThread {
val state = coreContext.core.globalState parseFriends(cursor)
if (state == GlobalState.Shutdown || state == GlobalState.Off) { }
Log.w("$TAG Core is being stopped or already destroyed, abort") }
return@postOnCoreThread
@MainThread
override fun onLoaderReset(loader: Loader<Cursor>) {
Log.i("$TAG Loader reset")
}
@WorkerThread
private fun parseFriends(cursor: Cursor) {
val core = coreContext.core
val state = core.globalState
if (state == GlobalState.Shutdown || state == GlobalState.Off) {
Log.w("$TAG Core is being stopped or already destroyed, abort")
return
} }
val friends = HashMap<String, Friend>()
try { try {
// Cursor can be null now that we are on a different dispatcher according to Crashlytics
val friendsPhoneNumbers = arrayListOf<String>() val friendsPhoneNumbers = arrayListOf<String>()
val friendsAddresses = arrayListOf<Address>() val friendsAddresses = arrayListOf<Address>()
var previousId = "" var previousId = ""
while (cursor != null && !cursor.isClosed && cursor.moveToNext()) {
val contactIdColumn = cursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID)
val mimetypeColumn = cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)
val displayNameColumn = cursor.getColumnIndexOrThrow(
ContactsContract.Data.DISPLAY_NAME_PRIMARY
)
val starredColumn = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.STARRED)
val lookupColumn = cursor.getColumnIndexOrThrow(
ContactsContract.Contacts.LOOKUP_KEY
)
val phoneNumberColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.NUMBER
)
val phoneTypeColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.TYPE
)
val phoneLabelColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.LABEL
)
val normalizedPhoneColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER
)
val sipAddressColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS
)
val companyColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Organization.COMPANY
)
val jobTitleColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Organization.TITLE
)
val givenNameColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
)
val familyNameColumn = cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME
)
while (!cursor.isClosed && cursor.moveToNext()) {
try { try {
val id: String = val id: String = cursor.getString(contactIdColumn)
cursor.getString( val mime: String? = cursor.getString(mimetypeColumn)
cursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID)
)
val mime: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)
)
if (previousId.isEmpty() || previousId != id) { if (previousId.isEmpty() || previousId != id) {
friendsPhoneNumbers.clear() friendsPhoneNumbers.clear()
@ -125,12 +170,7 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
val friend = friends[id] ?: core.createFriend() val friend = friends[id] ?: core.createFriend()
friend.refKey = id friend.refKey = id
if (friend.name.isNullOrEmpty()) { if (friend.name.isNullOrEmpty()) {
val displayName: String? = val displayName: String? = cursor.getString(displayNameColumn)
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.Data.DISPLAY_NAME_PRIMARY
)
)
friend.name = displayName friend.name = displayName
val uri = friend.getNativeContactPictureUri() val uri = friend.getNativeContactPictureUri()
@ -138,18 +178,11 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
friend.photo = uri.toString() friend.photo = uri.toString()
} }
val starred = val starred = cursor.getInt(starredColumn) == 1
cursor.getInt(
cursor.getColumnIndexOrThrow(ContactsContract.Contacts.STARRED)
) == 1
friend.starred = starred friend.starred = starred
val lookupKey = val lookupKey =
cursor.getString( cursor.getString(lookupColumn)
cursor.getColumnIndexOrThrow(
ContactsContract.Contacts.LOOKUP_KEY
)
)
friend.nativeUri = friend.nativeUri =
"${ContactsContract.Contacts.CONTENT_LOOKUP_URI}/$lookupKey" "${ContactsContract.Contacts.CONTENT_LOOKUP_URI}/$lookupKey"
@ -160,30 +193,10 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
when (mime) { when (mime) {
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> { ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
val data1: String? = val data1: String? = cursor.getString(phoneNumberColumn)
cursor.getString( val data2: String? = cursor.getString(phoneTypeColumn)
cursor.getColumnIndexOrThrow( val data3: String? = cursor.getString(phoneLabelColumn)
ContactsContract.CommonDataKinds.Phone.NUMBER val data4: String? = cursor.getString(normalizedPhoneColumn)
)
)
val data2: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.TYPE
)
)
val data3: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.LABEL
)
)
val data4: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER
)
)
val label = val label =
PhoneNumberUtils.addressBookLabelTypeToVcardParamString( PhoneNumberUtils.addressBookLabelTypeToVcardParamString(
@ -218,12 +231,7 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
} }
} }
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> { ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
val sipAddress: String? = val sipAddress: String? = cursor.getString(sipAddressColumn)
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS
)
)
if (sipAddress != null) { if (sipAddress != null) {
val address = core.interpretUrl(sipAddress, false) val address = core.interpretUrl(sipAddress, false)
if (address != null && if (address != null &&
@ -237,22 +245,12 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
} }
} }
ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> { ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> {
val organization: String? = val organization: String? = cursor.getString(companyColumn)
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Organization.COMPANY
)
)
if (organization != null) { if (organization != null) {
friend.organization = organization friend.organization = organization
} }
val job: String? = val job: String? = cursor.getString(jobTitleColumn)
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Organization.TITLE
)
)
if (job != null) { if (job != null) {
friend.jobTitle = job friend.jobTitle = job
} }
@ -260,22 +258,12 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> { ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
val vCard = friend.vcard val vCard = friend.vcard
if (vCard != null) { if (vCard != null) {
val givenName: String? = val givenName: String? = cursor.getString(givenNameColumn)
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
)
)
if (!givenName.isNullOrEmpty()) { if (!givenName.isNullOrEmpty()) {
vCard.givenName = givenName vCard.givenName = givenName
} }
val familyName: String? = val familyName: String? = cursor.getString(familyNameColumn)
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME
)
)
if (!familyName.isNullOrEmpty()) { if (!familyName.isNullOrEmpty()) {
vCard.familyName = familyName vCard.familyName = familyName
} }
@ -289,6 +277,24 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
} }
} }
Log.i("$TAG Contacts parsed, posting another task to handle adding them (or not)")
// Re-post another task to allow other tasks on Core thread
coreContext.postOnCoreThread {
addFriendsIfNeeded()
}
} catch (sde: StaleDataException) {
Log.e("$TAG State Data Exception: $sde")
} catch (ise: IllegalStateException) {
Log.e("$TAG Illegal State Exception: $ise")
} catch (e: Exception) {
Log.e("$TAG Exception: $e")
}
}
@WorkerThread
private fun addFriendsIfNeeded() {
val core = coreContext.core
if (core.globalState == GlobalState.Shutdown || core.globalState == GlobalState.Off) { if (core.globalState == GlobalState.Shutdown || core.globalState == GlobalState.Off) {
Log.w("$TAG Core is being stopped or already destroyed, abort") Log.w("$TAG Core is being stopped or already destroyed, abort")
} else if (friends.isEmpty()) { } else if (friends.isEmpty()) {
@ -296,12 +302,14 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
} else { } else {
Log.i("$TAG ${friends.size} friends fetched") Log.i("$TAG ${friends.size} friends fetched")
val friendsList = core.getFriendListByName(NATIVE_ADDRESS_BOOK_FRIEND_LIST) ?: core.createFriendList() val friendsList = core.getFriendListByName(NATIVE_ADDRESS_BOOK_FRIEND_LIST)
?: core.createFriendList()
if (friendsList.displayName.isNullOrEmpty()) { if (friendsList.displayName.isNullOrEmpty()) {
Log.i( Log.i(
"$TAG Friend list [$NATIVE_ADDRESS_BOOK_FRIEND_LIST] didn't exist yet, let's create it" "$TAG Friend list [$NATIVE_ADDRESS_BOOK_FRIEND_LIST] didn't exist yet, let's create it"
) )
friendsList.isDatabaseStorageEnabled = true // Store them to keep presence info available for push notifications & favorites friendsList.isDatabaseStorageEnabled =
true // Store them to keep presence info available for push notifications & favorites
friendsList.type = FriendList.Type.Default friendsList.type = FriendList.Type.Default
friendsList.displayName = NATIVE_ADDRESS_BOOK_FRIEND_LIST friendsList.displayName = NATIVE_ADDRESS_BOOK_FRIEND_LIST
core.addFriendList(friendsList) core.addFriendList(friendsList)
@ -321,7 +329,8 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
"$TAG Friend [${localFriend.name}] with ref key [${localFriend.refKey}] found in newly fetched batch" "$TAG Friend [${localFriend.name}] with ref key [${localFriend.refKey}] found in newly fetched batch"
) )
localFriend.edit() localFriend.edit()
localFriend.nativeUri = newlyFetchedFriend.nativeUri // Native URI isn't stored in linphone database, needs to be updated localFriend.nativeUri =
newlyFetchedFriend.nativeUri // Native URI isn't stored in linphone database, needs to be updated
// Update basic fields that may have changed // Update basic fields that may have changed
localFriend.name = newlyFetchedFriend.name localFriend.name = newlyFetchedFriend.name
@ -401,18 +410,5 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
Log.i("$TAG Subscription(s) updated") Log.i("$TAG Subscription(s) updated")
coreContext.contactsManager.onNativeContactsLoaded() coreContext.contactsManager.onNativeContactsLoaded()
} }
} catch (sde: StaleDataException) {
Log.e("$TAG State Data Exception: $sde")
} catch (ise: IllegalStateException) {
Log.e("$TAG Illegal State Exception: $ise")
} catch (e: Exception) {
Log.e("$TAG Exception: $e")
}
}
}
@MainThread
override fun onLoaderReset(loader: Loader<Cursor>) {
Log.i("$TAG Loader reset")
} }
} }

View file

@ -145,6 +145,7 @@ class ContactsManager @UiThread constructor() {
@MainThread @MainThread
fun loadContacts(activity: MainActivity) { fun loadContacts(activity: MainActivity) {
Log.i("$TAG Starting contacts loader")
val manager = LoaderManager.getInstance(activity) val manager = LoaderManager.getInstance(activity)
manager.restartLoader(0, null, ContactLoader()) manager.restartLoader(0, null, ContactLoader())
} }

View file

@ -29,14 +29,14 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.Gravity import android.view.Gravity
import androidx.annotation.MainThread import android.view.ViewTreeObserver
import androidx.annotation.UiThread import androidx.annotation.UiThread
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.doOnAttach
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.findNavController import androidx.navigation.findNavController
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
@ -82,6 +82,20 @@ class MainActivity : GenericActivity() {
private var currentlyDisplayedAuthDialog: Dialog? = null private var currentlyDisplayedAuthDialog: Dialog? = null
private var navigatedToDefaultFragment = false
private val destinationListener = object : NavController.OnDestinationChangedListener {
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
Log.i("$TAG Latest visited fragment was restored")
navigatedToDefaultFragment = true
controller.removeOnDestinationChangedListener(this)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// Must be done before the setContentView // Must be done before the setContentView
installSplashScreen() installSplashScreen()
@ -165,14 +179,23 @@ class MainActivity : GenericActivity() {
} }
} }
binding.root.doOnAttach { // Wait for latest visited fragment to be displayed before hiding the splashscreen
binding.root.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
return if (navigatedToDefaultFragment) {
Log.i("$TAG Report UI has been fully drawn (TTFD)") Log.i("$TAG Report UI has been fully drawn (TTFD)")
try { try {
reportFullyDrawn() reportFullyDrawn()
} catch (se: SecurityException) { } catch (se: SecurityException) {
Log.e("$TAG Security exception when doing reportFullyDrawn(): $se") Log.e("$TAG Security exception when doing reportFullyDrawn(): $se")
} }
binding.root.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
false
} }
}
})
coreContext.bearerAuthenticationRequestedEvent.observe(this) { coreContext.bearerAuthenticationRequestedEvent.observe(this) {
it.consume { pair -> it.consume { pair ->
@ -224,8 +247,10 @@ class MainActivity : GenericActivity() {
override fun onPostCreate(savedInstanceState: Bundle?) { override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState) super.onPostCreate(savedInstanceState)
goToLatestVisitedFragment()
if (intent != null) { if (intent != null) {
handleIntent(intent, false) handleIntent(intent)
} else { } else {
// This should never happen! // This should never happen!
Log.e("$TAG onPostCreate called without intent !") Log.e("$TAG onPostCreate called without intent !")
@ -270,7 +295,7 @@ class MainActivity : GenericActivity() {
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
handleIntent(intent, true) handleIntent(intent)
} }
@SuppressLint("RtlHardcoded") @SuppressLint("RtlHardcoded")
@ -294,8 +319,71 @@ class MainActivity : GenericActivity() {
return findNavController(R.id.main_nav_host_fragment) return findNavController(R.id.main_nav_host_fragment)
} }
@MainThread private fun goToLatestVisitedFragment() {
private fun handleIntent(intent: Intent, isNewIntent: Boolean) { try {
// Prevent navigating to default fragment upon rotation (we only want to do it on first start)
if (intent.action == Intent.ACTION_MAIN && intent.type == null && intent.data == null) {
if (viewModel.mainIntentHandled) {
Log.d(
"$TAG Main intent without type nor data was already handled, do nothing"
)
} else {
viewModel.mainIntentHandled = true
}
}
val defaultFragmentId = getPreferences(Context.MODE_PRIVATE).getInt(
DEFAULT_FRAGMENT_KEY,
HISTORY_FRAGMENT_ID
)
Log.i(
"$TAG Trying to navigate to set default destination [$defaultFragmentId]"
)
val args = intent.extras
try {
val navOptionsBuilder = NavOptions.Builder()
navOptionsBuilder.setPopUpTo(R.id.historyListFragment, true)
navOptionsBuilder.setLaunchSingleTop(true)
val navOptions = navOptionsBuilder.build()
when (defaultFragmentId) {
CONTACTS_FRAGMENT_ID -> {
findNavController().addOnDestinationChangedListener(destinationListener)
findNavController().navigate(
R.id.contactsListFragment,
args,
navOptions
)
}
CHAT_FRAGMENT_ID -> {
findNavController().addOnDestinationChangedListener(destinationListener)
findNavController().navigate(
R.id.conversationsListFragment,
args,
navOptions
)
}
MEETINGS_FRAGMENT_ID -> {
findNavController().addOnDestinationChangedListener(destinationListener)
findNavController().navigate(
R.id.meetingsListFragment,
args,
navOptions
)
}
else -> {
Log.i("$TAG Default fragment is the same as the latest visited one")
navigatedToDefaultFragment = true
}
}
} catch (ise: IllegalStateException) {
Log.e("$TAG Can't navigate to Conversations fragment: $ise")
}
} catch (ise: IllegalStateException) {
Log.i("$TAG Failed to handle intent: $ise")
}
}
private fun handleIntent(intent: Intent) {
Log.i( Log.i(
"$TAG Handling intent action [${intent.action}], type [${intent.type}] and data [${intent.data}]" "$TAG Handling intent action [${intent.action}], type [${intent.type}] and data [${intent.data}]"
) )
@ -326,12 +414,11 @@ class MainActivity : GenericActivity() {
} }
} }
else -> { else -> {
handleMainIntent(intent, isNewIntent) handleMainIntent(intent)
} }
} }
} }
@MainThread
private fun handleLocusOrShortcut(id: String) { private fun handleLocusOrShortcut(id: String) {
Log.i("$TAG Found locus ID [$id]") Log.i("$TAG Found locus ID [$id]")
val pair = LinphoneUtils.getLocalAndPeerSipUrisFromChatRoomId(id) val pair = LinphoneUtils.getLocalAndPeerSipUrisFromChatRoomId(id)
@ -345,8 +432,7 @@ class MainActivity : GenericActivity() {
} }
} }
@MainThread private fun handleMainIntent(intent: Intent) {
private fun handleMainIntent(intent: Intent, isNewIntent: Boolean) {
coreContext.postOnCoreThread { core -> coreContext.postOnCoreThread { core ->
if (corePreferences.firstLaunch) { if (corePreferences.firstLaunch) {
Log.i("$TAG First time Linphone 6.0 has been started, showing Welcome activity") Log.i("$TAG First time Linphone 6.0 has been started, showing Welcome activity")
@ -361,18 +447,6 @@ class MainActivity : GenericActivity() {
} }
} else { } else {
coreContext.postOnMainThread { coreContext.postOnMainThread {
// Prevent navigating to default fragment upon rotation (we only want to do it on first start)
if (intent.action == Intent.ACTION_MAIN && intent.type == null && intent.data == null && !isNewIntent) {
if (viewModel.mainIntentHandled) {
Log.d(
"$TAG Main intent without type nor data was already handled, do nothing"
)
return@postOnMainThread
} else {
viewModel.mainIntentHandled = true
}
}
if (intent.hasExtra("Chat")) { if (intent.hasExtra("Chat")) {
Log.i("$TAG Intent has [Chat] extra") Log.i("$TAG Intent has [Chat] extra")
try { try {
@ -404,64 +478,12 @@ class MainActivity : GenericActivity() {
} catch (ise: IllegalStateException) { } catch (ise: IllegalStateException) {
Log.e("$TAG Can't navigate to Conversations fragment: $ise") Log.e("$TAG Can't navigate to Conversations fragment: $ise")
} }
} else if (!isNewIntent) {
try {
val defaultFragmentId = getPreferences(Context.MODE_PRIVATE).getInt(
DEFAULT_FRAGMENT_KEY,
HISTORY_FRAGMENT_ID
)
Log.i(
"$TAG Trying to navigate to set default destination [$defaultFragmentId]"
)
val args = intent.extras
try {
val navOptionsBuilder = NavOptions.Builder()
navOptionsBuilder.setPopUpTo(R.id.historyListFragment, true)
navOptionsBuilder.setLaunchSingleTop(true)
val navOptions = navOptionsBuilder.build()
when (defaultFragmentId) {
HISTORY_FRAGMENT_ID -> {
findNavController().navigate(
R.id.historyListFragment,
args,
navOptions
)
}
CONTACTS_FRAGMENT_ID -> {
findNavController().navigate(
R.id.contactsListFragment,
args,
navOptions
)
}
CHAT_FRAGMENT_ID -> {
findNavController().navigate(
R.id.conversationsListFragment,
args,
navOptions
)
}
MEETINGS_FRAGMENT_ID -> {
findNavController().navigate(
R.id.meetingsListFragment,
args,
navOptions
)
}
}
} catch (ise: IllegalStateException) {
Log.e("$TAG Can't navigate to Conversations fragment: $ise")
}
} catch (ise: IllegalStateException) {
Log.i("$TAG Failed to handle intent: $ise")
}
} }
} }
} }
} }
} }
@MainThread
private fun handleSendIntent(intent: Intent, multiple: Boolean) { private fun handleSendIntent(intent: Intent, multiple: Boolean) {
val parcelablesUri = arrayListOf<Uri>() val parcelablesUri = arrayListOf<Uri>()
@ -550,7 +572,6 @@ class MainActivity : GenericActivity() {
} }
} }
@MainThread
private fun parseShortcutIfAny(intent: Intent): Pair<String, String>? { private fun parseShortcutIfAny(intent: Intent): Pair<String, String>? {
val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID
if (shortcutId != null) { if (shortcutId != null) {
@ -562,7 +583,6 @@ class MainActivity : GenericActivity() {
return null return null
} }
@MainThread
private fun handleCallIntent(intent: Intent) { private fun handleCallIntent(intent: Intent) {
val uri = intent.data?.toString() val uri = intent.data?.toString()
if (uri.isNullOrEmpty()) { if (uri.isNullOrEmpty()) {
@ -595,7 +615,6 @@ class MainActivity : GenericActivity() {
} }
} }
@MainThread
private fun handleConfigIntent(uri: String) { private fun handleConfigIntent(uri: String) {
val remoteConfigUri = uri.substring("linphone-config:".length) val remoteConfigUri = uri.substring("linphone-config:".length)
val url = when { val url = when {

View file

@ -699,6 +699,12 @@ class MessageModel @WorkerThread constructor(
@WorkerThread @WorkerThread
private fun startVoiceRecordPlayer() { private fun startVoiceRecordPlayer() {
if (voiceRecordAudioFocusRequest == null) {
voiceRecordAudioFocusRequest = AudioUtils.acquireAudioFocusForVoiceRecordingOrPlayback(
coreContext.context
)
}
if (isPlayerClosed()) { if (isPlayerClosed()) {
Log.w("$TAG Player closed, let's open it first") Log.w("$TAG Player closed, let's open it first")
initVoiceRecordPlayer() initVoiceRecordPlayer()
@ -712,12 +718,6 @@ class MessageModel @WorkerThread constructor(
) )
} }
if (voiceRecordAudioFocusRequest == null) {
voiceRecordAudioFocusRequest = AudioUtils.acquireAudioFocusForVoiceRecordingOrPlayback(
coreContext.context
)
}
Log.i("$TAG Playing voice record") Log.i("$TAG Playing voice record")
isPlayingVoiceRecord.postValue(true) isPlayingVoiceRecord.postValue(true)
voiceRecordPlayer.start() voiceRecordPlayer.start()