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.util.Patterns
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.loader.app.LoaderManager
import androidx.loader.content.CursorLoader
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
}
val friends = HashMap<String, Friend>()
@MainThread
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
val mimeType = ContactsContract.Data.MIMETYPE
@ -92,322 +95,8 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
}
Log.i("$TAG Load finished, found ${cursor.count} entries in cursor")
coreContext.postOnCoreThread { core ->
val state = coreContext.core.globalState
if (state == GlobalState.Shutdown || state == GlobalState.Off) {
Log.w("$TAG Core is being stopped or already destroyed, abort")
return@postOnCoreThread
}
val friends = HashMap<String, Friend>()
try {
// Cursor can be null now that we are on a different dispatcher according to Crashlytics
val friendsPhoneNumbers = arrayListOf<String>()
val friendsAddresses = arrayListOf<Address>()
var previousId = ""
while (cursor != null && !cursor.isClosed && cursor.moveToNext()) {
try {
val id: String =
cursor.getString(
cursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID)
)
val mime: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)
)
if (previousId.isEmpty() || previousId != id) {
friendsPhoneNumbers.clear()
friendsAddresses.clear()
previousId = id
}
val friend = friends[id] ?: core.createFriend()
friend.refKey = id
if (friend.name.isNullOrEmpty()) {
val displayName: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.Data.DISPLAY_NAME_PRIMARY
)
)
friend.name = displayName
val uri = friend.getNativeContactPictureUri()
if (uri != null) {
friend.photo = uri.toString()
}
val starred =
cursor.getInt(
cursor.getColumnIndexOrThrow(ContactsContract.Contacts.STARRED)
) == 1
friend.starred = starred
val lookupKey =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.Contacts.LOOKUP_KEY
)
)
friend.nativeUri =
"${ContactsContract.Contacts.CONTENT_LOOKUP_URI}/$lookupKey"
friend.isSubscribesEnabled = false
// Disable peer to peer short term presence
friend.incSubscribePolicy = SubscribePolicy.SPDeny
}
when (mime) {
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
val data1: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Phone.NUMBER
)
)
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 =
PhoneNumberUtils.addressBookLabelTypeToVcardParamString(
data2?.toInt()
?: ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM,
data3
)
val number =
if (data1.isNullOrEmpty() ||
!Patterns.PHONE.matcher(data1).matches()
) {
data4 ?: data1
} else {
data1
}
if (number != null) {
if (
friendsPhoneNumbers.find {
PhoneNumberUtils.arePhoneNumberWeakEqual(
it,
number
)
} == null
) {
val phoneNumber = Factory.instance()
.createFriendPhoneNumber(number, label)
friend.addPhoneNumberWithLabel(phoneNumber)
friendsPhoneNumbers.add(number)
}
}
}
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
val sipAddress: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS
)
)
if (sipAddress != null) {
val address = core.interpretUrl(sipAddress, false)
if (address != null &&
friendsAddresses.find {
it.weakEqual(address)
} == null
) {
friend.addAddress(address)
friendsAddresses.add(address)
}
}
}
ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> {
val organization: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Organization.COMPANY
)
)
if (organization != null) {
friend.organization = organization
}
val job: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.Organization.TITLE
)
)
if (job != null) {
friend.jobTitle = job
}
}
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
val vCard = friend.vcard
if (vCard != null) {
val givenName: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
)
)
if (!givenName.isNullOrEmpty()) {
vCard.givenName = givenName
}
val familyName: String? =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME
)
)
if (!familyName.isNullOrEmpty()) {
vCard.familyName = familyName
}
}
}
}
friends[id] = friend
} catch (e: Exception) {
Log.e("$TAG Exception: $e")
}
}
if (core.globalState == GlobalState.Shutdown || core.globalState == GlobalState.Off) {
Log.w("$TAG Core is being stopped or already destroyed, abort")
} else if (friends.isEmpty()) {
Log.w("$TAG No friend created!")
} else {
Log.i("$TAG ${friends.size} friends fetched")
val friendsList = core.getFriendListByName(NATIVE_ADDRESS_BOOK_FRIEND_LIST) ?: core.createFriendList()
if (friendsList.displayName.isNullOrEmpty()) {
Log.i(
"$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.type = FriendList.Type.Default
friendsList.displayName = NATIVE_ADDRESS_BOOK_FRIEND_LIST
core.addFriendList(friendsList)
for (friend in friends.values) {
friendsList.addLocalFriend(friend)
}
Log.i("$TAG Friends added")
} else {
Log.i(
"$TAG Friend list [$NATIVE_ADDRESS_BOOK_FRIEND_LIST] found, synchronizing existing friends with new ones"
)
for (localFriend in friendsList.friends) {
val newlyFetchedFriend = friends[localFriend.refKey]
if (newlyFetchedFriend != null) {
Log.d(
"$TAG Friend [${localFriend.name}] with ref key [${localFriend.refKey}] found in newly fetched batch"
)
localFriend.edit()
localFriend.nativeUri = newlyFetchedFriend.nativeUri // Native URI isn't stored in linphone database, needs to be updated
// Update basic fields that may have changed
localFriend.name = newlyFetchedFriend.name
localFriend.organization = newlyFetchedFriend.organization
localFriend.jobTitle = newlyFetchedFriend.jobTitle
localFriend.photo = newlyFetchedFriend.photo
// Clear local friend phone numbers & add all newly fetched one ones
var atLeastAPhoneNumberWasRemoved = false
for (phoneNumber in localFriend.phoneNumbersWithLabel) {
val found = newlyFetchedFriend.phoneNumbers.find {
it == phoneNumber.phoneNumber
}
if (found == null) {
atLeastAPhoneNumberWasRemoved = true
}
localFriend.removePhoneNumberWithLabel(phoneNumber)
}
for (phoneNumber in newlyFetchedFriend.phoneNumbersWithLabel) {
localFriend.addPhoneNumberWithLabel(phoneNumber)
}
// If at least a phone number was removed, remove all SIP address from local friend before adding all from newly fetched one.
// If none was removed, simply add SIP addresses from fetched contact that aren't already in the local friend.
if (atLeastAPhoneNumberWasRemoved) {
Log.w(
"$TAG At least a phone number was removed from native contact [${localFriend.name}], clearing all SIP addresses from local friend before adding back the ones that still exists"
)
for (sipAddress in localFriend.addresses) {
localFriend.removeAddress(sipAddress)
}
}
// Adding only newly added SIP address(es) in native contact if any
for (sipAddress in newlyFetchedFriend.addresses) {
val found = localFriend.addresses.find {
it.weakEqual(sipAddress)
}
if (found == null) {
localFriend.addAddress(sipAddress)
}
}
localFriend.done()
} else {
Log.i(
"$TAG Friend [${localFriend.name}] with ref key [${localFriend.refKey}] not found in newly fetched batch, removing it"
)
friendsList.removeFriend(localFriend)
}
}
// Check for newly created friends since last sync
val localFriends = friendsList.friends
for (key in friends.keys) {
val found = localFriends.find {
it.refKey == key
}
if (found == null) {
val newFriend = friends[key]
if (newFriend != null) {
Log.i(
"$TAG Friend [${newFriend.name}] with ref key [${newFriend.refKey}] not found in currently stored list, adding it"
)
friendsList.addLocalFriend(newFriend)
} else {
Log.e(
"$TAG Expected to find newly fetched friend with ref key [$key] but was null!"
)
}
}
}
Log.i("$TAG Friends synchronized")
}
friends.clear()
friendsList.updateSubscriptions()
Log.i("$TAG Subscription(s) updated")
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")
}
coreContext.postOnCoreThread {
parseFriends(cursor)
}
}
@ -415,4 +104,311 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
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
}
try {
val friendsPhoneNumbers = arrayListOf<String>()
val friendsAddresses = arrayListOf<Address>()
var previousId = ""
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 {
val id: String = cursor.getString(contactIdColumn)
val mime: String? = cursor.getString(mimetypeColumn)
if (previousId.isEmpty() || previousId != id) {
friendsPhoneNumbers.clear()
friendsAddresses.clear()
previousId = id
}
val friend = friends[id] ?: core.createFriend()
friend.refKey = id
if (friend.name.isNullOrEmpty()) {
val displayName: String? = cursor.getString(displayNameColumn)
friend.name = displayName
val uri = friend.getNativeContactPictureUri()
if (uri != null) {
friend.photo = uri.toString()
}
val starred = cursor.getInt(starredColumn) == 1
friend.starred = starred
val lookupKey =
cursor.getString(lookupColumn)
friend.nativeUri =
"${ContactsContract.Contacts.CONTENT_LOOKUP_URI}/$lookupKey"
friend.isSubscribesEnabled = false
// Disable peer to peer short term presence
friend.incSubscribePolicy = SubscribePolicy.SPDeny
}
when (mime) {
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
val data1: String? = cursor.getString(phoneNumberColumn)
val data2: String? = cursor.getString(phoneTypeColumn)
val data3: String? = cursor.getString(phoneLabelColumn)
val data4: String? = cursor.getString(normalizedPhoneColumn)
val label =
PhoneNumberUtils.addressBookLabelTypeToVcardParamString(
data2?.toInt()
?: ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM,
data3
)
val number =
if (data1.isNullOrEmpty() ||
!Patterns.PHONE.matcher(data1).matches()
) {
data4 ?: data1
} else {
data1
}
if (number != null) {
if (
friendsPhoneNumbers.find {
PhoneNumberUtils.arePhoneNumberWeakEqual(
it,
number
)
} == null
) {
val phoneNumber = Factory.instance()
.createFriendPhoneNumber(number, label)
friend.addPhoneNumberWithLabel(phoneNumber)
friendsPhoneNumbers.add(number)
}
}
}
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
val sipAddress: String? = cursor.getString(sipAddressColumn)
if (sipAddress != null) {
val address = core.interpretUrl(sipAddress, false)
if (address != null &&
friendsAddresses.find {
it.weakEqual(address)
} == null
) {
friend.addAddress(address)
friendsAddresses.add(address)
}
}
}
ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> {
val organization: String? = cursor.getString(companyColumn)
if (organization != null) {
friend.organization = organization
}
val job: String? = cursor.getString(jobTitleColumn)
if (job != null) {
friend.jobTitle = job
}
}
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
val vCard = friend.vcard
if (vCard != null) {
val givenName: String? = cursor.getString(givenNameColumn)
if (!givenName.isNullOrEmpty()) {
vCard.givenName = givenName
}
val familyName: String? = cursor.getString(familyNameColumn)
if (!familyName.isNullOrEmpty()) {
vCard.familyName = familyName
}
}
}
}
friends[id] = friend
} catch (e: Exception) {
Log.e("$TAG Exception: $e")
}
}
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) {
Log.w("$TAG Core is being stopped or already destroyed, abort")
} else if (friends.isEmpty()) {
Log.w("$TAG No friend created!")
} else {
Log.i("$TAG ${friends.size} friends fetched")
val friendsList = core.getFriendListByName(NATIVE_ADDRESS_BOOK_FRIEND_LIST)
?: core.createFriendList()
if (friendsList.displayName.isNullOrEmpty()) {
Log.i(
"$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.type = FriendList.Type.Default
friendsList.displayName = NATIVE_ADDRESS_BOOK_FRIEND_LIST
core.addFriendList(friendsList)
for (friend in friends.values) {
friendsList.addLocalFriend(friend)
}
Log.i("$TAG Friends added")
} else {
Log.i(
"$TAG Friend list [$NATIVE_ADDRESS_BOOK_FRIEND_LIST] found, synchronizing existing friends with new ones"
)
for (localFriend in friendsList.friends) {
val newlyFetchedFriend = friends[localFriend.refKey]
if (newlyFetchedFriend != null) {
Log.d(
"$TAG Friend [${localFriend.name}] with ref key [${localFriend.refKey}] found in newly fetched batch"
)
localFriend.edit()
localFriend.nativeUri =
newlyFetchedFriend.nativeUri // Native URI isn't stored in linphone database, needs to be updated
// Update basic fields that may have changed
localFriend.name = newlyFetchedFriend.name
localFriend.organization = newlyFetchedFriend.organization
localFriend.jobTitle = newlyFetchedFriend.jobTitle
localFriend.photo = newlyFetchedFriend.photo
// Clear local friend phone numbers & add all newly fetched one ones
var atLeastAPhoneNumberWasRemoved = false
for (phoneNumber in localFriend.phoneNumbersWithLabel) {
val found = newlyFetchedFriend.phoneNumbers.find {
it == phoneNumber.phoneNumber
}
if (found == null) {
atLeastAPhoneNumberWasRemoved = true
}
localFriend.removePhoneNumberWithLabel(phoneNumber)
}
for (phoneNumber in newlyFetchedFriend.phoneNumbersWithLabel) {
localFriend.addPhoneNumberWithLabel(phoneNumber)
}
// If at least a phone number was removed, remove all SIP address from local friend before adding all from newly fetched one.
// If none was removed, simply add SIP addresses from fetched contact that aren't already in the local friend.
if (atLeastAPhoneNumberWasRemoved) {
Log.w(
"$TAG At least a phone number was removed from native contact [${localFriend.name}], clearing all SIP addresses from local friend before adding back the ones that still exists"
)
for (sipAddress in localFriend.addresses) {
localFriend.removeAddress(sipAddress)
}
}
// Adding only newly added SIP address(es) in native contact if any
for (sipAddress in newlyFetchedFriend.addresses) {
val found = localFriend.addresses.find {
it.weakEqual(sipAddress)
}
if (found == null) {
localFriend.addAddress(sipAddress)
}
}
localFriend.done()
} else {
Log.i(
"$TAG Friend [${localFriend.name}] with ref key [${localFriend.refKey}] not found in newly fetched batch, removing it"
)
friendsList.removeFriend(localFriend)
}
}
// Check for newly created friends since last sync
val localFriends = friendsList.friends
for (key in friends.keys) {
val found = localFriends.find {
it.refKey == key
}
if (found == null) {
val newFriend = friends[key]
if (newFriend != null) {
Log.i(
"$TAG Friend [${newFriend.name}] with ref key [${newFriend.refKey}] not found in currently stored list, adding it"
)
friendsList.addLocalFriend(newFriend)
} else {
Log.e(
"$TAG Expected to find newly fetched friend with ref key [$key] but was null!"
)
}
}
}
Log.i("$TAG Friends synchronized")
}
friends.clear()
friendsList.updateSubscriptions()
Log.i("$TAG Subscription(s) updated")
coreContext.contactsManager.onNativeContactsLoaded()
}
}
}

