diff --git a/app/src/main/java/org/linphone/contacts/ContactLoader.kt b/app/src/main/java/org/linphone/contacts/ContactLoader.kt index 4071dda3a..ad7bc1773 100644 --- a/app/src/main/java/org/linphone/contacts/ContactLoader.kt +++ b/app/src/main/java/org/linphone/contacts/ContactLoader.kt @@ -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 { private const val MIN_INTERVAL_TO_WAIT_BEFORE_REFRESH = 300000L // 5 minutes } + val friends = HashMap() + @MainThread override fun onCreateLoader(id: Int, args: Bundle?): Loader { val mimeType = ContactsContract.Data.MIMETYPE @@ -92,322 +95,8 @@ class ContactLoader : LoaderManager.LoaderCallbacks { } 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() - try { - // Cursor can be null now that we are on a different dispatcher according to Crashlytics - val friendsPhoneNumbers = arrayListOf() - val friendsAddresses = arrayListOf
() - 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 { override fun onLoaderReset(loader: Loader) { 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() + val friendsAddresses = arrayListOf
() + 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() + } + } } diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt index dd3aa6c58..0809a70f0 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -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()) } diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt index 47c4a4213..499eb7032 100644 --- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt @@ -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() @@ -550,7 +572,6 @@ class MainActivity : GenericActivity() { } } - @MainThread private fun parseShortcutIfAny(intent: Intent): Pair? { 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 { diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt index 2d26e6a68..5b4400ed3 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt @@ -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()