View file

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

View file

@ -29,14 +29,14 @@ import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.view.Gravity
import androidx.annotation.MainThread
import android.view.ViewTreeObserver
import androidx.annotation.UiThread
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.doOnAttach
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.findNavController
import kotlinx.coroutines.Deferred
@ -82,6 +82,20 @@ class MainActivity : GenericActivity() {
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?) {
// Must be done before the setContentView
installSplashScreen()
@ -165,14 +179,23 @@ class MainActivity : GenericActivity() {
}
}
binding.root.doOnAttach {
Log.i("$TAG Report UI has been fully drawn (TTFD)")
try {
reportFullyDrawn()
} catch (se: SecurityException) {
Log.e("$TAG Security exception when doing reportFullyDrawn(): $se")
// 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)")
try {
reportFullyDrawn()
} catch (se: SecurityException) {
Log.e("$TAG Security exception when doing reportFullyDrawn(): $se")
}
binding.root.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
false
}
}
}
})
coreContext.bearerAuthenticationRequestedEvent.observe(this) {
it.consume { pair ->
@ -224,8 +247,10 @@ class MainActivity : GenericActivity() {
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
goToLatestVisitedFragment()
if (intent != null) {
handleIntent(intent, false)
handleIntent(intent)
} else {
// This should never happen!
Log.e("$TAG onPostCreate called without intent !")
@ -270,7 +295,7 @@ class MainActivity : GenericActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent, true)
handleIntent(intent)
}
@SuppressLint("RtlHardcoded")
@ -294,8 +319,71 @@ class MainActivity : GenericActivity() {
return findNavController(R.id.main_nav_host_fragment)
}
@MainThread
private fun handleIntent(intent: Intent, isNewIntent: Boolean) {
private fun goToLatestVisitedFragment() {
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(
"$TAG Handling intent action [${intent.action}], type [${intent.type}] and data [${intent.data}]"
)
@ -326,12 +414,11 @@ class MainActivity : GenericActivity() {
}
}
else -> {
handleMainIntent(intent, isNewIntent)
handleMainIntent(intent)
}
}
}
@MainThread
private fun handleLocusOrShortcut(id: String) {
Log.i("$TAG Found locus ID [$id]")
val pair = LinphoneUtils.getLocalAndPeerSipUrisFromChatRoomId(id)
@ -345,8 +432,7 @@ class MainActivity : GenericActivity() {
}
}
@MainThread
private fun handleMainIntent(intent: Intent, isNewIntent: Boolean) {
private fun handleMainIntent(intent: Intent) {
coreContext.postOnCoreThread { core ->
if (corePreferences.firstLaunch) {
Log.i("$TAG First time Linphone 6.0 has been started, showing Welcome activity")
@ -361,18 +447,6 @@ class MainActivity : GenericActivity() {
}
} else {
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")) {
Log.i("$TAG Intent has [Chat] extra")
try {
@ -404,64 +478,12 @@ class MainActivity : GenericActivity() {
} catch (ise: IllegalStateException) {
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) {
val parcelablesUri = arrayListOf<Uri>()
@ -550,7 +572,6 @@ class MainActivity : GenericActivity() {
}
}
@MainThread
private fun parseShortcutIfAny(intent: Intent): Pair<String, String>? {
val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID
if (shortcutId != null) {
@ -562,7 +583,6 @@ class MainActivity : GenericActivity() {
return null
}
@MainThread
private fun handleCallIntent(intent: Intent) {
val uri = intent.data?.toString()
if (uri.isNullOrEmpty()) {
@ -595,7 +615,6 @@ class MainActivity : GenericActivity() {
}
}
@MainThread
private fun handleConfigIntent(uri: String) {
val remoteConfigUri = uri.substring("linphone-config:".length)
val url = when {

View file

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