From 55820991f591df197b2054cd2ac64efa7903d6d1 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 4 Nov 2021 11:50:02 +0100 Subject: [PATCH 001/130] Bumped dependencies --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bf4097fe6..60674706e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -215,10 +215,10 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.media:media:1.4.3' - implementation 'androidx.fragment:fragment-ktx:1.4.0-beta01' + implementation 'androidx.fragment:fragment-ktx:1.4.0-rc01' implementation 'androidx.core:core-ktx:1.7.0' - def nav_version = "2.4.0-beta01" + def nav_version = "2.4.0-beta02" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" From d78d67f1ab251a173851ab38b2800e6e0db54632 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 4 Nov 2021 14:06:38 +0100 Subject: [PATCH 002/130] Fixed going back twice from file viewer when pressing top bar back button --- .../activities/main/files/fragments/GenericViewerFragment.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/GenericViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/GenericViewerFragment.kt index 661dd21cc..7367813fa 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/GenericViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/GenericViewerFragment.kt @@ -56,4 +56,8 @@ abstract class GenericViewerFragment : SecureFragment() (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) ?.setContent(content) } + + override fun goBack() { + findNavController().popBackStack() + } } From 170cf1189bdb3370a2fc6e5b396be77c4d305fe7 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 4 Nov 2021 14:32:29 +0100 Subject: [PATCH 003/130] Added missing translatable=false attribute on recently added plural string, required for weblate --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a3879da35..e1dfc7e9b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -223,7 +223,7 @@ Your media volume is low, you may want to increase it %1$d unread message %1$d unread messages - + @string/chat_room_unread_message @string/chat_room_unread_messages From b305a10f0840a13cd4d0593b2ff52cda3721da3e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 5 Nov 2021 09:14:46 +0100 Subject: [PATCH 004/130] Removed DTMF sounds on Dialer, soundcard takes too much time to start to make it useful --- CHANGELOG.md | 1 + .../main/dialer/viewmodels/DialerViewModel.kt | 20 ++----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d361ca4c..0adeca9c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Group changes to describe their impact on the project, as follows: - Going back to the dialer when pressing back in a chat room after clicking on a chat message notification ### Removed +- Dialer will no longer make DTMF sound when pressing digits - Global push notification setting in Network, use the switch in each Account instead ## [4.5.5] - 2021-10-28 diff --git a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt index 04f9bdebe..bccebe240 100644 --- a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt @@ -21,7 +21,6 @@ package org.linphone.activities.main.dialer.viewmodels import android.content.Context import android.os.Vibrator -import android.provider.Settings import android.text.Editable import android.widget.EditText import androidx.lifecycle.MutableLiveData @@ -69,23 +68,8 @@ class DialerViewModel : LogsUploadViewModel() { } enteredUri.value = sb.toString() - if (coreContext.core.callsNb == 0) { - val contentResolver = coreContext.context.contentResolver - try { - if (Settings.System.getInt( - contentResolver, - Settings.System.DTMF_TONE_WHEN_DIALING - ) == 1 - ) { - coreContext.core.playDtmf(key, 1) - - if (vibrator.hasVibrator() && corePreferences.dtmfKeypadVibration) { - Compatibility.eventVibration(vibrator) - } - } - } catch (snfe: Settings.SettingNotFoundException) { - Log.e("[Dialer] Can't play DTMF: $snfe") - } + if (vibrator.hasVibrator() && corePreferences.dtmfKeypadVibration) { + Compatibility.eventVibration(vibrator) } } From 06e07518bb0d4391426b482c8b99d074bdb9ba37 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 5 Nov 2021 14:13:20 +0100 Subject: [PATCH 005/130] Fixed file transfer server URL setting --- .../main/settings/viewmodels/ChatSettingsViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ChatSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ChatSettingsViewModel.kt index 01f78ccf3..4b8de0353 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ChatSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ChatSettingsViewModel.kt @@ -35,7 +35,7 @@ class ChatSettingsViewModel : GenericSettingsViewModel() { val fileSharingUrlListener = object : SettingListenerStub() { override fun onTextValueChanged(newValue: String) { - core.logCollectionUploadServerUrl = newValue + core.fileTransferServer = newValue } } val fileSharingUrl = MutableLiveData() From 4a4d5f5d74c33ead9b496b26a54ddec9850ffa47 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 5 Nov 2021 16:27:22 +0100 Subject: [PATCH 006/130] Added a hidden setting to disable prefering normalized phone numbes when importing contacts from native address book --- app/src/main/java/org/linphone/contact/NativeContact.kt | 8 +++++++- app/src/main/java/org/linphone/core/CorePreferences.kt | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/contact/NativeContact.kt b/app/src/main/java/org/linphone/contact/NativeContact.kt index b2a109227..73a6f5b24 100644 --- a/app/src/main/java/org/linphone/contact/NativeContact.kt +++ b/app/src/main/java/org/linphone/contact/NativeContact.kt @@ -118,7 +118,13 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null) label ).toString() - val number = data4 ?: data1 + // data4 = ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER + // data1 = ContactsContract.CommonDataKinds.Phone.NUMBER + val number = if (corePreferences.preferNormalizedPhoneNumbersFromAddressBook) { + data4 ?: data1 + } else { + data1 ?: data4 + } if (number != null && number.isNotEmpty()) { Log.d("[Native Contact] Found phone number $data1 ($data4), type label is $typeLabel") if (!rawPhoneNumbers.contains(number)) { diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 7b237ada2..30046c867 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -424,6 +424,9 @@ class CorePreferences constructor(private val context: Context) { val fetchContactsFromDefaultDirectory: Boolean get() = config.getBool("app", "fetch_contacts_from_default_directory", true) + val preferNormalizedPhoneNumbersFromAddressBook: Boolean + get() = config.getBool("app", "prefer_normalized_phone_numbers_from_address_book", true) + val hideStaticImageCamera: Boolean get() = config.getBool("app", "hide_static_image_camera", true) From 264865fdc35f8a389f6b50ffdc109dfcdfe99e8f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 5 Nov 2021 17:06:39 +0100 Subject: [PATCH 007/130] Hide reply chat bubble if original message is missing --- app/src/main/res/layout/chat_message_list_cell.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/chat_message_list_cell.xml b/app/src/main/res/layout/chat_message_list_cell.xml index 0d56b3f67..f25d32023 100644 --- a/app/src/main/res/layout/chat_message_list_cell.xml +++ b/app/src/main/res/layout/chat_message_list_cell.xml @@ -166,7 +166,7 @@ android:layout_gravity="@{data.chatMessage.outgoing ? Gravity.RIGHT : Gravity.LEFT}" app:data="@{data.replyData}" app:clickListener="@{replyClickListener}" - android:visibility="@{data.chatMessage.reply ? View.VISIBLE : View.GONE, default=gone}" /> + android:visibility="@{data.replyData != null ? View.VISIBLE : View.GONE, default=gone}" /> Date: Mon, 8 Nov 2021 12:14:18 +0100 Subject: [PATCH 008/130] Keep phone numbers as-is in contact views + fixed prefix not set when creating/connecting an username based linphone SIP account --- .../assistant/viewmodels/AccountLoginViewModel.kt | 13 +++++++++++++ .../viewmodels/EmailAccountValidationViewModel.kt | 14 ++++++++++++++ .../main/java/org/linphone/core/CorePreferences.kt | 4 +++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AccountLoginViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/AccountLoginViewModel.kt index 01d80a054..3067024b3 100644 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AccountLoginViewModel.kt +++ b/app/src/main/java/org/linphone/activities/assistant/viewmodels/AccountLoginViewModel.kt @@ -25,6 +25,7 @@ import org.linphone.R import org.linphone.core.* import org.linphone.core.tools.Log import org.linphone.utils.Event +import org.linphone.utils.PhoneNumberUtils class AccountLoginViewModelFactory(private val accountCreator: AccountCreator) : ViewModelProvider.NewInstanceFactory() { @@ -220,6 +221,18 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM proxyConfig.isPushNotificationAllowed = true + if (proxyConfig.dialPrefix.isNullOrEmpty()) { + val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context) + if (dialPlan != null) { + Log.i("[Assistant] [Account Login] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}") + proxyConfig.edit() + proxyConfig.dialPrefix = dialPlan.countryCallingCode + proxyConfig.done() + } else { + Log.w("[Assistant] [Account Login] Failed to find dial plan") + } + } + Log.i("[Assistant] [Account Login] Proxy config created") return true } diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountValidationViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountValidationViewModel.kt index 6032efaeb..1f67ba43d 100644 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountValidationViewModel.kt +++ b/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountValidationViewModel.kt @@ -22,11 +22,13 @@ package org.linphone.activities.assistant.viewmodels import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import org.linphone.LinphoneApplication import org.linphone.core.AccountCreator import org.linphone.core.AccountCreatorListenerStub import org.linphone.core.ProxyConfig import org.linphone.core.tools.Log import org.linphone.utils.Event +import org.linphone.utils.PhoneNumberUtils class EmailAccountValidationViewModelFactory(private val accountCreator: AccountCreator) : ViewModelProvider.NewInstanceFactory() { @@ -106,6 +108,18 @@ class EmailAccountValidationViewModel(val accountCreator: AccountCreator) : View proxyConfig.isPushNotificationAllowed = true + if (proxyConfig.dialPrefix.isNullOrEmpty()) { + val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(LinphoneApplication.coreContext.context) + if (dialPlan != null) { + Log.i("[Assistant] [Account Validation] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}") + proxyConfig.edit() + proxyConfig.dialPrefix = dialPlan.countryCallingCode + proxyConfig.done() + } else { + Log.w("[Assistant] [Account Validation] Failed to find dial plan") + } + } + Log.i("[Assistant] [Account Validation] Proxy config created") return true } diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 30046c867..d423b10eb 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -424,8 +424,10 @@ class CorePreferences constructor(private val context: Context) { val fetchContactsFromDefaultDirectory: Boolean get() = config.getBool("app", "fetch_contacts_from_default_directory", true) + // From Android Contact APIs we can also retrieve the internationalized phone number + // By default we display the same value as the native address book app val preferNormalizedPhoneNumbersFromAddressBook: Boolean - get() = config.getBool("app", "prefer_normalized_phone_numbers_from_address_book", true) + get() = config.getBool("app", "prefer_normalized_phone_numbers_from_address_book", false) val hideStaticImageCamera: Boolean get() = config.getBool("app", "hide_static_image_camera", true) From 0488d70d0f2c0c555e51cc02d601c52ebaef328e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 9 Nov 2021 09:31:08 +0100 Subject: [PATCH 009/130] This should prevent Android from trying to start DummySyncService --- app/src/main/res/xml/sync_adapter.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/xml/sync_adapter.xml b/app/src/main/res/xml/sync_adapter.xml index 1c9333943..2be6acf4d 100644 --- a/app/src/main/res/xml/sync_adapter.xml +++ b/app/src/main/res/xml/sync_adapter.xml @@ -4,4 +4,6 @@ android:accountType="@string/sync_account_type" android:contentAuthority="com.android.contacts" android:supportsUploading="false" - android:userVisible="true" /> + android:userVisible="true" + android:allowParallelSyncs="false" + android:isAlwaysSyncable="false" /> From 7ff3d7abfe3eb9e151edb6c9b72b6c279658db37 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 9 Nov 2021 11:25:50 +0100 Subject: [PATCH 010/130] Fixed not possible to going back to OutgoingCallActivity when call is in early-media state + fixed UI not showing numpad button when coming back --- .../java/org/linphone/activities/call/OutgoingCallActivity.kt | 3 ++- .../org/linphone/activities/call/viewmodels/CallViewModel.kt | 1 + .../java/org/linphone/contact/DummyAuthenticationService.kt | 2 ++ app/src/main/java/org/linphone/contact/DummySyncService.kt | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt index 5c1c9fe82..e5b25322e 100644 --- a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt +++ b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt @@ -207,7 +207,8 @@ class OutgoingCallActivity : ProximitySensorActivity() { for (call in coreContext.core.calls) { if (call.state == Call.State.OutgoingInit || call.state == Call.State.OutgoingProgress || - call.state == Call.State.OutgoingRinging + call.state == Call.State.OutgoingRinging || + call.state == Call.State.OutgoingEarlyMedia ) { return call } diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt index d779b5fcc..9275ade4a 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt @@ -114,6 +114,7 @@ open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAd call.addListener(listener) isPaused.value = call.state == Call.State.Paused + isOutgoingEarlyMedia.value = call.state == Call.State.OutgoingEarlyMedia } override fun onCleared() { diff --git a/app/src/main/java/org/linphone/contact/DummyAuthenticationService.kt b/app/src/main/java/org/linphone/contact/DummyAuthenticationService.kt index 3cc3cf4b0..001568cab 100644 --- a/app/src/main/java/org/linphone/contact/DummyAuthenticationService.kt +++ b/app/src/main/java/org/linphone/contact/DummyAuthenticationService.kt @@ -28,6 +28,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.IBinder +import org.linphone.core.tools.Log // Below classes are required to be able to create our DummySyncService... internal class DummyAuthenticator(context: Context) : AbstractAccountAuthenticator(context) { @@ -88,6 +89,7 @@ class DummyAuthenticationService : Service() { override fun onCreate() { authenticator = DummyAuthenticator(this) + Log.i("[Dummy Auth Service] Authenticator created") } override fun onBind(intent: Intent): IBinder { diff --git a/app/src/main/java/org/linphone/contact/DummySyncService.kt b/app/src/main/java/org/linphone/contact/DummySyncService.kt index 60cec9642..a040dafca 100644 --- a/app/src/main/java/org/linphone/contact/DummySyncService.kt +++ b/app/src/main/java/org/linphone/contact/DummySyncService.kt @@ -24,6 +24,7 @@ import android.app.Service import android.content.* import android.os.Bundle import android.os.IBinder +import org.linphone.core.tools.Log // Below classes are required to be able to use our own contact MIME type entry... class DummySyncAdapter(context: Context, autoInit: Boolean) : AbstractThreadedSyncAdapter(context, autoInit) { @@ -44,8 +45,10 @@ class DummySyncService : Service() { override fun onCreate() { synchronized(syncAdapterLock) { + Log.i("[Dummy Sync Adapter] Sync Service created") if (syncAdapter == null) { syncAdapter = DummySyncAdapter(applicationContext, true) + Log.i("[Dummy Sync Adapter] Sync Adapter created") } } } From 64a54a0bfbd20ecf114eae4e44a268b34a8f28d4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 10 Nov 2021 11:23:26 +0100 Subject: [PATCH 011/130] Removed launcher activity, replaced by SplashScreen API --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 25 +++---- .../activities/launcher/LauncherActivity.kt | 70 ------------------- .../linphone/activities/main/MainActivity.kt | 3 + app/src/main/res/drawable/launch_screen.xml | 15 ---- .../res/drawable/vector_linphone_logo.xml | 20 ++++++ app/src/main/res/layout/launcher_activity.xml | 65 ----------------- app/src/main/res/values-night/styles.xml | 2 +- app/src/main/res/values/styles.xml | 8 ++- 9 files changed, 42 insertions(+), 167 deletions(-) delete mode 100644 app/src/main/java/org/linphone/activities/launcher/LauncherActivity.kt delete mode 100644 app/src/main/res/drawable/launch_screen.xml create mode 100644 app/src/main/res/drawable/vector_linphone_logo.xml delete mode 100644 app/src/main/res/layout/launcher_activity.xml diff --git a/app/build.gradle b/app/build.gradle index 60674706e..452bdb5ca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -230,6 +230,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.security:security-crypto-ktx:1.1.0-alpha03" + implementation 'androidx.core:core-splashscreen:1.0.0-alpha02' implementation 'com.google.android.material:material:1.4.0' implementation 'com.google.android.flexbox:flexbox:3.0.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7fb8213c6..431fad6d6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,29 +39,24 @@ android:label="${appLabel}" android:roundIcon="@mipmap/ic_launcher_round" android:extractNativeLibs="${extractNativeLibs}" - android:theme="@style/AppTheme" + android:theme="@style/AppSplashScreenTheme" android:allowNativeHeapPointerTagging="false"> - - - - - - - - + + + + + + + diff --git a/app/src/main/java/org/linphone/activities/launcher/LauncherActivity.kt b/app/src/main/java/org/linphone/activities/launcher/LauncherActivity.kt deleted file mode 100644 index d8a206adf..000000000 --- a/app/src/main/java/org/linphone/activities/launcher/LauncherActivity.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.linphone.activities.launcher - -import android.content.Intent -import android.os.Bundle -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericActivity -import org.linphone.activities.main.MainActivity -import org.linphone.core.tools.Log - -class LauncherActivity : GenericActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView(R.layout.launcher_activity) - } - - override fun onStart() { - super.onStart() - coreContext.handler.postDelayed({ onReady() }, 500) - } - - private fun onReady() { - Log.i("[Launcher] Core is ready") - - if (corePreferences.preventInterfaceFromShowingUp) { - Log.w("[Context] We were asked to not show the user interface") - finish() - return - } - - val intent = Intent() - intent.setClass(this, MainActivity::class.java) - - // Propagate current intent action, type and data - if (getIntent() != null) { - val extras = getIntent().extras - if (extras != null) intent.putExtras(extras) - } - intent.action = getIntent().action - intent.type = getIntent().type - intent.data = getIntent().data - - startActivity(intent) - if (corePreferences.enableAnimations) { - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index bd2aabd1e..6a9771e31 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -30,6 +30,7 @@ import android.view.Gravity import android.view.MotionEvent import android.view.View import android.view.inputmethod.InputMethodManager +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.databinding.DataBindingUtil @@ -112,6 +113,8 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val splashScreen = installSplashScreen() + binding = DataBindingUtil.setContentView(this, R.layout.main_activity) binding.lifecycleOwner = this diff --git a/app/src/main/res/drawable/launch_screen.xml b/app/src/main/res/drawable/launch_screen.xml deleted file mode 100644 index 5a3965dfc..000000000 --- a/app/src/main/res/drawable/launch_screen.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_linphone_logo.xml b/app/src/main/res/drawable/vector_linphone_logo.xml new file mode 100644 index 000000000..9c1d994f9 --- /dev/null +++ b/app/src/main/res/drawable/vector_linphone_logo.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/app/src/main/res/layout/launcher_activity.xml b/app/src/main/res/layout/launcher_activity.xml deleted file mode 100644 index 5efc4c664..000000000 --- a/app/src/main/res/layout/launcher_activity.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 6933329f9..83cce0c6d 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -4,7 +4,7 @@ + + From bd3fb48442f0909bf25cca4a63bb0a94ccc8c2d4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 15 Nov 2021 12:01:00 +0100 Subject: [PATCH 018/130] Disabled full-screen mode while in call by default --- app/src/main/java/org/linphone/core/CorePreferences.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 0da1cd467..572ccf296 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -325,7 +325,7 @@ class CorePreferences constructor(private val context: Context) { } var fullScreenCallUI: Boolean - get() = config.getBool("app", "full_screen_call", true) + get() = config.getBool("app", "full_screen_call", false) set(value) { config.setBool("app", "full_screen_call", value) } From 0c4b4d6e3ce45812bf2fef9669984a57e23f8da3 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 15 Nov 2021 12:02:19 +0100 Subject: [PATCH 019/130] Use createWithAdaptiveBitmap instead of createWithBitmap to prevent crash on some devices --- app/src/main/java/org/linphone/contact/Contact.kt | 2 +- app/src/main/java/org/linphone/contact/NativeContact.kt | 2 +- .../java/org/linphone/notifications/NotificationsManager.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/contact/Contact.kt b/app/src/main/java/org/linphone/contact/Contact.kt index 2d370a1eb..f7ff508fa 100644 --- a/app/src/main/java/org/linphone/contact/Contact.kt +++ b/app/src/main/java/org/linphone/contact/Contact.kt @@ -151,7 +151,7 @@ open class Contact : Comparable { if (bm == null) IconCompat.createWithResource( coreContext.context, R.drawable.avatar - ) else IconCompat.createWithBitmap(bm) + ) else IconCompat.createWithAdaptiveBitmap(bm) if (icon != null) { personBuilder.setIcon(icon) } diff --git a/app/src/main/java/org/linphone/contact/NativeContact.kt b/app/src/main/java/org/linphone/contact/NativeContact.kt index 73a6f5b24..b4f2f4a5c 100644 --- a/app/src/main/java/org/linphone/contact/NativeContact.kt +++ b/app/src/main/java/org/linphone/contact/NativeContact.kt @@ -71,7 +71,7 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null) if (bm == null) IconCompat.createWithResource( coreContext.context, R.drawable.avatar - ) else IconCompat.createWithBitmap(bm) + ) else IconCompat.createWithAdaptiveBitmap(bm) if (icon != null) { personBuilder.setIcon(icon) } diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 28c26b141..c7a21f515 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -378,7 +378,7 @@ class NotificationsManager(private val context: Context) { val builder = Person.Builder().setName(displayName) val userIcon = if (picture != null) { - IconCompat.createWithBitmap(picture) + IconCompat.createWithAdaptiveBitmap(picture) } else { IconCompat.createWithResource(context, R.drawable.avatar) } From 610841097c0c85f1bb83bed4b6c2e1448e1c10e4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 15 Nov 2021 12:16:49 +0100 Subject: [PATCH 020/130] Fixed navigation issues in MainActivity --- .../org/linphone/activities/Navigation.kt | 27 +++++++++++++++++++ .../linphone/activities/main/MainActivity.kt | 25 +++++++---------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index 9dd38fc13..3a792eeda 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -47,6 +47,7 @@ import org.linphone.activities.main.settings.fragments.* import org.linphone.activities.main.sidemenu.fragments.SideMenuFragment import org.linphone.contact.NativeContact import org.linphone.core.Address +import org.linphone.core.tools.Log internal fun Fragment.findMasterNavController(): NavController { return parentFragment?.parentFragment?.findNavController() ?: findNavController() @@ -72,6 +73,32 @@ internal fun MainActivity.navigateToDialer(args: Bundle?) { ) } +internal fun MainActivity.navigateToChatRooms(args: Bundle? = null) { + findNavController(R.id.nav_host_fragment).navigate( + R.id.action_global_masterChatRoomsFragment, + args, + popupTo(R.id.masterChatRoomsFragment, true) + ) +} + +internal fun MainActivity.navigateToChatRoom(localAddress: String?, peerAddress: String?) { + val deepLink = "linphone-android://chat-room/$localAddress/$peerAddress" + Log.i("[Main Activity] Starting deep link: $deepLink") + findNavController(R.id.nav_host_fragment).navigate( + Uri.parse(deepLink), + popupTo(R.id.masterChatRoomsFragment, true) + ) +} + +internal fun MainActivity.navigateToContact(contactId: String?) { + val deepLink = "linphone-android://contact/view/$contactId" + Log.i("[Main Activity] Starting deep link: $deepLink") + findNavController(R.id.nav_host_fragment).navigate( + Uri.parse(deepLink), + popupTo(R.id.masterContactsFragment, true) + ) +} + /* Tabs fragment related */ internal fun TabsFragment.navigateToCallHistory() { diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index a24cd6b53..1b5857641 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -49,8 +49,7 @@ import kotlinx.coroutines.* import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R -import org.linphone.activities.GenericActivity -import org.linphone.activities.SnackBarActivity +import org.linphone.activities.* import org.linphone.activities.assistant.AssistantActivity import org.linphone.activities.main.viewmodels.CallOverlayViewModel import org.linphone.activities.main.viewmodels.SharedMainViewModel @@ -255,9 +254,8 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin if (uri != null) { val contactId = coreContext.contactsManager.getAndroidContactIdFromUri(uri) if (contactId != null) { - val deepLink = "linphone-android://contact/view/$contactId" - Log.i("[Main Activity] Found contact URI parameter in intent: $uri, starting deep link: $deepLink") - findNavController(R.id.nav_host_fragment).navigate(Uri.parse(deepLink)) + Log.i("[Main Activity] Found contact URI parameter in intent: $uri") + navigateToContact(contactId) } } } else { @@ -284,9 +282,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin when { intent.hasExtra("ContactId") -> { val id = intent.getStringExtra("ContactId") - val deepLink = "linphone-android://contact/view/$id" - Log.i("[Main Activity] Found contact id parameter in intent: $id, starting deep link: $deepLink") - findNavController(R.id.nav_host_fragment).navigate(Uri.parse(deepLink)) + navigateToContact(id) } intent.hasExtra("Chat") -> { if (corePreferences.disableChat) return @@ -429,12 +425,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin addressToIM = addressToIM.substring("mmsto:".length) } - val peerAddress = coreContext.core.interpretUrl(addressToIM)?.asStringUriOnly() val localAddress = coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly() - val deepLink = "linphone-android://chat-room/$localAddress/$peerAddress" - Log.i("[Main Activity] Starting deep link: $deepLink") - findNavController(R.id.nav_host_fragment).navigate(Uri.parse(deepLink)) + val peerAddress = coreContext.core.interpretUrl(addressToIM)?.asStringUriOnly() + navigateToChatRoom(localAddress, peerAddress) } else { val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID if (shortcutId != null) { @@ -442,7 +436,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin handleLocusOrShortcut(shortcutId) } else { Log.i("[Main Activity] Going into chat rooms list") - findNavController(R.id.nav_host_fragment).navigate(R.id.action_global_masterChatRoomsFragment) + navigateToChatRooms() } } } @@ -452,11 +446,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin if (split.size == 2) { val localAddress = split[0] val peerAddress = split[1] - val deepLink = "linphone-android://chat-room/$localAddress/$peerAddress" - findNavController(R.id.nav_host_fragment).navigate(Uri.parse(deepLink)) + navigateToChatRoom(localAddress, peerAddress) } else { Log.e("[Main Activity] Failed to parse shortcut/locus id: $id") - findNavController(R.id.nav_host_fragment).navigate(R.id.action_global_masterChatRoomsFragment) + navigateToChatRooms() } } From 1b5fc47e7a93fbb31623871eb0b3d26ca28ed033 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 15 Nov 2021 12:19:54 +0100 Subject: [PATCH 021/130] Try to prevent blank screen chat room --- .../main/chat/fragments/DetailChatRoomFragment.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index a589a8c80..8dff727e4 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -120,11 +120,6 @@ class DetailChatRoomFragment : MasterFragment Date: Mon, 15 Nov 2021 14:08:12 +0100 Subject: [PATCH 022/130] Forgot to replace 2 calls to findNavController in MainActivity --- app/src/main/java/org/linphone/activities/Navigation.kt | 3 --- .../java/org/linphone/activities/main/MainActivity.kt | 9 ++++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index 3a792eeda..90d07c331 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -47,7 +47,6 @@ import org.linphone.activities.main.settings.fragments.* import org.linphone.activities.main.sidemenu.fragments.SideMenuFragment import org.linphone.contact.NativeContact import org.linphone.core.Address -import org.linphone.core.tools.Log internal fun Fragment.findMasterNavController(): NavController { return parentFragment?.parentFragment?.findNavController() ?: findNavController() @@ -83,7 +82,6 @@ internal fun MainActivity.navigateToChatRooms(args: Bundle? = null) { internal fun MainActivity.navigateToChatRoom(localAddress: String?, peerAddress: String?) { val deepLink = "linphone-android://chat-room/$localAddress/$peerAddress" - Log.i("[Main Activity] Starting deep link: $deepLink") findNavController(R.id.nav_host_fragment).navigate( Uri.parse(deepLink), popupTo(R.id.masterChatRoomsFragment, true) @@ -92,7 +90,6 @@ internal fun MainActivity.navigateToChatRoom(localAddress: String?, peerAddress: internal fun MainActivity.navigateToContact(contactId: String?) { val deepLink = "linphone-android://contact/view/$contactId" - Log.i("[Main Activity] Starting deep link: $deepLink") findNavController(R.id.nav_host_fragment).navigate( Uri.parse(deepLink), popupTo(R.id.masterContactsFragment, true) diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index 1b5857641..acdff80b1 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -282,6 +282,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin when { intent.hasExtra("ContactId") -> { val id = intent.getStringExtra("ContactId") + Log.i("[Main Activity] Found contact ID in extras: $id") navigateToContact(id) } intent.hasExtra("Chat") -> { @@ -291,10 +292,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin val peerAddress = intent.getStringExtra("RemoteSipUri") val localAddress = intent.getStringExtra("LocalSipUri") Log.i("[Main Activity] Found chat room intent extra: local SIP URI=[$localAddress], peer SIP URI=[$peerAddress]") - findNavController(R.id.nav_host_fragment).navigate(Uri.parse("linphone-android://chat-room/$localAddress/$peerAddress")) + navigateToChatRoom(localAddress, peerAddress) } else { Log.i("[Main Activity] Found chat intent extra, go to chat rooms list") - findNavController(R.id.nav_host_fragment).navigate(R.id.action_global_masterChatRoomsFragment) + navigateToChatRooms() } } intent.hasExtra("Dialer") -> { @@ -428,6 +429,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin val localAddress = coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly() val peerAddress = coreContext.core.interpretUrl(addressToIM)?.asStringUriOnly() + Log.i("[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses") navigateToChatRoom(localAddress, peerAddress) } else { val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID @@ -446,9 +448,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin if (split.size == 2) { val localAddress = split[0] val peerAddress = split[1] + Log.i("[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses, computed from shortcut/locus id") navigateToChatRoom(localAddress, peerAddress) } else { - Log.e("[Main Activity] Failed to parse shortcut/locus id: $id") + Log.e("[Main Activity] Failed to parse shortcut/locus id: $id, going to chat rooms list") navigateToChatRooms() } } From f0b455eb8f0f9b80650501fb62c7c73979190248 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 16 Nov 2021 10:21:29 +0100 Subject: [PATCH 023/130] Fixed camera not turning on during call when granting permission + minor UI fix for history detail while in landscape --- .../activities/call/OutgoingCallActivity.kt | 11 +++++++++- .../call/fragments/ControlsFragment.kt | 22 ++++++++++++++++--- .../call/viewmodels/ControlsViewModel.kt | 12 ++++++---- .../res/layout/history_detail_fragment.xml | 3 ++- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt index e5b25322e..b4223f928 100644 --- a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt +++ b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt @@ -106,7 +106,16 @@ class OutgoingCallActivity : ProximitySensorActivity() { } ) - controlsViewModel.askPermissionEvent.observe( + controlsViewModel.askAudioRecordPermissionEvent.observe( + this, + { + it.consume { permission -> + requestPermissions(arrayOf(permission), 0) + } + } + ) + + controlsViewModel.askCameraPermissionEvent.observe( this, { it.consume { permission -> diff --git a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt index 859b47611..674b31ee8 100644 --- a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt +++ b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt @@ -113,7 +113,7 @@ class ControlsFragment : GenericFragment() { it.consume { if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { Log.i("[Controls Fragment] Asking for WRITE_EXTERNAL_STORAGE permission") - requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) + requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 2) } } } @@ -179,7 +179,7 @@ class ControlsFragment : GenericFragment() { } ) - controlsViewModel.askPermissionEvent.observe( + controlsViewModel.askAudioRecordPermissionEvent.observe( viewLifecycleOwner, { it.consume { permission -> @@ -189,6 +189,16 @@ class ControlsFragment : GenericFragment() { } ) + controlsViewModel.askCameraPermissionEvent.observe( + viewLifecycleOwner, + { + it.consume { permission -> + Log.i("[Controls Fragment] Asking for $permission permission") + requestPermissions(arrayOf(permission), 1) + } + } + ) + controlsViewModel.toggleNumpadEvent.observe( viewLifecycleOwner, { @@ -251,7 +261,13 @@ class ControlsFragment : GenericFragment() { } } } - } else if (requestCode == 1 && grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) { + } else if (requestCode == 1) { + if (grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) { + Log.i("[Controls Fragment] CAMERA permission has been granted") + coreContext.core.reloadVideoDevices() + controlsViewModel.toggleVideo() + } + } else if (requestCode == 2 && grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) { callsViewModel.takeScreenshot() } super.onRequestPermissionsResult(requestCode, permissions, grantResults) diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt index f74bb0471..33d7aac80 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt @@ -87,7 +87,11 @@ class ControlsViewModel : ViewModel() { MutableLiveData>() } - val askPermissionEvent: MutableLiveData> by lazy { + val askAudioRecordPermissionEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val askCameraPermissionEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -176,7 +180,7 @@ class ControlsViewModel : ViewModel() { } if (coreContext.isVideoCallOrConferenceActive() && !PermissionHelper.get().hasCameraPermission()) { - askPermissionEvent.value = Event(Manifest.permission.CAMERA) + askCameraPermissionEvent.value = Event(Manifest.permission.CAMERA) } updateUI() @@ -244,7 +248,7 @@ class ControlsViewModel : ViewModel() { fun toggleMuteMicrophone() { if (!PermissionHelper.get().hasRecordAudioPermission()) { - askPermissionEvent.value = Event(Manifest.permission.RECORD_AUDIO) + askAudioRecordPermissionEvent.value = Event(Manifest.permission.RECORD_AUDIO) return } @@ -279,7 +283,7 @@ class ControlsViewModel : ViewModel() { fun toggleVideo() { if (!PermissionHelper.get().hasCameraPermission()) { - askPermissionEvent.value = Event(Manifest.permission.CAMERA) + askCameraPermissionEvent.value = Event(Manifest.permission.CAMERA) return } diff --git a/app/src/main/res/layout/history_detail_fragment.xml b/app/src/main/res/layout/history_detail_fragment.xml index 4654fcd8e..951213347 100644 --- a/app/src/main/res/layout/history_detail_fragment.xml +++ b/app/src/main/res/layout/history_detail_fragment.xml @@ -80,7 +80,7 @@ android:layout_below="@id/top_bar" android:gravity="center" android:orientation="vertical" - android:paddingTop="20dp" + android:paddingTop="10dp" android:paddingBottom="5dp"> From 329d8b53c5de9d2c657cfeb1b548bcb35f13b592 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 16 Nov 2021 11:11:18 +0100 Subject: [PATCH 024/130] Fixed assistant buttons not disabled in landscape if conditions aren't accepted --- app/src/main/res/layout-land/assistant_welcome_fragment.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/layout-land/assistant_welcome_fragment.xml b/app/src/main/res/layout-land/assistant_welcome_fragment.xml index 8eb656873..d504eb313 100644 --- a/app/src/main/res/layout-land/assistant_welcome_fragment.xml +++ b/app/src/main/res/layout-land/assistant_welcome_fragment.xml @@ -89,6 +89,7 @@ Date: Tue, 16 Nov 2021 13:29:02 +0100 Subject: [PATCH 025/130] Bumped dependencies --- app/build.gradle | 7 ++----- build.gradle | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 452bdb5ca..848982abe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,8 +143,8 @@ android { initWith release resValue "bool", "crashlytics_enabled", crashlyticsEnabled.toString() - if (crashlyticsEnabled) { + if (crashlyticsEnabled) { firebaseCrashlytics { nativeSymbolUploadEnabled true unstrippedNativeLibsDir file(LinphoneSdkBuildDir + '/libs-debug/').toString() @@ -161,14 +161,13 @@ android { resValue "string", "sync_account_type", getPackageName() + ".sync" resValue "string", "file_provider", getPackageName() + ".debug.fileprovider" resValue "string", "linphone_address_mime_type", "vnd.android.cursor.item/vnd." + getPackageName() + ".provider.sip_address" + resValue "bool", "crashlytics_enabled", crashlyticsEnabled.toString() if (!firebaseEnabled) { resValue "string", "gcm_defaultSenderId", "none" } - resValue "bool", "crashlytics_enabled", crashlyticsEnabled.toString() if (crashlyticsEnabled) { - firebaseCrashlytics { nativeSymbolUploadEnabled true unstrippedNativeLibsDir file(LinphoneSdkBuildDir + '/libs-debug/').toString() @@ -211,8 +210,6 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.media:media:1.4.3' implementation 'androidx.fragment:fragment-ktx:1.4.0-rc01' diff --git a/build.gradle b/build.gradle index a8907faaa..2e256b988 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.5.30' - repositories { google() maven { @@ -15,9 +13,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.0.3' classpath 'com.google.gms:google-services:4.3.10' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0" - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.0' } } From 07b6d07b7faa2fc943a14e0812b81aaf4b14da92 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 16 Nov 2021 13:52:46 +0100 Subject: [PATCH 026/130] Fixed issue with variant renaming --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 848982abe..c67b7fc76 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,7 +95,7 @@ android { } // See https://developer.android.com/studio/releases/gradle-plugin#3-6-0-behavior for why extractNativeLibs is set to true in debug flavor - if (variant.buildType.name == "release" || variant.buildType.name == "releaseAppBundle") { + if (variant.buildType.name == "release" || variant.buildType.name == "releaseWithCrashlytics") { variant.getMergedFlavor().manifestPlaceholders = [linphone_address_mime_type: "vnd.android.cursor.item/vnd." + getPackageName() + ".provider.sip_address", linphone_file_provider: getPackageName() + ".fileprovider", appLabel: "@string/app_name", From 8a11fc9c4a02aa4b5e25a4006d184a442a2e91b1 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 17 Nov 2021 10:30:43 +0100 Subject: [PATCH 027/130] Minor changes to fix code inspector warnings --- .../call/fragments/StatusFragment.kt | 4 +- .../call/viewmodels/ControlsViewModel.kt | 34 ++-- .../main/chat/ChatScrollListener.kt | 2 +- .../main/chat/data/ChatMessageContentData.kt | 10 +- .../main/chat/data/ChatMessageData.kt | 2 +- .../viewmodels/ChatMessageSendingViewModel.kt | 6 +- .../main/chat/viewmodels/ChatRoomViewModel.kt | 4 +- .../chat/viewmodels/ChatRoomsListViewModel.kt | 4 +- .../viewmodels/AdvancedSettingsViewModel.kt | 4 +- .../main/viewmodels/StatusViewModel.kt | 2 +- .../compatibility/Api21Compatibility.kt | 5 + .../compatibility/Api31Compatibility.kt | 32 ++++ .../linphone/compatibility/Compatibility.kt | 7 + .../linphone/contact/AsyncContactsLoader.kt | 10 +- .../main/java/org/linphone/contact/Contact.kt | 4 +- .../org/linphone/contact/ContactsManager.kt | 12 +- .../org/linphone/contact/NativeContact.kt | 165 +++++++++--------- .../linphone/contact/NativeContactEditor.kt | 29 ++- .../java/org/linphone/core/CoreContext.kt | 2 +- .../notifications/NotificationsManager.kt | 4 +- .../main/java/org/linphone/utils/FileUtils.kt | 10 +- .../linphone/views/VoiceRecordProgressBar.kt | 22 +-- .../main/res/layout/chat_bubble_activity.xml | 1 + .../res/layout/chat_room_detail_fragment.xml | 1 + .../res/layout/settings_advanced_fragment.xml | 3 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 3 +- app/src/main/res/values/strings.xml | 1 + 28 files changed, 230 insertions(+), 155 deletions(-) create mode 100644 app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt diff --git a/app/src/main/java/org/linphone/activities/call/fragments/StatusFragment.kt b/app/src/main/java/org/linphone/activities/call/fragments/StatusFragment.kt index 0213bb9cf..5c2a4a41a 100644 --- a/app/src/main/java/org/linphone/activities/call/fragments/StatusFragment.kt +++ b/app/src/main/java/org/linphone/activities/call/fragments/StatusFragment.kt @@ -114,8 +114,8 @@ class StatusFragment : GenericFragment() { val viewModel = DialogViewModel(getString(R.string.zrtp_dialog_message), getString(R.string.zrtp_dialog_title)) viewModel.showZrtp = true - viewModel.zrtpReadSas = toRead.toUpperCase(Locale.getDefault()) - viewModel.zrtpListenSas = toListen.toUpperCase(Locale.getDefault()) + viewModel.zrtpReadSas = toRead.uppercase(Locale.getDefault()) + viewModel.zrtpListenSas = toListen.uppercase(Locale.getDefault()) viewModel.showIcon = true viewModel.iconResource = R.drawable.security_2_indicator diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt index 33d7aac80..96d597b64 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt @@ -342,23 +342,27 @@ class ControlsViewModel : ViewModel() { val currentCall = core.currentCall val conference = core.conference - if (currentCall != null) { - if (currentCall.isRecording) { - currentCall.stopRecording() - } else { - currentCall.startRecording() + when { + currentCall != null -> { + if (currentCall.isRecording) { + currentCall.stopRecording() + } else { + currentCall.startRecording() + } + isRecording.value = currentCall.isRecording } - isRecording.value = currentCall.isRecording - } else if (conference != null) { - val path = LinphoneUtils.getRecordingFilePathForConference() - if (conference.isRecording) { - conference.stopRecording() - } else { - conference.startRecording(path) + conference != null -> { + val path = LinphoneUtils.getRecordingFilePathForConference() + if (conference.isRecording) { + conference.stopRecording() + } else { + conference.startRecording(path) + } + isRecording.value = conference.isRecording + } + else -> { + isRecording.value = false } - isRecording.value = conference.isRecording - } else { - isRecording.value = false } if (closeMenu) toggleOptionsMenu() diff --git a/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt b/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt index 440f1d239..d2e2c7622 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt @@ -29,7 +29,7 @@ internal abstract class ChatScrollListener(private val mLayoutManager: LinearLay // True if we are still waiting for the last set of data to load. private var loading = true - var userHasScrolledUp: Boolean = false + private var userHasScrolledUp: Boolean = false // This happens many times a second during a scroll, so be wary of the code you place here. // We are given a few useful parameters to help us work out if we need to load some more data, diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt index 0623cf4ac..e1a34dde9 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt @@ -60,7 +60,6 @@ class ChatMessageContentData( val fileName = MutableLiveData() val filePath = MutableLiveData() - val fileSize = MutableLiveData() val downloadable = MutableLiveData() val downloadEnabled = MutableLiveData() @@ -72,7 +71,6 @@ class ChatMessageContentData( val formattedDuration = MutableLiveData() val voiceRecordPlayingPosition = MutableLiveData() val isVoiceRecordPlaying = MutableLiveData() - var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null val isAlone: Boolean get() { @@ -86,7 +84,9 @@ class ChatMessageContentData( return count == 1 } - var isFileEncrypted: Boolean = false + private var isFileEncrypted: Boolean = false + + private var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null private lateinit var voiceRecordingPlayer: Player private val playerListener = PlayerListener { @@ -193,8 +193,8 @@ class ChatMessageContentData( } // Display download size and underline text - fileSize.value = AppUtils.bytesToDisplayableSize(content.fileSize.toLong()) - val spannable = SpannableString("${AppUtils.getString(R.string.chat_message_download_file)} (${fileSize.value})") + val fileSize = AppUtils.bytesToDisplayableSize(content.fileSize.toLong()) + val spannable = SpannableString("${AppUtils.getString(R.string.chat_message_download_file)} ($fileSize)") spannable.setSpan(UnderlineSpan(), 0, spannable.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) downloadLabel.value = spannable diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt index cd8230096..43265f98e 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt @@ -165,7 +165,7 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes val list = arrayListOf() val contentsList = chatMessage.contents - for (index in 0 until contentsList.size) { + for (index in contentsList.indices) { val content = contentsList[index] if (content.isFileTransfer || content.isFile) { val data = ChatMessageContentData(chatMessage, index) diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt index f7e0de69c..dfefccc3f 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt @@ -86,11 +86,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() val isPlayingVoiceRecording = MutableLiveData() - val recorder: Recorder - val voiceRecordPlayingPosition = MutableLiveData() - var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null + private val recorder: Recorder + + private var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null private lateinit var voiceRecordingPlayer: Player private val playerListener = PlayerListener { diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt index 7fd7402d3..f17074a31 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt @@ -90,12 +90,12 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf var oneParticipantOneDevice: Boolean = false - var addressToCall: Address? = null - var onlyParticipantOnlyDeviceAddress: Address? = null val chatUnreadCountTranslateY = MutableLiveData() + private var addressToCall: Address? = null + private val bounceAnimator: ValueAnimator by lazy { ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset), 0f).apply { addUpdateListener { diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt index 14f943db2..674cb096b 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt @@ -170,12 +170,10 @@ class ChatRoomsListViewModel : ErrorReportingViewModel() { } private fun findChatRoomIndex(chatRoom: ChatRoom): Int { - var index = 0 - for (chatRoomViewModel in chatRooms.value.orEmpty()) { + for ((index, chatRoomViewModel) in chatRooms.value.orEmpty().withIndex()) { if (chatRoomViewModel.chatRoom == chatRoom) { return index } - index++ } return -1 } diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt index ba78aaa23..c177e7386 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt @@ -32,8 +32,8 @@ import org.linphone.mediastream.Version import org.linphone.utils.Event class AdvancedSettingsViewModel : LogsUploadViewModel() { - protected val prefs = corePreferences - protected val core = coreContext.core + private val prefs = corePreferences + private val core = coreContext.core val debugModeListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/StatusViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/StatusViewModel.kt index a6d0404a7..27f0afb14 100644 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/StatusViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/viewmodels/StatusViewModel.kt @@ -57,7 +57,7 @@ open class StatusViewModel : ViewModel() { body: Content ) { if (body.type == "application" && body.subtype == "simple-message-summary" && body.size > 0) { - val data = body.utf8Text?.toLowerCase(Locale.getDefault()) + val data = body.utf8Text?.lowercase(Locale.getDefault()) val voiceMail = data?.split("voice-message: ") if (voiceMail?.size ?: 0 >= 2) { val toParse = voiceMail!![1].split("/", limit = 0) diff --git a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt index cd59c2076..11aabca98 100644 --- a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt @@ -22,6 +22,7 @@ package org.linphone.compatibility import android.annotation.SuppressLint import android.annotation.TargetApi import android.app.Activity +import android.app.PendingIntent import android.bluetooth.BluetoothAdapter import android.content.ContentValues import android.content.Context @@ -207,5 +208,9 @@ class Api21Compatibility { fun requestDismissKeyguard(activity: Activity) { activity.window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) } + + fun getUpdateCurrentPendingIntentFlag(): Int { + return PendingIntent.FLAG_UPDATE_CURRENT + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt new file mode 100644 index 000000000..a1c454e70 --- /dev/null +++ b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.compatibility + +import android.annotation.TargetApi +import android.app.PendingIntent + +@TargetApi(31) +class Api31Compatibility { + companion object { + fun getUpdateCurrentPendingIntentFlag(): Int { + return PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + } + } +} diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index 5df9208e3..b1dc9d2b5 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -252,5 +252,12 @@ class Compatibility { } return Api21Compatibility.addAudioToMediaStore(context, content) } + + fun getUpdateCurrentPendingIntentFlag(): Int { + if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) { + return Api31Compatibility.getUpdateCurrentPendingIntentFlag() + } + return Api21Compatibility.getUpdateCurrentPendingIntentFlag() + } } } diff --git a/app/src/main/java/org/linphone/contact/AsyncContactsLoader.kt b/app/src/main/java/org/linphone/contact/AsyncContactsLoader.kt index 3433f893b..b554d0a40 100644 --- a/app/src/main/java/org/linphone/contact/AsyncContactsLoader.kt +++ b/app/src/main/java/org/linphone/contact/AsyncContactsLoader.kt @@ -129,10 +129,10 @@ class AsyncContactsLoader(private val context: Context) : try { val id: String = - cursor.getString(cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID)) + cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID)) val starred = - cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.STARRED)) == 1 - val lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)) + cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.STARRED)) == 1 + val lookupKey = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.LOOKUP_KEY)) var contact: Contact? = androidContactsCache[id] if (contact == null) { Log.d( @@ -148,6 +148,10 @@ class AsyncContactsLoader(private val context: Context) : Log.e( "[Contacts Loader] Couldn't get values from cursor, exception: $ise" ) + } catch (iae: IllegalArgumentException) { + Log.e( + "[Contacts Loader] Couldn't get values from cursor, exception: $iae" + ) } } cursor.close() diff --git a/app/src/main/java/org/linphone/contact/Contact.kt b/app/src/main/java/org/linphone/contact/Contact.kt index f7ff508fa..8cba22e3a 100644 --- a/app/src/main/java/org/linphone/contact/Contact.kt +++ b/app/src/main/java/org/linphone/contact/Contact.kt @@ -51,10 +51,10 @@ open class Contact : Comparable { // Raw SIP addresses are only used for contact edition var rawSipAddresses = arrayListOf() - var thumbnailUri: Uri? = null - var friend: Friend? = null + private var thumbnailUri: Uri? = null + override fun compareTo(other: Contact): Int { val fn = fullName ?: "" val otherFn = other.fullName ?: "" diff --git a/app/src/main/java/org/linphone/contact/ContactsManager.kt b/app/src/main/java/org/linphone/contact/ContactsManager.kt index e5c28ef6e..fdc2af0d8 100644 --- a/app/src/main/java/org/linphone/contact/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contact/ContactsManager.kt @@ -79,12 +79,6 @@ class ContactsManager(private val context: Context) { @Synchronized private set - var localAccountsContacts = ArrayList() - @Synchronized - get - @Synchronized - private set - val magicSearch: MagicSearch by lazy { val magicSearch = coreContext.core.createMagicSearch() magicSearch.limitedSearch = false @@ -93,6 +87,12 @@ class ContactsManager(private val context: Context) { var latestContactFetch: String = "" + private var localAccountsContacts = ArrayList() + @Synchronized + get + @Synchronized + private set + private val friendsMap: HashMap = HashMap() private val contactsUpdatedListeners = ArrayList() diff --git a/app/src/main/java/org/linphone/contact/NativeContact.kt b/app/src/main/java/org/linphone/contact/NativeContact.kt index b4f2f4a5c..cb487bedd 100644 --- a/app/src/main/java/org/linphone/contact/NativeContact.kt +++ b/app/src/main/java/org/linphone/contact/NativeContact.kt @@ -86,97 +86,104 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null) @Synchronized override fun syncValuesFromAndroidCursor(cursor: Cursor) { - val displayName: String? = - cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME_PRIMARY)) + try { + val displayName: String? = + cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.DISPLAY_NAME_PRIMARY)) - val mime: String? = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE)) - val data1: String? = cursor.getString(cursor.getColumnIndex("data1")) - val data2: String? = cursor.getString(cursor.getColumnIndex("data2")) - val data3: String? = cursor.getString(cursor.getColumnIndex("data3")) - val data4: String? = cursor.getString(cursor.getColumnIndex("data4")) + val mime: String? = + cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)) + val data1: String? = cursor.getString(cursor.getColumnIndexOrThrow("data1")) + val data2: String? = cursor.getString(cursor.getColumnIndexOrThrow("data2")) + val data3: String? = cursor.getString(cursor.getColumnIndexOrThrow("data3")) + val data4: String? = cursor.getString(cursor.getColumnIndexOrThrow("data4")) - if (fullName == null || fullName != displayName) { - Log.d("[Native Contact] Setting display name $displayName") - fullName = displayName - } + if (fullName == null || fullName != displayName) { + Log.d("[Native Contact] Setting display name $displayName") + fullName = displayName + } - val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type) - when (mime) { - ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> { - if (data1 == null && data4 == null) { - Log.d("[Native Contact] Phone number data is empty") - return - } + val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type) + when (mime) { + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> { + if (data1 == null && data4 == null) { + Log.d("[Native Contact] Phone number data is empty") + return + } - val labelColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL) - val label: String? = cursor.getString(labelColumnIndex) - val typeColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE) - val type: Int = cursor.getInt(typeColumnIndex) - val typeLabel = ContactsContract.CommonDataKinds.Phone.getTypeLabel( - coreContext.context.resources, - type, - label - ).toString() + val labelColumnIndex = + cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL) + val label: String? = cursor.getString(labelColumnIndex) + val typeColumnIndex = + cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE) + val type: Int = cursor.getInt(typeColumnIndex) + val typeLabel = ContactsContract.CommonDataKinds.Phone.getTypeLabel( + coreContext.context.resources, + type, + label + ).toString() - // data4 = ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER - // data1 = ContactsContract.CommonDataKinds.Phone.NUMBER - val number = if (corePreferences.preferNormalizedPhoneNumbersFromAddressBook) { - data4 ?: data1 - } else { - data1 ?: data4 - } - if (number != null && number.isNotEmpty()) { - Log.d("[Native Contact] Found phone number $data1 ($data4), type label is $typeLabel") - if (!rawPhoneNumbers.contains(number)) { - phoneNumbers.add(PhoneNumber(number, typeLabel)) - rawPhoneNumbers.add(number) + // data4 = ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER + // data1 = ContactsContract.CommonDataKinds.Phone.NUMBER + val number = if (corePreferences.preferNormalizedPhoneNumbersFromAddressBook) { + data4 ?: data1 + } else { + data1 ?: data4 + } + if (number != null && number.isNotEmpty()) { + Log.d("[Native Contact] Found phone number $data1 ($data4), type label is $typeLabel") + if (!rawPhoneNumbers.contains(number)) { + phoneNumbers.add(PhoneNumber(number, typeLabel)) + rawPhoneNumbers.add(number) + } } } - } - linphoneMime, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> { - if (data1 == null) { - Log.d("[Native Contact] SIP address is null") - return - } + linphoneMime, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> { + if (data1 == null) { + Log.d("[Native Contact] SIP address is null") + return + } - Log.d("[Native Contact] Found SIP address $data1") - if (rawPhoneNumbers.contains(data1)) { - Log.d("[Native Contact] SIP address value already exists in phone numbers list, skipping") - return - } + Log.d("[Native Contact] Found SIP address $data1") + if (rawPhoneNumbers.contains(data1)) { + Log.d("[Native Contact] SIP address value already exists in phone numbers list, skipping") + return + } - val address: Address? = coreContext.core.interpretUrl(data1) - if (address == null) { - Log.e("[Native Contact] Couldn't parse address $data1 !") - return - } + val address: Address? = coreContext.core.interpretUrl(data1) + if (address == null) { + Log.e("[Native Contact] Couldn't parse address $data1 !") + return + } - val stringAddress = address.asStringUriOnly() - Log.d("[Native Contact] Found SIP address $stringAddress") - if (!rawSipAddresses.contains(data1)) { - sipAddresses.add(address) - rawSipAddresses.add(data1) + val stringAddress = address.asStringUriOnly() + Log.d("[Native Contact] Found SIP address $stringAddress") + if (!rawSipAddresses.contains(data1)) { + sipAddresses.add(address) + rawSipAddresses.add(data1) + } + } + ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> { + if (data1 == null) { + Log.d("[Native Contact] Organization is null") + return + } + + Log.d("[Native Contact] Found organization $data1") + organization = data1 + } + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> { + if (data2 == null && data3 == null) { + Log.d("[Native Contact] First name and last name are both null") + return + } + + Log.d("[Native Contact] Found first name $data2 and last name $data3") + firstName = data2 + lastName = data3 } } - ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> { - if (data1 == null) { - Log.d("[Native Contact] Organization is null") - return - } - - Log.d("[Native Contact] Found organization $data1") - organization = data1 - } - ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> { - if (data2 == null && data3 == null) { - Log.d("[Native Contact] First name and last name are both null") - return - } - - Log.d("[Native Contact] Found first name $data2 and last name $data3") - firstName = data2 - lastName = data3 - } + } catch (iae: IllegalArgumentException) { + Log.e("[Native Contact] Exception: $iae") } } diff --git a/app/src/main/java/org/linphone/contact/NativeContactEditor.kt b/app/src/main/java/org/linphone/contact/NativeContactEditor.kt index 84f900e74..630cb9703 100644 --- a/app/src/main/java/org/linphone/contact/NativeContactEditor.kt +++ b/app/src/main/java/org/linphone/contact/NativeContactEditor.kt @@ -96,8 +96,12 @@ class NativeContactEditor(val contact: NativeContact) { if (cursor?.moveToFirst() == true) { do { if (rawId == null) { - rawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID)) - Log.i("[Native Contact Editor] Found raw id $rawId for native contact with id ${contact.nativeId}") + try { + rawId = cursor.getString(cursor.getColumnIndexOrThrow(RawContacts._ID)) + Log.i("[Native Contact Editor] Found raw id $rawId for native contact with id ${contact.nativeId}") + } catch (iae: IllegalArgumentException) { + Log.e("[Native Contact Editor] Exception: $iae") + } } } while (cursor.moveToNext() && rawId == null) } @@ -258,11 +262,16 @@ class NativeContactEditor(val contact: NativeContact) { ) if (cursor?.moveToFirst() == true) { do { - val accountType = - cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_TYPE)) - if (accountType == AppUtils.getString(R.string.sync_account_type) && syncAccountRawId == null) { - syncAccountRawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID)) - Log.d("[Native Contact Editor] Found linphone raw id $syncAccountRawId for native contact with id ${contact.nativeId}") + try { + val accountType = + cursor.getString(cursor.getColumnIndexOrThrow(RawContacts.ACCOUNT_TYPE)) + if (accountType == AppUtils.getString(R.string.sync_account_type) && syncAccountRawId == null) { + syncAccountRawId = + cursor.getString(cursor.getColumnIndexOrThrow(RawContacts._ID)) + Log.d("[Native Contact Editor] Found linphone raw id $syncAccountRawId for native contact with id ${contact.nativeId}") + } + } catch (iae: IllegalArgumentException) { + Log.e("[Native Contact Editor] Exception: $iae") } } while (cursor.moveToNext() && syncAccountRawId == null) } @@ -461,7 +470,11 @@ class NativeContactEditor(val contact: NativeContact) { val count = cursor?.count ?: 0 val data1 = if (count > 0) { if (cursor?.moveToFirst() == true) { - cursor.getString(cursor.getColumnIndex("data1")) + try { + cursor.getString(cursor.getColumnIndexOrThrow("data1")) + } catch (iae: IllegalArgumentException) { + Log.e("[Native Contact Editor] Exception: $iae") + } } else null } else null cursor?.close() diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index bb2197f9a..383a47a90 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -87,7 +87,7 @@ class CoreContext(val context: Context, coreConfig: Config) { "$sdkVersion ($sdkBranch, $sdkBuildType)" } - val collator = Collator.getInstance() + val collator: Collator = Collator.getInstance() val contactsManager: ContactsManager by lazy { ContactsManager(context) } diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index c7a21f515..729cfbebe 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -633,7 +633,7 @@ class NotificationsManager(private val context: Context) { context, notifiable.notificationId, target, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + Compatibility.getUpdateCurrentPendingIntentFlag() ) val id = LinphoneUtils.getChatRoomId(room.localAddress, room.peerAddress) @@ -888,7 +888,7 @@ class NotificationsManager(private val context: Context) { context, notifiable.notificationId, replyIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + Compatibility.getUpdateCurrentPendingIntentFlag() ) return NotificationCompat.Action.Builder( R.drawable.chat_send_over, diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index e9536c5b7..0955b39b7 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -70,31 +70,31 @@ class FileUtils { } fun isPlainTextFile(path: String): Boolean { - val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault()) + val extension = getExtensionFromFileName(path).lowercase(Locale.getDefault()) val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) return type?.startsWith("text/plain") ?: false } fun isExtensionPdf(path: String): Boolean { - val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault()) + val extension = getExtensionFromFileName(path).lowercase(Locale.getDefault()) val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) return type?.startsWith("application/pdf") ?: false } fun isExtensionImage(path: String): Boolean { - val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault()) + val extension = getExtensionFromFileName(path).lowercase(Locale.getDefault()) val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) return type?.startsWith("image/") ?: false } fun isExtensionVideo(path: String): Boolean { - val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault()) + val extension = getExtensionFromFileName(path).lowercase(Locale.getDefault()) val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) return type?.startsWith("video/") ?: false } fun isExtensionAudio(path: String): Boolean { - val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault()) + val extension = getExtensionFromFileName(path).lowercase(Locale.getDefault()) val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) return type?.startsWith("audio/") ?: false } diff --git a/app/src/main/java/org/linphone/views/VoiceRecordProgressBar.kt b/app/src/main/java/org/linphone/views/VoiceRecordProgressBar.kt index 3d918af4c..900e45e8f 100644 --- a/app/src/main/java/org/linphone/views/VoiceRecordProgressBar.kt +++ b/app/src/main/java/org/linphone/views/VoiceRecordProgressBar.kt @@ -205,7 +205,17 @@ class VoiceRecordProgressBar : View { } } - fun setProgressDrawable(drawable: Drawable) { + fun setSecondaryProgressTint(color: Int) { + val drawable = progressDrawable + if (drawable != null) { + if (drawable is LayerDrawable) { + val secondaryProgressDrawable = drawable.findDrawableByLayerId(android.R.id.secondaryProgress) + secondaryProgressDrawable?.setTint(color) + } + } + } + + private fun setProgressDrawable(drawable: Drawable) { val needUpdate: Boolean = if (progressDrawable != null && drawable !== progressDrawable) { progressDrawable?.callback = null true @@ -233,16 +243,6 @@ class VoiceRecordProgressBar : View { } } - fun setSecondaryProgressTint(color: Int) { - val drawable = progressDrawable - if (drawable != null) { - if (drawable is LayerDrawable) { - val secondaryProgressDrawable = drawable.findDrawableByLayerId(android.R.id.secondaryProgress) - secondaryProgressDrawable?.setTint(color) - } - } - } - private fun refreshProgress(id: Int, progress: Int) { var scale: Float = if (max > 0) (progress.toFloat() / max) else 0f diff --git a/app/src/main/res/layout/chat_bubble_activity.xml b/app/src/main/res/layout/chat_bubble_activity.xml index 7b9c755d7..4c67bbbc0 100644 --- a/app/src/main/res/layout/chat_bubble_activity.xml +++ b/app/src/main/res/layout/chat_bubble_activity.xml @@ -97,6 +97,7 @@ android:id="@+id/message" android:enabled="@{!chatSendingViewModel.isReadOnly}" android:text="@={chatSendingViewModel.textToSend}" + android:hint="@string/chat_room_sending_message_hint" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" diff --git a/app/src/main/res/layout/chat_room_detail_fragment.xml b/app/src/main/res/layout/chat_room_detail_fragment.xml index 36f37c288..d7094e429 100644 --- a/app/src/main/res/layout/chat_room_detail_fragment.xml +++ b/app/src/main/res/layout/chat_room_detail_fragment.xml @@ -328,6 +328,7 @@ android:layout_alignParentBottom="true" android:background="@drawable/round_button_background" android:padding="13dp" + android:contentDescription="@string/content_descripton_scroll_to_bottom" android:src="@drawable/scroll_to_bottom" /> + xmlns:linphone="http://schemas.android.com/apk/res-auto" + xmlns:bind="http://schemas.android.com/tools"> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 44151a411..d99516d2f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -192,7 +192,7 @@ Contactos Marcador telefónico Conversaciones - Eliminar el último caracter + Eliminar el último carácter Crear contacto Mostrar todas las llamadas Mostrar solo llamadas perdidad diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 75a13a517..a72ac3e00 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -481,7 +481,7 @@ Tous les appels Appel entrant Appel sortant - Créer une conversation 1-1 + Créer une conversation 1–1 Créer une conversation de groupe Non sécurisé Sécurisé @@ -626,4 +626,5 @@ Nécessite des permissions supplémentaires %1$d messages non lus %1$d message non lu + Aller au dernier message reçu ou au premier message non lu \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e1dfc7e9b..2f5d1f797 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -737,4 +737,5 @@ Cancel voice recording Pause voice recording Play voice recording + Scroll to bottom or first unread message From 09cb05c9234cc24cee81c13f735342cd6521aa21 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 17 Nov 2021 11:08:46 +0100 Subject: [PATCH 028/130] Report when UI has been fully drawn --- .../java/org/linphone/activities/main/MainActivity.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index acdff80b1..ced990d1e 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -33,6 +33,7 @@ import android.view.inputmethod.InputMethodManager import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.doOnAttach import androidx.databinding.DataBindingUtil import androidx.fragment.app.FragmentContainerView import androidx.lifecycle.ViewModelProvider @@ -154,7 +155,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin tabsFragment = findViewById(R.id.tabs_fragment) statusFragment = findViewById(R.id.status_fragment) - initOverlay() + binding.root.doOnAttach { + Log.i("[Main Activity] Report UI has been fully drawn (TTFD)") + reportFullyDrawn() + } } override fun onNewIntent(intent: Intent?) { @@ -196,6 +200,8 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin updateTabsFragmentVisibility() } + initOverlay() + if (intent != null) handleIntentParams(intent) } From 250c06ec065c44b8fe3a3ceafd12f62af97c1057 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 17 Nov 2021 16:10:17 +0100 Subject: [PATCH 029/130] Update history details when a call is finished and we go back to it + fixed landscape layout vertical scrolling in history details --- .../history/adapters/CallLogsListAdapter.kt | 2 +- .../main/history/data/CallLogData.kt | 77 ++++++++ .../main/history/data/GroupedCallLogData.kt | 5 +- .../fragments/DetailCallLogFragment.kt | 2 +- .../history/viewmodels/CallLogViewModel.kt | 81 +++------ .../main/res/layout/history_detail_cell.xml | 2 +- .../res/layout/history_detail_fragment.xml | 168 +++++++++--------- app/src/main/res/layout/history_list_cell.xml | 2 +- 8 files changed, 193 insertions(+), 146 deletions(-) create mode 100644 app/src/main/java/org/linphone/activities/main/history/data/CallLogData.kt diff --git a/app/src/main/java/org/linphone/activities/main/history/adapters/CallLogsListAdapter.kt b/app/src/main/java/org/linphone/activities/main/history/adapters/CallLogsListAdapter.kt index ae1e80203..a2506d516 100644 --- a/app/src/main/java/org/linphone/activities/main/history/adapters/CallLogsListAdapter.kt +++ b/app/src/main/java/org/linphone/activities/main/history/adapters/CallLogsListAdapter.kt @@ -65,7 +65,7 @@ class CallLogsListAdapter( ) : RecyclerView.ViewHolder(binding.root) { fun bind(callLogGroup: GroupedCallLogData) { with(binding) { - val callLogViewModel = callLogGroup.lastCallLogViewModel + val callLogViewModel = callLogGroup.lastCallLogData viewModel = callLogViewModel lifecycleOwner = viewLifecycleOwner diff --git a/app/src/main/java/org/linphone/activities/main/history/data/CallLogData.kt b/app/src/main/java/org/linphone/activities/main/history/data/CallLogData.kt new file mode 100644 index 000000000..72483c694 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/history/data/CallLogData.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.history.data + +import java.text.SimpleDateFormat +import java.util.* +import org.linphone.R +import org.linphone.contact.GenericContactData +import org.linphone.core.Call +import org.linphone.core.CallLog +import org.linphone.utils.TimestampUtils + +class CallLogData(callLog: CallLog) : GenericContactData(callLog.remoteAddress) { + val statusIconResource: Int by lazy { + if (callLog.dir == Call.Dir.Incoming) { + if (callLog.status == Call.Status.Missed) { + R.drawable.call_status_missed + } else { + R.drawable.call_status_incoming + } + } else { + R.drawable.call_status_outgoing + } + } + + val iconContentDescription: Int by lazy { + if (callLog.dir == Call.Dir.Incoming) { + if (callLog.status == Call.Status.Missed) { + R.string.content_description_missed_call + } else { + R.string.content_description_incoming_call + } + } else { + R.string.content_description_outgoing_call + } + } + + val directionIconResource: Int by lazy { + if (callLog.dir == Call.Dir.Incoming) { + if (callLog.status == Call.Status.Missed) { + R.drawable.call_missed + } else { + R.drawable.call_incoming + } + } else { + R.drawable.call_outgoing + } + } + + val duration: String by lazy { + val dateFormat = SimpleDateFormat(if (callLog.duration >= 3600) "HH:mm:ss" else "mm:ss", Locale.getDefault()) + val cal = Calendar.getInstance() + cal[0, 0, 0, 0, 0] = callLog.duration + dateFormat.format(cal.time) + } + + val date: String by lazy { + TimestampUtils.toString(callLog.startDate, shortDate = false, hideYear = false) + } +} diff --git a/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt b/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt index 89b0d1716..ea76e8fe8 100644 --- a/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt +++ b/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt @@ -19,15 +19,14 @@ */ package org.linphone.activities.main.history.data -import org.linphone.activities.main.history.viewmodels.CallLogViewModel import org.linphone.core.CallLog class GroupedCallLogData(callLog: CallLog) { var lastCallLog: CallLog = callLog val callLogs = arrayListOf(callLog) - val lastCallLogViewModel = CallLogViewModel(lastCallLog) + val lastCallLogData = CallLogData(lastCallLog) fun destroy() { - lastCallLogViewModel.destroy() + lastCallLogData.destroy() } } diff --git a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt b/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt index dc2599280..f4254f96b 100644 --- a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt @@ -70,7 +70,7 @@ class DetailCallLogFragment : GenericFragment() { useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - viewModel.relatedCallLogs.value = callLogGroup.callLogs + viewModel.addRelatedCallLogs(callLogGroup.callLogs) binding.setBackClickListener { goBack() diff --git a/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogViewModel.kt b/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogViewModel.kt index f5529312a..22b734f7a 100644 --- a/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogViewModel.kt @@ -22,17 +22,17 @@ package org.linphone.activities.main.history.viewmodels import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R +import org.linphone.activities.main.history.data.CallLogData import org.linphone.contact.GenericContactViewModel import org.linphone.core.* import org.linphone.core.tools.Log import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils -import org.linphone.utils.TimestampUtils class CallLogViewModelFactory(private val callLog: CallLog) : ViewModelProvider.NewInstanceFactory() { @@ -48,53 +48,6 @@ class CallLogViewModel(val callLog: CallLog) : GenericContactViewModel(callLog.r LinphoneUtils.getDisplayableAddress(callLog.remoteAddress) } - val statusIconResource: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (callLog.status == Call.Status.Missed) { - R.drawable.call_status_missed - } else { - R.drawable.call_status_incoming - } - } else { - R.drawable.call_status_outgoing - } - } - - val iconContentDescription: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (callLog.status == Call.Status.Missed) { - R.string.content_description_missed_call - } else { - R.string.content_description_incoming_call - } - } else { - R.string.content_description_outgoing_call - } - } - - val directionIconResource: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (callLog.status == Call.Status.Missed) { - R.drawable.call_missed - } else { - R.drawable.call_incoming - } - } else { - R.drawable.call_outgoing - } - } - - val duration: String by lazy { - val dateFormat = SimpleDateFormat(if (callLog.duration >= 3600) "HH:mm:ss" else "mm:ss", Locale.getDefault()) - val cal = Calendar.getInstance() - cal[0, 0, 0, 0, 0] = callLog.duration - dateFormat.format(cal.time) - } - - val date: String by lazy { - TimestampUtils.toString(callLog.startDate, shortDate = false, hideYear = false) - } - val startCallEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -109,7 +62,16 @@ class CallLogViewModel(val callLog: CallLog) : GenericContactViewModel(callLog.r val secureChatAllowed = contact.value?.friend?.getPresenceModelForUriOrTel(peerSipUri)?.hasCapability(FriendCapability.LimeX3Dh) ?: false - val relatedCallLogs = MutableLiveData>() + val relatedCallLogs = MutableLiveData>() + + private val listener = object : CoreListenerStub() { + override fun onCallLogUpdated(core: Core, log: CallLog) { + if (callLog.remoteAddress.weakEqual(log.remoteAddress) && callLog.localAddress.weakEqual(log.localAddress)) { + Log.i("[History Detail] New call log for ${callLog.remoteAddress.asStringUriOnly()} with local address ${callLog.localAddress.asStringUriOnly()}") + addRelatedCallLogs(arrayListOf(log)) + } + } + } private val chatRoomListener = object : ChatRoomListenerStub() { override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) { @@ -126,14 +88,19 @@ class CallLogViewModel(val callLog: CallLog) : GenericContactViewModel(callLog.r init { waitForChatRoomCreation.value = false + + coreContext.core.addListener(listener) } override fun onCleared() { + coreContext.core.removeListener(listener) destroy() + super.onCleared() } fun destroy() { + relatedCallLogs.value.orEmpty().forEach(CallLogData::destroy) } fun startCall() { @@ -157,11 +124,15 @@ class CallLogViewModel(val callLog: CallLog) : GenericContactViewModel(callLog.r } } - fun getCallsHistory(): ArrayList { - val callsHistory = ArrayList() - for (callLog in relatedCallLogs.value.orEmpty()) { - callsHistory.add(CallLogViewModel(callLog)) + fun addRelatedCallLogs(logs: ArrayList) { + val callsHistory = ArrayList() + + // We assume new logs are more recent than the ones we already have, so we add them first + for (log in logs) { + callsHistory.add(CallLogData(log)) } - return callsHistory + callsHistory.addAll(relatedCallLogs.value.orEmpty()) + + relatedCallLogs.value = callsHistory } } diff --git a/app/src/main/res/layout/history_detail_cell.xml b/app/src/main/res/layout/history_detail_cell.xml index f682ba7a3..b2aadd39a 100644 --- a/app/src/main/res/layout/history_detail_cell.xml +++ b/app/src/main/res/layout/history_detail_cell.xml @@ -5,7 +5,7 @@ + type="org.linphone.activities.main.history.data.CallLogData" /> - - - - - - - + android:layout_height="match_parent" + android:layout_below="@id/top_bar"> + android:orientation="vertical" + android:paddingTop="10dp" + android:paddingBottom="5dp"> - + - + - + + + + + - + - + - + - + - + - + + + - + - + + type="org.linphone.activities.main.history.data.CallLogData" /> From 59cfddde83f39873e50a2b99682e3eca79c4b0a6 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 17 Nov 2021 17:24:04 +0100 Subject: [PATCH 030/130] Added setting to disable automatically switch audio route to bluetooth device when available --- .../main/settings/viewmodels/AudioSettingsViewModel.kt | 8 ++++++++ app/src/main/res/layout/settings_audio_fragment.xml | 6 ++++++ app/src/main/res/values-fr/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 18 insertions(+) diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt index 16551c4cb..5e356b891 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt @@ -110,6 +110,13 @@ class AudioSettingsViewModel : GenericSettingsViewModel() { val outputAudioDeviceLabels = MutableLiveData>() private val outputAudioDeviceValues = MutableLiveData>() + val preferBluetoothDevicesListener = object : SettingListenerStub() { + override fun onBoolValueChanged(newValue: Boolean) { + prefs.routeAudioToBluetoothIfAvailable = newValue + } + } + val preferBluetoothDevices = MutableLiveData() + val codecBitrateListener = object : SettingListenerStub() { override fun onListValueChanged(position: Int) { for (payloadType in core.audioPayloadTypes) { @@ -154,6 +161,7 @@ class AudioSettingsViewModel : GenericSettingsViewModel() { prefs.getString(R.string.audio_settings_echo_canceller_calibration_summary) } echoTesterStatus.value = prefs.getString(R.string.audio_settings_echo_tester_summary) + preferBluetoothDevices.value = prefs.routeAudioToBluetoothIfAvailable initInputAudioDevicesList() initOutputAudioDevicesList() initCodecBitrateList() diff --git a/app/src/main/res/layout/settings_audio_fragment.xml b/app/src/main/res/layout/settings_audio_fragment.xml index ef34b864e..75fa5a969 100644 --- a/app/src/main/res/layout/settings_audio_fragment.xml +++ b/app/src/main/res/layout/settings_audio_fragment.xml @@ -110,6 +110,12 @@ linphone:selectedIndex="@{viewModel.outputAudioDeviceIndex}" linphone:labels="@{viewModel.outputAudioDeviceLabels}"/> + + %1$d messages non lus %1$d message non lu Aller au dernier message reçu ou au premier message non lu + Acheminer l\'audio vers l\'appareil bluetooth, s\'il existe + Il aura la priorité sur le périphérique de sortie par défaut \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2f5d1f797..6ed825ea7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -376,6 +376,8 @@ Changes will take effect starting next call Default output audio device Changes will take effect starting next call + Route audio to the bluetooth device if any + It will have priority over the default output device Codec bitrate limit Microphone gain (in decibels) From 33ff33016753f81b7d73b5c0f302bbab33dd25f2 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 18 Nov 2021 16:38:26 +0100 Subject: [PATCH 031/130] Fixed assistant's layouts preview in Android Studio --- app/src/main/res/layout-land/assistant_welcome_fragment.xml | 2 +- app/src/main/res/layout/assistant_account_login_fragment.xml | 2 +- .../layout/assistant_echo_canceller_calibration_fragment.xml | 2 +- .../res/layout/assistant_email_account_creation_fragment.xml | 2 +- .../res/layout/assistant_email_account_validation_fragment.xml | 2 +- .../res/layout/assistant_generic_account_login_fragment.xml | 2 +- .../res/layout/assistant_phone_account_creation_fragment.xml | 2 +- .../res/layout/assistant_phone_account_linking_fragment.xml | 2 +- .../res/layout/assistant_phone_account_validation_fragment.xml | 2 +- app/src/main/res/layout/assistant_qr_code_fragment.xml | 2 +- .../main/res/layout/assistant_remote_provisioning_fragment.xml | 2 +- app/src/main/res/layout/assistant_welcome_fragment.xml | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/layout-land/assistant_welcome_fragment.xml b/app/src/main/res/layout-land/assistant_welcome_fragment.xml index d504eb313..d89ae4936 100644 --- a/app/src/main/res/layout-land/assistant_welcome_fragment.xml +++ b/app/src/main/res/layout-land/assistant_welcome_fragment.xml @@ -29,7 +29,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_account_login_fragment.xml b/app/src/main/res/layout/assistant_account_login_fragment.xml index 584e4dbf0..02bd16328 100644 --- a/app/src/main/res/layout/assistant_account_login_fragment.xml +++ b/app/src/main/res/layout/assistant_account_login_fragment.xml @@ -28,7 +28,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_echo_canceller_calibration_fragment.xml b/app/src/main/res/layout/assistant_echo_canceller_calibration_fragment.xml index 183386b3e..acf86c157 100644 --- a/app/src/main/res/layout/assistant_echo_canceller_calibration_fragment.xml +++ b/app/src/main/res/layout/assistant_echo_canceller_calibration_fragment.xml @@ -16,7 +16,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_email_account_creation_fragment.xml b/app/src/main/res/layout/assistant_email_account_creation_fragment.xml index 78906ee61..03d2f83f0 100644 --- a/app/src/main/res/layout/assistant_email_account_creation_fragment.xml +++ b/app/src/main/res/layout/assistant_email_account_creation_fragment.xml @@ -19,7 +19,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_email_account_validation_fragment.xml b/app/src/main/res/layout/assistant_email_account_validation_fragment.xml index 73639b611..6a487897e 100644 --- a/app/src/main/res/layout/assistant_email_account_validation_fragment.xml +++ b/app/src/main/res/layout/assistant_email_account_validation_fragment.xml @@ -18,7 +18,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_generic_account_login_fragment.xml b/app/src/main/res/layout/assistant_generic_account_login_fragment.xml index a1790bf5c..42a11a26a 100644 --- a/app/src/main/res/layout/assistant_generic_account_login_fragment.xml +++ b/app/src/main/res/layout/assistant_generic_account_login_fragment.xml @@ -20,7 +20,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_phone_account_creation_fragment.xml b/app/src/main/res/layout/assistant_phone_account_creation_fragment.xml index 8ae77dcbc..7e3f11c7c 100644 --- a/app/src/main/res/layout/assistant_phone_account_creation_fragment.xml +++ b/app/src/main/res/layout/assistant_phone_account_creation_fragment.xml @@ -24,7 +24,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_phone_account_linking_fragment.xml b/app/src/main/res/layout/assistant_phone_account_linking_fragment.xml index 5bc5d3a63..b17358407 100644 --- a/app/src/main/res/layout/assistant_phone_account_linking_fragment.xml +++ b/app/src/main/res/layout/assistant_phone_account_linking_fragment.xml @@ -24,7 +24,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_phone_account_validation_fragment.xml b/app/src/main/res/layout/assistant_phone_account_validation_fragment.xml index 2d0aba0d5..e047a3b8e 100644 --- a/app/src/main/res/layout/assistant_phone_account_validation_fragment.xml +++ b/app/src/main/res/layout/assistant_phone_account_validation_fragment.xml @@ -18,7 +18,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_qr_code_fragment.xml b/app/src/main/res/layout/assistant_qr_code_fragment.xml index ff0946e12..89c6b82ee 100644 --- a/app/src/main/res/layout/assistant_qr_code_fragment.xml +++ b/app/src/main/res/layout/assistant_qr_code_fragment.xml @@ -17,7 +17,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_remote_provisioning_fragment.xml b/app/src/main/res/layout/assistant_remote_provisioning_fragment.xml index 84d818ad6..3cfbfb2f0 100644 --- a/app/src/main/res/layout/assistant_remote_provisioning_fragment.xml +++ b/app/src/main/res/layout/assistant_remote_provisioning_fragment.xml @@ -21,7 +21,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> diff --git a/app/src/main/res/layout/assistant_welcome_fragment.xml b/app/src/main/res/layout/assistant_welcome_fragment.xml index 582de4e67..54aa1e66d 100644 --- a/app/src/main/res/layout/assistant_welcome_fragment.xml +++ b/app/src/main/res/layout/assistant_welcome_fragment.xml @@ -29,7 +29,7 @@ android:id="@+id/top_bar_fragment" android:name="org.linphone.activities.assistant.fragments.TopBarFragment" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/main_activity_top_bar_size" android:layout_alignParentTop="true" tools:layout="@layout/assistant_top_bar_fragment" /> From eb83e7e5afb3ca1b6108e4c0b4d86729e5888582 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 18 Nov 2021 17:36:04 +0100 Subject: [PATCH 032/130] Fixed bottom tabs not responding to click due to null current destination in nav controller --- .../main/java/org/linphone/activities/Navigation.kt | 12 ++++-------- app/src/main/res/navigation/main_nav_graph.xml | 6 ++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index 90d07c331..abfe51e11 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -103,9 +103,8 @@ internal fun TabsFragment.navigateToCallHistory() { R.id.masterContactsFragment -> R.id.action_masterContactsFragment_to_masterCallLogsFragment R.id.dialerFragment -> R.id.action_dialerFragment_to_masterCallLogsFragment R.id.masterChatRoomsFragment -> R.id.action_masterChatRoomsFragment_to_masterCallLogsFragment - else -> 0 + else -> R.id.action_global_masterCallLogsFragment } - if (action == 0) return findNavController().navigate( action, null, @@ -118,9 +117,8 @@ internal fun TabsFragment.navigateToContacts() { R.id.masterCallLogsFragment -> R.id.action_masterCallLogsFragment_to_masterContactsFragment R.id.dialerFragment -> R.id.action_dialerFragment_to_masterContactsFragment R.id.masterChatRoomsFragment -> R.id.action_masterChatRoomsFragment_to_masterContactsFragment - else -> 0 + else -> R.id.action_global_masterContactsFragment } - if (action == 0) return findNavController().navigate( action, null, @@ -133,9 +131,8 @@ internal fun TabsFragment.navigateToDialer() { R.id.masterCallLogsFragment -> R.id.action_masterCallLogsFragment_to_dialerFragment R.id.masterContactsFragment -> R.id.action_masterContactsFragment_to_dialerFragment R.id.masterChatRoomsFragment -> R.id.action_masterChatRoomsFragment_to_dialerFragment - else -> 0 + else -> R.id.action_global_dialerFragment } - if (action == 0) return findNavController().navigate( action, null, @@ -148,9 +145,8 @@ internal fun TabsFragment.navigateToChatRooms() { R.id.masterCallLogsFragment -> R.id.action_masterCallLogsFragment_to_masterChatRoomsFragment R.id.masterContactsFragment -> R.id.action_masterContactsFragment_to_masterChatRoomsFragment R.id.dialerFragment -> R.id.action_dialerFragment_to_masterChatRoomsFragment - else -> 0 + else -> R.id.action_global_masterChatRoomsFragment } - if (action == 0) return findNavController().navigate( action, null, diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 3f6f05195..5a9df56af 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -64,6 +64,9 @@ app:uri="linphone-android://contact/new/{sipUri}" android:autoVerify="true" /> + + Date: Thu, 18 Nov 2021 17:43:50 +0100 Subject: [PATCH 033/130] Moved fragment dependency to 1.4.0 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c67b7fc76..b30e4c5cf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -212,7 +212,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.media:media:1.4.3' - implementation 'androidx.fragment:fragment-ktx:1.4.0-rc01' + implementation 'androidx.fragment:fragment-ktx:1.4.0' implementation 'androidx.core:core-ktx:1.7.0' def nav_version = "2.4.0-beta02" From a0a39b3884847a5e9d1ecd983fecd105d81b0099 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 22 Nov 2021 10:09:40 +0100 Subject: [PATCH 034/130] Clean up zombie TelecomManager connections if any --- .../telecom/TelecomConnectionService.kt | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/linphone/telecom/TelecomConnectionService.kt b/app/src/main/java/org/linphone/telecom/TelecomConnectionService.kt index ac8f8da9c..fa3f8c4de 100644 --- a/app/src/main/java/org/linphone/telecom/TelecomConnectionService.kt +++ b/app/src/main/java/org/linphone/telecom/TelecomConnectionService.kt @@ -42,6 +42,7 @@ class TelecomConnectionService : ConnectionService() { Call.State.OutgoingProgress -> { for (connection in TelecomHelper.get().connections) { if (connection.callId.isEmpty()) { + Log.i("[Telecom Connection Service] Updating connection with call ID: ${call.callLog.callId}") connection.callId = core.currentCall?.callLog?.callId ?: "" } } @@ -51,6 +52,18 @@ class TelecomConnectionService : ConnectionService() { Call.State.Connected -> onCallConnected(call) } } + + override fun onLastCallEnded(core: Core) { + val connectionsCount = TelecomHelper.get().connections.size + if (connectionsCount > 0) { + Log.w("[Telecom Connection Service] Last call ended, there is $connectionsCount connections still alive") + for (connection in TelecomHelper.get().connections) { + Log.w("[Telecom Connection Service] Destroying zombie connection ${connection.callId}") + connection.setDisconnected(DisconnectCause(DisconnectCause.OTHER)) + connection.destroy() + } + } + } } override fun onCreate() { @@ -162,8 +175,12 @@ class TelecomConnectionService : ConnectionService() { } private fun onCallError(call: Call) { - val connection = TelecomHelper.get().findConnectionForCallId(call.callLog.callId) - connection ?: return + val callId = call.callLog.callId + val connection = TelecomHelper.get().findConnectionForCallId(callId) + if (connection == null) { + Log.e("[Telecom Connection Service] Failed to find connection for call id: $callId") + return + } TelecomHelper.get().connections.remove(connection) connection.setDisconnected(DisconnectCause(DisconnectCause.ERROR)) @@ -171,19 +188,27 @@ class TelecomConnectionService : ConnectionService() { } private fun onCallEnded(call: Call) { - val connection = TelecomHelper.get().findConnectionForCallId(call.callLog.callId) - connection ?: return + val callId = call.callLog.callId + val connection = TelecomHelper.get().findConnectionForCallId(callId) + if (connection == null) { + Log.e("[Telecom Connection Service] Failed to find connection for call id: $callId") + return + } TelecomHelper.get().connections.remove(connection) val reason = call.reason - Log.i("[Telecom Connection Service] Call ended with reason: $reason") + Log.i("[Telecom Connection Service] Call [$callId] ended with reason: $reason, destroying connection") connection.setDisconnected(DisconnectCause(DisconnectCause.LOCAL)) connection.destroy() } private fun onCallConnected(call: Call) { - val connection = TelecomHelper.get().findConnectionForCallId(call.callLog.callId) - connection ?: return + val callId = call.callLog.callId + val connection = TelecomHelper.get().findConnectionForCallId(callId) + if (connection == null) { + Log.e("[Telecom Connection Service] Failed to find connection for call id: $callId") + return + } if (connection.state != Connection.STATE_HOLDING) { connection.setActive() From 05a167110bd7db34a00f2277d9f4550fa85ca7e9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 22 Nov 2021 10:35:01 +0100 Subject: [PATCH 035/130] Disable export downloaded images to gallery chat setting if auto download is enabled --- app/src/main/res/layout/settings_chat_fragment.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/settings_chat_fragment.xml b/app/src/main/res/layout/settings_chat_fragment.xml index d7d992413..b7b138f5f 100644 --- a/app/src/main/res/layout/settings_chat_fragment.xml +++ b/app/src/main/res/layout/settings_chat_fragment.xml @@ -102,7 +102,7 @@ From 1f3c17b818cd1ac27ae4ce2f2d4f446a966fdb42 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 18 Nov 2021 16:11:35 +0100 Subject: [PATCH 036/130] Another attempt to fix blank chat screen --- .../chat/fragments/DetailChatRoomFragment.kt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 8dff727e4..c18fb2f08 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -108,6 +108,16 @@ class DetailChatRoomFragment : MasterFragment Date: Mon, 22 Nov 2021 15:10:02 +0100 Subject: [PATCH 037/130] Update secured chat room devices list security level after validation call --- .../activities/main/chat/fragments/DevicesFragment.kt | 6 ++++++ .../activities/main/chat/viewmodels/DevicesListViewModel.kt | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt index 3bc2134ec..243c37fe1 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt @@ -66,4 +66,10 @@ class DevicesFragment : SecureFragment() { goBack() } } + + override fun onResume() { + super.onResume() + + listViewModel.updateParticipants() + } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/DevicesListViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/DevicesListViewModel.kt index 3f61d5497..625ed9419 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/DevicesListViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/DevicesListViewModel.kt @@ -59,16 +59,16 @@ class DevicesListViewModel(private val chatRoom: ChatRoom) : ViewModel() { init { chatRoom.addListener(listener) - updateParticipants() } override fun onCleared() { participants.value.orEmpty().forEach(DevicesListGroupData::destroy) chatRoom.removeListener(listener) + super.onCleared() } - private fun updateParticipants() { + fun updateParticipants() { participants.value.orEmpty().forEach(DevicesListGroupData::destroy) val list = arrayListOf() From 613a7558f8894e6c61091b0aa5d99058acc9c16c Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Nov 2021 11:12:58 +0100 Subject: [PATCH 038/130] Improved troubleshooting section of README for native crash debugging --- README.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f04ebdbaa..4715b240a 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,9 @@ Also check you have built the SDK for the right CPU architecture using the `-DLI - Push notification might not work when app has been started by Android Studio consecutively to an install. Remove the app from the recent activity view and start it again using the launcher icon to resolve this. -## Troubleshouting +## Troubleshooting + +### Behavior issue When submitting an issue on our [Github repository](https://github.com/BelledonneCommunications/linphone-android), please follow the template and attach the matching library logs: @@ -105,7 +107,32 @@ When submitting an issue on our [Github repository](https://github.com/Belledonn 2. Then restart the app, reproduce the issue and upload the logs using the `Send logs` button on the About page. -3. Finally paste the link to the uploaded logs (link is already in the clipboard after a sucessful upload). +3. Finally paste the link to the uploaded logs (link is already in the clipboard after a successful upload). + +### Native crash + +First of all, to be able to get a symbolized stack trace, you need the debug version of our libraries. + +If you haven't built the SDK locally (see [building a local SDK](#BuildingalocalSDK)), here's how to get them: + +1. Go to our [maven repository](https://download.linphone.org/maven_repository/org/linphone/linphone-sdk-android-debug/), in the linphone-android-debug directory. + +2. Download the AAR file with **the exact same version** as the AAR that was used to generate the crash's stacktrace. + +3. Extract the AAR somewhere on your computer (it's a simple ZIP file even it's doesn't have the extension). Libraries are stored inside the ```jni``` folder (a directory for each architectured built, usually ```arm64-v8a, armeabi-v7a, x86_64 and x86```). + +4. To get consistent with locally built SDK, rename the ```jni``` directory into ```libs-debug```. + +Now you need the ```ndk-stack``` tool and possibly ```adb logcat```. + +If your computer isn't used for Android development, you can download those tools from [Google website](https://developer.android.com/studio#downloads), in the ```Command line tools only``` section. + +Once you have the debug libraries and the proper tools installed, you can use the ```ndk-stack``` tool to symbolize your stacktrace. Note that you also need to know the architecture (armv7, arm64, x86, etc...) of the libraries that were used. + +Here's how to get the stacktrace and the right architecture from a device plugged to your computer: +``` +adb logcat -d | ndk-stack -sym ./libs-debug/`adb shell getprop ro.product.cpu.abi | tr -d '\r'` +``` ## Create an APK with a different package name @@ -137,6 +164,6 @@ Due to the full app rewrite we can't re-use previous translations, so we'll be v In order to submit a patch for inclusion in linphone's source code: 1. First make sure your patch applies to latest git sources before submitting: patches made to old versions can't and won't be merged. -2. Fill out and send us an email with the link of pullrequest and the [Contributor Agreement](https://linphone.org/sites/default/files/bc-contributor-agreement_0.pdf) for your patch to be included in the git tree. +2. Fill out and send us an email with the link of pull-request and the [Contributor Agreement](https://linphone.org/sites/default/files/bc-contributor-agreement_0.pdf) for your patch to be included in the git tree. The goal of this agreement to grant us peaceful exercise of our rights on the linphone source code, while not losing your rights on your contribution. From 47984597afe8d1de330205c8f1995dcde6855ecc Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Nov 2021 12:01:35 +0100 Subject: [PATCH 039/130] Fixed read phone number permission granted in assistant not updating UI --- .../fragments/AbstractPhoneFragment.kt | 13 +++++++--- .../fragments/AccountLoginFragment.kt | 13 +++++++--- .../EchoCancellerCalibrationFragment.kt | 23 +++++++++++------ .../fragments/PhoneAccountCreationFragment.kt | 13 +++++++--- .../fragments/PhoneAccountLinkingFragment.kt | 5 +++- .../assistant/fragments/QrCodeFragment.kt | 25 ++++++++++++------- .../compatibility/Api23Compatibility.kt | 6 +++++ .../compatibility/Api29Compatibility.kt | 5 ---- .../compatibility/Api30Compatibility.kt | 5 ++-- .../linphone/compatibility/Compatibility.kt | 8 +++--- 10 files changed, 79 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt index 7b92f5652..fbc2db617 100644 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt +++ b/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt @@ -20,6 +20,7 @@ package org.linphone.activities.assistant.fragments +import android.annotation.TargetApi import android.content.pm.PackageManager import androidx.databinding.ViewDataBinding import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -28,10 +29,15 @@ import org.linphone.activities.GenericFragment import org.linphone.activities.assistant.viewmodels.AbstractPhoneViewModel import org.linphone.compatibility.Compatibility import org.linphone.core.tools.Log +import org.linphone.mediastream.Version import org.linphone.utils.PermissionHelper import org.linphone.utils.PhoneNumberUtils abstract class AbstractPhoneFragment : GenericFragment() { + companion object { + const val READ_PHONE_STATE_PERMISSION_REQUEST_CODE = 0 + } + abstract val viewModel: AbstractPhoneViewModel override fun onRequestPermissionsResult( @@ -39,7 +45,7 @@ abstract class AbstractPhoneFragment : GenericFragment() permissions: Array, grantResults: IntArray ) { - if (requestCode == 0) { + if (requestCode == READ_PHONE_STATE_PERMISSION_REQUEST_CODE) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Log.i("[Assistant] READ_PHONE_STATE/READ_PHONE_NUMBERS permission granted") updateFromDeviceInfo() @@ -49,11 +55,12 @@ abstract class AbstractPhoneFragment : GenericFragment() } } - protected fun checkPermission() { + @TargetApi(Version.API23_MARSHMALLOW_60) + protected fun checkPermissions() { if (!resources.getBoolean(R.bool.isTablet)) { if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) { Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission") - Compatibility.requestReadPhoneStateOrNumbersPermission(requireActivity(), 0) + Compatibility.requestReadPhoneStateOrNumbersPermission(this, READ_PHONE_STATE_PERMISSION_REQUEST_CODE) } else { updateFromDeviceInfo() } diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/AccountLoginFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/AccountLoginFragment.kt index 1a6e1f264..cfdd675f5 100644 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/AccountLoginFragment.kt +++ b/app/src/main/java/org/linphone/activities/assistant/fragments/AccountLoginFragment.kt @@ -35,6 +35,7 @@ import org.linphone.activities.main.viewmodels.DialogViewModel import org.linphone.activities.navigateToEchoCancellerCalibration import org.linphone.activities.navigateToPhoneAccountValidation import org.linphone.databinding.AssistantAccountLoginFragmentBinding +import org.linphone.mediastream.Version import org.linphone.utils.DialogUtils class AccountLoginFragment : AbstractPhoneFragment() { @@ -52,7 +53,10 @@ class AccountLoginFragment : AbstractPhoneFragment() { + companion object { + const val RECORD_AUDIO_PERMISSION_REQUEST_CODE = 0 + } + private lateinit var viewModel: EchoCancellerCalibrationViewModel override fun getLayoutId(): Int = R.layout.assistant_echo_canceller_calibration_fragment @@ -54,7 +58,7 @@ class EchoCancellerCalibrationFragment : GenericFragment, grantResults: IntArray ) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Echo Canceller Calibration] RECORD_AUDIO permission granted") - viewModel.startEchoCancellerCalibration() - } else { - Log.w("[Echo Canceller Calibration] RECORD_AUDIO permission denied") - requireActivity().finish() + if (requestCode == RECORD_AUDIO_PERMISSION_REQUEST_CODE) { + val granted = + grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED + if (granted) { + Log.i("[Echo Canceller Calibration] RECORD_AUDIO permission granted") + viewModel.startEchoCancellerCalibration() + } else { + Log.w("[Echo Canceller Calibration] RECORD_AUDIO permission denied") + requireActivity().finish() + } } } } diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountCreationFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountCreationFragment.kt index f396bd059..749ac6449 100644 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountCreationFragment.kt +++ b/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountCreationFragment.kt @@ -29,8 +29,10 @@ import org.linphone.activities.assistant.viewmodels.PhoneAccountCreationViewMode import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel import org.linphone.activities.navigateToPhoneAccountValidation import org.linphone.databinding.AssistantPhoneAccountCreationFragmentBinding +import org.linphone.mediastream.Version -class PhoneAccountCreationFragment : AbstractPhoneFragment() { +class PhoneAccountCreationFragment : + AbstractPhoneFragment() { private lateinit var sharedViewModel: SharedAssistantViewModel override lateinit var viewModel: PhoneAccountCreationViewModel @@ -45,7 +47,10 @@ class PhoneAccountCreationFragment : AbstractPhoneFragment() { private lateinit var sharedViewModel: SharedAssistantViewModel @@ -105,6 +106,8 @@ class PhoneAccountLinkingFragment : AbstractPhoneFragment() { + companion object { + const val CAMERA_PERMISSION_REQUEST_CODE = 0 + } + private lateinit var sharedViewModel: SharedAssistantViewModel private lateinit var viewModel: QrCodeViewModel @@ -64,7 +68,7 @@ class QrCodeFragment : GenericFragment() { if (!PermissionHelper.required(requireContext()).hasCameraPermission()) { Log.i("[QR Code] Asking for CAMERA permission") - requestPermissions(arrayOf(android.Manifest.permission.CAMERA), 0) + requestPermissions(arrayOf(android.Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE) } } @@ -89,14 +93,17 @@ class QrCodeFragment : GenericFragment() { permissions: Array, grantResults: IntArray ) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[QR Code] CAMERA permission granted") - coreContext.core.reloadVideoDevices() - viewModel.setBackCamera() - } else { - Log.w("[QR Code] CAMERA permission denied") - findNavController().navigateUp() + if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { + val granted = + grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED + if (granted) { + Log.i("[QR Code] CAMERA permission granted") + coreContext.core.reloadVideoDevices() + viewModel.setBackCamera() + } else { + Log.w("[QR Code] CAMERA permission denied") + findNavController().navigateUp() + } } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api23Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api23Compatibility.kt index 080d8f739..4a7505564 100644 --- a/app/src/main/java/org/linphone/compatibility/Api23Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api23Compatibility.kt @@ -19,10 +19,12 @@ */ package org.linphone.compatibility +import android.Manifest import android.annotation.TargetApi import android.content.Context import android.content.pm.PackageManager import android.provider.Settings +import androidx.fragment.app.Fragment @TargetApi(23) class Api23Compatibility { @@ -31,6 +33,10 @@ class Api23Compatibility { return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED } + fun requestReadPhoneStatePermission(fragment: Fragment, code: Int) { + fragment.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), code) + } + fun canDrawOverlay(context: Context): Boolean { return Settings.canDrawOverlays(context) } diff --git a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt index 9ffd1e3ea..1d5c2831c 100644 --- a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt @@ -21,7 +21,6 @@ package org.linphone.compatibility import android.Manifest import android.annotation.TargetApi -import android.app.Activity import android.app.NotificationChannel import android.app.NotificationManager import android.content.ContentValues @@ -57,10 +56,6 @@ class Api29Compatibility { return granted } - fun requestReadPhoneStatePermission(activity: Activity, code: Int) { - activity.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), code) - } - fun createMessageChannel( context: Context, notificationManager: NotificationManagerCompat diff --git a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt index 433d4c349..a32a5b4db 100644 --- a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt @@ -24,6 +24,7 @@ import android.annotation.TargetApi import android.app.Activity import android.content.Context import android.content.pm.ShortcutManager +import androidx.fragment.app.Fragment import org.linphone.core.ChatRoom import org.linphone.core.tools.Log import org.linphone.utils.LinphoneUtils @@ -41,8 +42,8 @@ class Api30Compatibility { return granted } - fun requestReadPhoneNumbersPermission(activity: Activity, code: Int) { - activity.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_NUMBERS), code) + fun requestReadPhoneNumbersPermission(fragment: Fragment, code: Int) { + fragment.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_NUMBERS), code) } fun requestTelecomManagerPermission(activity: Activity, code: Int) { diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index b1dc9d2b5..ca66774c4 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -30,6 +30,7 @@ import android.telephony.TelephonyManager import android.view.View import android.view.WindowManager import androidx.core.app.NotificationManagerCompat +import androidx.fragment.app.Fragment import org.linphone.core.ChatRoom import org.linphone.core.Content import org.linphone.mediastream.Version @@ -55,13 +56,14 @@ class Compatibility { } // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers - fun requestReadPhoneStateOrNumbersPermission(activity: Activity, code: Int) { + fun requestReadPhoneStateOrNumbersPermission(fragment: Fragment, code: Int) { if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { - Api30Compatibility.requestReadPhoneNumbersPermission(activity, code) + Api30Compatibility.requestReadPhoneNumbersPermission(fragment, code) } else { - Api29Compatibility.requestReadPhoneStatePermission(activity, code) + Api23Compatibility.requestReadPhoneStatePermission(fragment, code) } } + // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers fun requestTelecomManagerPermission(activity: Activity, code: Int) { if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { From d3b34006889dc960f58f64c92a38251b82c86089 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Nov 2021 14:23:27 +0100 Subject: [PATCH 040/130] Changed how versionCode is set --- app/build.gradle | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b30e4c5cf..d8b3cf323 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,11 @@ plugins { id 'org.jlleitschuh.gradle.ktlint' } +def appVersionName = "4.6.0" +// Uncomment for release +// def appVersionCode = 40600 // 4.06.00 +def appVersionCode = 40590 // 4.05.90 + static def getPackageName() { return "org.linphone" } @@ -24,7 +29,7 @@ if (crashlyticsEnabled) { def gitBranch = new ByteArrayOutputStream() task getGitVersion() { - def gitVersion = "4.6.0" + def gitVersion = appVersionName def gitVersionStream = new ByteArrayOutputStream() def gitCommitsCount = new ByteArrayOutputStream() def gitCommitHash = new ByteArrayOutputStream() @@ -52,9 +57,9 @@ task getGitVersion() { } else { gitVersion = gitVersionStream.toString().trim() + "." + gitCommitsCount.toString().trim() + "+" + gitCommitHash.toString().trim() } - println("Git version: " + gitVersion) + println("Git version: " + gitVersion + " (" + appVersionCode + ")") } catch (ignored) { - println("Git not found") + println("Git not found, using " + gitVersion + " (" + appVersionCode + ")") } project.version = gitVersion } @@ -84,7 +89,7 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 31 - versionCode 4600 + versionCode appVersionCode versionName "${project.version}" applicationId getPackageName() } From 85dd00b5d5c695741aeea398ca13637116c1774b Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Nov 2021 16:02:06 +0100 Subject: [PATCH 041/130] Improved chat-bubble UI + updated CHANGELOG --- CHANGELOG.md | 3 +++ app/src/main/res/layout/chat_bubble_activity.xml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acd2725b2..06292757f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,16 +28,19 @@ Group changes to describe their impact on the project, as follows: - Using new [Unified Content API](https://developer.android.com/about/versions/12/features/unified-content-api) to share files from keyboard (or other sources) - Bumped dependencies, gradle updated from 4.2.2 to 7.0.2 - Target Android SDK version set to 31 (Android 12) +- Splashscreen is using new APIs - SDK updated to 5.1.0 release ### Fixed - Chat notifications disappearing when app restarts - "Infinite backstack", now each view is stored (at most) once in the backstack - Going back to the dialer when pressing back in a chat room after clicking on a chat message notification +- Missing international prefix / phone number in assistant after granting permission ### Removed - Launcher Activity has been replaced by [Splash Screen API](https://developer.android.com/reference/kotlin/androidx/core/splashscreen/SplashScreen) - Dialer will no longer make DTMF sound when pressing digits +- Launcher activity - Global push notification setting in Network, use the switch in each Account instead ## [4.5.6] - 2021-11-08 diff --git a/app/src/main/res/layout/chat_bubble_activity.xml b/app/src/main/res/layout/chat_bubble_activity.xml index 4c67bbbc0..7883ba600 100644 --- a/app/src/main/res/layout/chat_bubble_activity.xml +++ b/app/src/main/res/layout/chat_bubble_activity.xml @@ -103,7 +103,7 @@ android:layout_gravity="center_vertical" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" - android:layout_marginLeft="5dp" + android:layout_marginStart="10dp" android:layout_weight="1" android:background="@drawable/resizable_text_field" android:imeOptions="flagNoExtractUi" @@ -124,7 +124,7 @@ android:contentDescription="@string/content_description_send_message" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="5dp" + android:padding="10dp" android:src="@drawable/chat_send_message" /> Date: Wed, 10 Nov 2021 09:36:13 +0000 Subject: [PATCH 042/130] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (628 of 628 strings) Translation: Linphone/Linphone Android (4.6 release) Translate-URL: https://weblate.linphone.org/projects/linphone/linphone-android-release-4-6/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 72 ++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 4fc92bf15..7be5f3715 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,9 +1,7 @@ - + ]> - 传输 UDP 用户数据报协议 @@ -569,4 +567,70 @@ 显示名 用户名 启用短暂消息(测试版) - + 上传日志失败 + 查看配置文件 + 回复 + 选择或创建对话以转发消息 + 似乎我们无法显示该文件。 + 您想以文本形式打开它还是将其(未加密)导出到第三方应用程序(如果有)? + 服务器超时 + 主账户 + 改善与蓝牙设备的交互 + 将拒绝的呼叫重定向到语音邮件 URI + 始终在此应用内打开文件 + 您仍然可以在第三方应用程序中导出它们 + 加密一切 + 允许对敏感视图进行屏幕捕获/录制 + DTLS + 如果输入了数字,则将前缀应用于数字 + 留言即回复 + 录制音频信息 + 截取收到的视频截图 + 在应用程序中打开对话而不是气泡 + 日志已清除 + 回复 + 选择或创建对话以共享文件 + 文件未找到 + 没有可用于此类文件的应用 + 无法在聊天气泡中打开加密文件 + 选择或创建对话以共享文本 + 导出 + 作为文本打开 + 回复 + 信息 + 按住按钮录制语音信息 + 您的媒体音量很低,您可能需要提高音量 + %1$d 未读消息 + %1$d 未读消息 + 暂时不可用 + 错误:%s + 使用条款 + 隐私政策 + 我接受 Belledonne Communications 的 %1$s 和 %2$s + 需要一些额外的权限 + 通话时全屏应用 + 隐藏状态栏和导航栏 + 在应用程序外显示叠加层 + 音频焦点丢失时暂停通话 + 自动开始通话录音 + 在传入的早期媒体流期间响铃 + 自动下载传入的录音 + 一旦启用就无法禁用! + 调试设置 + 其他设置 + 禁用 UI 的安全模式 + &appName; 未接来电通知 + 应用前缀于拨出的电话和聊天 + 在此对话中转发消息 + 附加到消息的文件 + 关闭通知气泡 + 在第三方应用程序中打开文件 + 取消消息转发 + 取消分享 + 取消回复 + 录制语音留言 + 停止录音 + 取消录音 + 暂停录音 + 播放录音 + \ No newline at end of file From 55757a1077692391f6ad5646272e99ed2447d7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93v=C3=A1ri?= Date: Thu, 18 Nov 2021 08:11:11 +0000 Subject: [PATCH 043/130] Translated using Weblate (Hungarian) Currently translated at 100.0% (631 of 631 strings) Translation: Linphone/Linphone Android (4.6 release) Translate-URL: https://weblate.linphone.org/projects/linphone/linphone-android-release-4-6/hu/ --- app/src/main/res/values-hu/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index b28960821..2a7dce777 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -626,4 +626,7 @@ Tevékenységek fejlesztése Bluetooth-eszközökkel %1$d olvasatlan üzenet %1$d olvasatlan üzenet + Ennek elsőbbsége lesz az alapértelmezett kimeneti eszközzel szemben + Görgetés legalulra vagy az első olvasatlan üzenetre + Hang átirányítása a Bluetooth-eszközre (ha van ilyen) \ No newline at end of file From 390f7b3dd30aad191c1ac0f763d7cb6c3bb925d5 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 24 Nov 2021 13:16:55 +0100 Subject: [PATCH 044/130] Removed call notifications if any when app starts (if app has crashed during a call, a zombie call notification that can't be dismissed will be kept until device restarts) --- .../notifications/NotificationsManager.kt | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 729cfbebe..6bce3d38b 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -20,6 +20,7 @@ package org.linphone.notifications import android.app.Notification +import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent @@ -90,6 +91,10 @@ class NotificationsManager(private val context: Context) { private const val SERVICE_NOTIF_ID = 1 private const val MISSED_CALLS_NOTIF_ID = 2 + + private const val CHAT_TAG = "Chat" + private const val CALL_TAG = "Call" + private const val MISSED_CALL_TAG = "Missed call" } private val notificationManager: NotificationManagerCompat by lazy { @@ -209,6 +214,14 @@ class NotificationsManager(private val context: Context) { init { Compatibility.createNotificationChannels(context, notificationManager) + + val manager = context.getSystemService(NotificationManager::class.java) as NotificationManager + for (notification in manager.activeNotifications) { + if (notification.tag == CALL_TAG) { + Log.w("[Notifications Manager] Found existing call notification [${notification.id}], cancelling it") + manager.cancel(notification.id) + } + } } fun onCoreReady() { @@ -234,9 +247,9 @@ class NotificationsManager(private val context: Context) { coreContext.core.removeListener(listener) } - private fun notify(id: Int, notification: Notification) { - Log.i("[Notifications Manager] Notifying $id") - notificationManager.notify(id, notification) + private fun notify(id: Int, notification: Notification, tag: String) { + Log.i("[Notifications Manager] Notifying [$id] with tag [$tag]") + notificationManager.notify(tag, id, notification) } fun cancel(id: Int) { @@ -445,7 +458,7 @@ class NotificationsManager(private val context: Context) { val notification = builder.build() Log.i("[Notifications Manager] Notifying incoming call notification") - notify(notifiable.notificationId, notification) + notify(notifiable.notificationId, notification, CALL_TAG) if (useAsForeground) { Log.i("[Notifications Manager] Notifying incoming call notification for foreground service") @@ -493,7 +506,7 @@ class NotificationsManager(private val context: Context) { val notification = builder.build() - notify(MISSED_CALLS_NOTIF_ID, notification) + notify(MISSED_CALLS_NOTIF_ID, notification, MISSED_CALL_TAG) } fun dismissMissedCallNotification() { @@ -587,7 +600,7 @@ class NotificationsManager(private val context: Context) { val notification = builder.build() - notify(notifiable.notificationId, notification) + notify(notifiable.notificationId, notification, CALL_TAG) if (useAsForeground) { startForeground(notifiable.notificationId, notification) @@ -638,7 +651,7 @@ class NotificationsManager(private val context: Context) { val id = LinphoneUtils.getChatRoomId(room.localAddress, room.peerAddress) val notification = createMessageNotification(notifiable, pendingIntent, bubbleIntent, id) - notify(notifiable.notificationId, notification) + notify(notifiable.notificationId, notification, CHAT_TAG) } private fun displayIncomingChatNotification(room: ChatRoom, message: ChatMessage) { From 5119f93365f76bf97a9a9cf1a22062066e227557 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 25 Nov 2021 10:01:33 +0100 Subject: [PATCH 045/130] Fixed chat message notification not being dismissed when marking the chat room as read --- .gitignore | 1 + .../chat_bubble/ChatBubbleActivity.kt | 3 +- .../notifications/NotificationsManager.kt | 58 ++++++++++++++----- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 56cbd2768..6c785d36f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ linphone-sdk-android/*.aar app/debug app/release app/releaseAppBundle +app/releaseWithCrashlytics keystore.properties app/src/main/res/xml/contacts.xml diff --git a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt index 0169412cc..47dde1db1 100644 --- a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt +++ b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt @@ -87,9 +87,8 @@ class ChatBubbleActivity : GenericActivity() { } // Workaround for the removed notification when a chat room is marked as read - coreContext.notificationsManager.dismissNotificationUponReadChatRoom = false + coreContext.notificationsManager.disableDismissNotificationUponReadForChatRoom(chatRoom) chatRoom.markAsRead() - coreContext.notificationsManager.dismissNotificationUponReadChatRoom = true viewModel = ViewModelProvider( this, diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 6bce3d38b..b7b58b63e 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -64,6 +64,7 @@ private class Notifiable(val notificationId: Int) { var localIdentity: String? = null var myself: String? = null var remoteAddress: String? = null + var dismissNotificationUponReadChatRoom: Boolean = true } private class NotifiableMessage( @@ -102,6 +103,7 @@ class NotificationsManager(private val context: Context) { } private val chatNotificationsMap: HashMap = HashMap() private val callNotificationsMap: HashMap = HashMap() + private val previousChatNotifications: ArrayList = arrayListOf() private var currentForegroundServiceNotificationId: Int = 0 private var serviceNotification: Notification? = null @@ -110,8 +112,6 @@ class NotificationsManager(private val context: Context) { var currentlyDisplayedChatRoomAddress: String? = null - var dismissNotificationUponReadChatRoom: Boolean = true - private val listener: CoreListenerStub = object : CoreListenerStub() { override fun onCallStateChanged( core: Core, @@ -173,11 +173,18 @@ class NotificationsManager(private val context: Context) { } override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { - if (dismissNotificationUponReadChatRoom) { - Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, removing notification if any") - dismissChatNotification(chatRoom) + val address = chatRoom.peerAddress.asStringUriOnly() + val notifiable = chatNotificationsMap[address] + if (notifiable != null) { + if (notifiable.dismissNotificationUponReadChatRoom) { + Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, removing notification if any") + dismissChatNotification(chatRoom) + } else { + Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, not removing notification, maybe because of a chat bubble?") + } } else { - Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, not removing notification, maybe because of a chat bubble?") + Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read but no notifiable found, removing notification if any") + dismissChatNotification(chatRoom) } } } @@ -203,11 +210,11 @@ class NotificationsManager(private val context: Context) { displayReplyMessageNotification(message, notifiable) } else { Log.e("[Notifications Manager] Couldn't find notification for chat room $address") - cancel(id) + cancel(id, CHAT_TAG) } } else if (state == ChatMessage.State.NotDelivered) { Log.e("[Notifications Manager] Reply wasn't delivered") - cancel(id) + cancel(id, CHAT_TAG) } } } @@ -219,7 +226,10 @@ class NotificationsManager(private val context: Context) { for (notification in manager.activeNotifications) { if (notification.tag == CALL_TAG) { Log.w("[Notifications Manager] Found existing call notification [${notification.id}], cancelling it") - manager.cancel(notification.id) + manager.cancel(CALL_TAG, notification.id) + } else if (notification.tag == CHAT_TAG) { + Log.i("[Notifications Manager] Found existing chat notification [${notification.id}]") + previousChatNotifications.add(notification.id) } } } @@ -240,7 +250,7 @@ class NotificationsManager(private val context: Context) { } for (notifiable in callNotificationsMap.values) { - notificationManager.cancel(notifiable.notificationId) + notificationManager.cancel(CALL_TAG, notifiable.notificationId) } stopForegroundNotification() @@ -252,9 +262,9 @@ class NotificationsManager(private val context: Context) { notificationManager.notify(tag, id, notification) } - fun cancel(id: Int) { - Log.i("[Notifications Manager] Canceling $id") - notificationManager.cancel(id) + fun cancel(id: Int, tag: String) { + Log.i("[Notifications Manager] Canceling [$id] with tag [$tag]") + notificationManager.cancel(tag, id) } fun resetChatNotificationCounterForSipUri(sipUri: String) { @@ -510,7 +520,7 @@ class NotificationsManager(private val context: Context) { } fun dismissMissedCallNotification() { - cancel(MISSED_CALLS_NOTIF_ID) + cancel(MISSED_CALLS_NOTIF_ID, MISSED_CALL_TAG) } fun displayCallNotification(call: Call, useAsForeground: Boolean = false) { @@ -611,7 +621,7 @@ class NotificationsManager(private val context: Context) { val address = call.remoteAddress.asStringUriOnly() val notifiable: Notifiable? = callNotificationsMap[address] if (notifiable != null) { - cancel(notifiable.notificationId) + cancel(notifiable.notificationId, CALL_TAG) callNotificationsMap.remove(address) } else { Log.w("[Notifications Manager] No notification found for call ${call.callLog.callId}") @@ -758,7 +768,23 @@ class NotificationsManager(private val context: Context) { if (notifiable != null) { Log.i("[Notifications Manager] Dismissing notification for chat room $room with id ${notifiable.notificationId}") notifiable.messages.clear() - cancel(notifiable.notificationId) + cancel(notifiable.notificationId, CHAT_TAG) + } else { + val previousNotificationId = previousChatNotifications.find { id -> id == room.creationTime.toInt() } + if (previousNotificationId != null) { + Log.i("[Notifications Manager] Found previous notification with same ID [$previousNotificationId], canceling it") + cancel(previousNotificationId, CHAT_TAG) + } + } + } + + fun disableDismissNotificationUponReadForChatRoom(chatRoom: ChatRoom) { + val address = chatRoom.peerAddress.asStringUriOnly() + val notifiable: Notifiable? = chatNotificationsMap[address] + if (notifiable != null) { + Log.i("[Notifications Manager] Prevent notification with id [${notifiable.notificationId}] from being dismissed when chat room will be marked as read") + notifiable.dismissNotificationUponReadChatRoom = false + chatNotificationsMap[address] = notifiable } } From ed59215e9c6e2ce62f44d280f2a6ae4e80ef3656 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 25 Nov 2021 10:37:18 +0100 Subject: [PATCH 046/130] Trying to fix notification not dismissed sometimes when marking it as read --- .../NotificationBroadcastReceiver.kt | 19 +++++++++++++++---- .../notifications/NotificationsManager.kt | 7 +++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt b/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt index f0161a4c3..5215bdf2d 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt @@ -19,6 +19,7 @@ */ package org.linphone.notifications +import android.app.NotificationManager import android.app.RemoteInput import android.content.BroadcastReceiver import android.content.Context @@ -33,13 +34,13 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { val notificationId = intent.getIntExtra(NotificationsManager.INTENT_NOTIF_ID, 0) if (intent.action == NotificationsManager.INTENT_REPLY_NOTIF_ACTION || intent.action == NotificationsManager.INTENT_MARK_AS_READ_ACTION) { - handleChatIntent(intent, notificationId) + handleChatIntent(context, intent, notificationId) } else if (intent.action == NotificationsManager.INTENT_ANSWER_CALL_NOTIF_ACTION || intent.action == NotificationsManager.INTENT_HANGUP_CALL_NOTIF_ACTION) { handleCallIntent(intent) } } - private fun handleChatIntent(intent: Intent, notificationId: Int) { + private fun handleChatIntent(context: Context, intent: Intent, notificationId: Int) { val remoteSipAddress = intent.getStringExtra(NotificationsManager.INTENT_REMOTE_ADDRESS) if (remoteSipAddress == null) { Log.e("[Notification Broadcast Receiver] Remote SIP address is null for notification id $notificationId") @@ -84,7 +85,11 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { msg.send() Log.i("[Notification Broadcast Receiver] Reply sent for notif id $notificationId") } else { - coreContext.notificationsManager.dismissChatNotification(room) + if (!coreContext.notificationsManager.dismissChatNotification(room)) { + Log.w("[Notification Broadcast Receiver] Notifications Manager failed to cancel notification") + val notificationManager = context.getSystemService(NotificationManager::class.java) + notificationManager.cancel(NotificationsManager.CHAT_TAG, notificationId) + } } } @@ -107,7 +112,13 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { if (intent.action == NotificationsManager.INTENT_ANSWER_CALL_NOTIF_ACTION) { coreContext.answerCall(call) } else { - if (call.state == Call.State.IncomingReceived || call.state == Call.State.IncomingEarlyMedia) coreContext.declineCall(call) else coreContext.terminateCall(call) + if (call.state == Call.State.IncomingReceived || + call.state == Call.State.IncomingEarlyMedia + ) { + coreContext.declineCall(call) + } else { + coreContext.terminateCall(call) + } } } diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index b7b58b63e..f2100c6a8 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -93,7 +93,7 @@ class NotificationsManager(private val context: Context) { private const val SERVICE_NOTIF_ID = 1 private const val MISSED_CALLS_NOTIF_ID = 2 - private const val CHAT_TAG = "Chat" + const val CHAT_TAG = "Chat" private const val CALL_TAG = "Call" private const val MISSED_CALL_TAG = "Missed call" } @@ -762,20 +762,23 @@ class NotificationsManager(private val context: Context) { displayChatNotifiable(message.chatRoom, notifiable) } - fun dismissChatNotification(room: ChatRoom) { + fun dismissChatNotification(room: ChatRoom): Boolean { val address = room.peerAddress.asStringUriOnly() val notifiable: Notifiable? = chatNotificationsMap[address] if (notifiable != null) { Log.i("[Notifications Manager] Dismissing notification for chat room $room with id ${notifiable.notificationId}") notifiable.messages.clear() cancel(notifiable.notificationId, CHAT_TAG) + return true } else { val previousNotificationId = previousChatNotifications.find { id -> id == room.creationTime.toInt() } if (previousNotificationId != null) { Log.i("[Notifications Manager] Found previous notification with same ID [$previousNotificationId], canceling it") cancel(previousNotificationId, CHAT_TAG) + return true } } + return false } fun disableDismissNotificationUponReadForChatRoom(chatRoom: ChatRoom) { From ef6bbbc3c87aff97030a5a03f675bc1c268fed2c Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 25 Nov 2021 10:47:58 +0100 Subject: [PATCH 047/130] Remove internation prefix from phone number in assistant, if possible --- .../assistant/viewmodels/AbstractPhoneViewModel.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPhoneViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPhoneViewModel.kt index 3daca0267..bfc4c0e10 100644 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPhoneViewModel.kt +++ b/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPhoneViewModel.kt @@ -56,14 +56,19 @@ abstract class AbstractPhoneViewModel(val accountCreator: AccountCreator) : } fun updateFromPhoneNumberAndOrDialPlan(number: String?, dialPlan: DialPlan?) { + val internationalPrefix = "+${dialPlan?.countryCallingCode}" if (dialPlan != null) { Log.i("[Assistant] Found prefix from dial plan: ${dialPlan.countryCallingCode}") - prefix.value = "+${dialPlan.countryCallingCode}" + prefix.value = internationalPrefix } if (number != null) { Log.i("[Assistant] Found phone number: $number") - phoneNumber.value = number!! + phoneNumber.value = if (number.startsWith(internationalPrefix)) { + number.substring(internationalPrefix.length) + } else { + number + } } } From 835b2dd86371af9bf260deba1892881d4e4a1182 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 26 Nov 2021 15:28:10 +0100 Subject: [PATCH 048/130] Fixed chat messages not marked as read when chat bubble is opened when they are received --- .../activities/chat_bubble/ChatBubbleActivity.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt index 47dde1db1..714773897 100644 --- a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt +++ b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt @@ -37,6 +37,8 @@ import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter import org.linphone.activities.main.chat.viewmodels.* import org.linphone.activities.main.viewmodels.ListTopBarViewModel import org.linphone.core.ChatRoom +import org.linphone.core.ChatRoomListenerStub +import org.linphone.core.EventLog import org.linphone.core.Factory import org.linphone.core.tools.Log import org.linphone.databinding.ChatBubbleActivityBinding @@ -58,6 +60,12 @@ class ChatBubbleActivity : GenericActivity() { } } + private val listener = object : ChatRoomListenerStub() { + override fun onChatMessageReceived(chatRoom: ChatRoom, eventLog: EventLog) { + chatRoom.markAsRead() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -172,6 +180,8 @@ class ChatBubbleActivity : GenericActivity() { override fun onResume() { super.onResume() + viewModel.chatRoom.addListener(listener) + val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly() coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = peerAddress coreContext.notificationsManager.resetChatNotificationCounterForSipUri(peerAddress) @@ -184,6 +194,8 @@ class ChatBubbleActivity : GenericActivity() { } override fun onPause() { + viewModel.chatRoom.removeListener(listener) + coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null super.onPause() From eb0b998c2cc21814616541497a0b7950acf01884 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 26 Nov 2021 16:57:46 +0100 Subject: [PATCH 049/130] Reworked used of tags to workaround duplicated notifications due to use of tags with foreground service --- .../notifications/NotificationsManager.kt | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index f2100c6a8..eea1fc8cf 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -94,7 +94,6 @@ class NotificationsManager(private val context: Context) { private const val MISSED_CALLS_NOTIF_ID = 2 const val CHAT_TAG = "Chat" - private const val CALL_TAG = "Call" private const val MISSED_CALL_TAG = "Missed call" } @@ -224,9 +223,9 @@ class NotificationsManager(private val context: Context) { val manager = context.getSystemService(NotificationManager::class.java) as NotificationManager for (notification in manager.activeNotifications) { - if (notification.tag == CALL_TAG) { - Log.w("[Notifications Manager] Found existing call notification [${notification.id}], cancelling it") - manager.cancel(CALL_TAG, notification.id) + if (notification.tag.isNullOrEmpty()) { // We use null tag for call notifications otherwise it will create duplicates when used with Service.startForeground()... + Log.w("[Notifications Manager] Found existing call? notification [${notification.id}], cancelling it") + manager.cancel(notification.tag, notification.id) } else if (notification.tag == CHAT_TAG) { Log.i("[Notifications Manager] Found existing chat notification [${notification.id}]") previousChatNotifications.add(notification.id) @@ -250,19 +249,19 @@ class NotificationsManager(private val context: Context) { } for (notifiable in callNotificationsMap.values) { - notificationManager.cancel(CALL_TAG, notifiable.notificationId) + notificationManager.cancel(notifiable.notificationId) } stopForegroundNotification() coreContext.core.removeListener(listener) } - private fun notify(id: Int, notification: Notification, tag: String) { + private fun notify(id: Int, notification: Notification, tag: String? = null) { Log.i("[Notifications Manager] Notifying [$id] with tag [$tag]") notificationManager.notify(tag, id, notification) } - fun cancel(id: Int, tag: String) { + fun cancel(id: Int, tag: String? = null) { Log.i("[Notifications Manager] Canceling [$id] with tag [$tag]") notificationManager.cancel(tag, id) } @@ -290,34 +289,38 @@ class NotificationsManager(private val context: Context) { service = coreService when { currentForegroundServiceNotificationId != 0 -> { - Log.e("[Notifications Manager] There is already a foreground service notification") + Log.e("[Notifications Manager] There is already a foreground service notification [$currentForegroundServiceNotificationId]") } coreContext.core.callsNb > 0 -> { // When this method will be called, we won't have any notification yet val call = coreContext.core.currentCall ?: coreContext.core.calls[0] when (call.state) { Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> { + Log.i("[Notifications Manager] Creating incoming call notification to be used as foreground service") displayIncomingCallNotification(call, true) } - else -> displayCallNotification(call, true) + else -> { + Log.i("[Notifications Manager] Creating call notification to be used as foreground service") + displayCallNotification(call, true) + } } } } } fun startForeground(coreService: CoreService, useAutoStartDescription: Boolean = true) { - Log.i("[Notifications Manager] Starting service as foreground") if (serviceNotification == null) { createServiceNotification(useAutoStartDescription) } currentForegroundServiceNotificationId = SERVICE_NOTIF_ID + Log.i("[Notifications Manager] Starting service as foreground [$currentForegroundServiceNotificationId]") coreService.startForeground(currentForegroundServiceNotificationId, serviceNotification) service = coreService } private fun startForeground(notificationId: Int, callNotification: Notification) { if (currentForegroundServiceNotificationId == 0 && service != null) { - Log.i("[Notifications Manager] Starting service as foreground using call notification") + Log.i("[Notifications Manager] Starting service as foreground using call notification [$notificationId]") currentForegroundServiceNotificationId = notificationId service?.startForeground(currentForegroundServiceNotificationId, callNotification) } @@ -325,7 +328,7 @@ class NotificationsManager(private val context: Context) { private fun stopForegroundNotification() { if (service != null) { - Log.i("[Notifications Manager] Stopping service as foreground") + Log.i("[Notifications Manager] Stopping service as foreground [$currentForegroundServiceNotificationId]") service?.stopForeground(true) currentForegroundServiceNotificationId = 0 } @@ -333,14 +336,14 @@ class NotificationsManager(private val context: Context) { fun stopForegroundNotificationIfPossible() { if (service != null && currentForegroundServiceNotificationId == SERVICE_NOTIF_ID && !corePreferences.keepServiceAlive) { - Log.i("[Notifications Manager] Stopping auto-started service notification") + Log.i("[Notifications Manager] Stopping auto-started service notification [$currentForegroundServiceNotificationId]") stopForegroundNotification() } } fun stopCallForeground() { if (service != null && currentForegroundServiceNotificationId != SERVICE_NOTIF_ID && !corePreferences.keepServiceAlive) { - Log.i("[Notifications Manager] Stopping call notification used as foreground service") + Log.i("[Notifications Manager] Stopping call notification [$currentForegroundServiceNotificationId] used as foreground service") stopForegroundNotification() } } @@ -415,7 +418,7 @@ class NotificationsManager(private val context: Context) { val notifiable = getNotifiableForCall(call) if (notifiable.notificationId == currentForegroundServiceNotificationId) { - Log.w("[Notifications Manager] Incoming call notification already displayed by foreground service, skipping") + Log.w("[Notifications Manager] Incoming call notification already displayed by foreground service [${notifiable.notificationId}], skipping") return } @@ -466,12 +469,11 @@ class NotificationsManager(private val context: Context) { } val notification = builder.build() - - Log.i("[Notifications Manager] Notifying incoming call notification") - notify(notifiable.notificationId, notification, CALL_TAG) + Log.i("[Notifications Manager] Notifying incoming call notification [${notifiable.notificationId}]") + notify(notifiable.notificationId, notification) if (useAsForeground) { - Log.i("[Notifications Manager] Notifying incoming call notification for foreground service") + Log.i("[Notifications Manager] Notifying incoming call notification for foreground service [${notifiable.notificationId}]") startForeground(notifiable.notificationId, notification) } } @@ -609,10 +611,11 @@ class NotificationsManager(private val context: Context) { } val notification = builder.build() - - notify(notifiable.notificationId, notification, CALL_TAG) + Log.i("[Notifications Manager] Notifying call notification [${notifiable.notificationId}]") + notify(notifiable.notificationId, notification) if (useAsForeground) { + Log.i("[Notifications Manager] Notifying call notification for foreground service [${notifiable.notificationId}]") startForeground(notifiable.notificationId, notification) } } @@ -621,7 +624,7 @@ class NotificationsManager(private val context: Context) { val address = call.remoteAddress.asStringUriOnly() val notifiable: Notifiable? = callNotificationsMap[address] if (notifiable != null) { - cancel(notifiable.notificationId, CALL_TAG) + cancel(notifiable.notificationId) callNotificationsMap.remove(address) } else { Log.w("[Notifications Manager] No notification found for call ${call.callLog.callId}") From 60cc5a31c48e4c20cfdefb9bce997f8f55ba97a8 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 29 Nov 2021 10:15:05 +0100 Subject: [PATCH 050/130] Added IME flag asking Android not to process user input in secured chat rooms --- CHANGELOG.md | 1 + .../main/chat/viewmodels/ChatMessageSendingViewModel.kt | 9 +++++++++ .../org/linphone/compatibility/Api21Compatibility.kt | 6 ++++++ .../org/linphone/compatibility/Api26Compatibility.kt | 5 +++++ .../java/org/linphone/compatibility/Compatibility.kt | 7 +++++++ app/src/main/res/layout/chat_bubble_activity.xml | 2 +- app/src/main/res/layout/chat_room_detail_fragment.xml | 2 +- 7 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06292757f..a9e69a0cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Group changes to describe their impact on the project, as follows: - Allow video recording in chat file sharing - Unread messages indicator in chat conversation that separates read & unread messages - Notify incoming/outgoing calls on bluetooth devices using self-managed connections from telecom manager API (disables SDK audio focus) +- Ask Android to not process what user types in an encrypted chat room to improve privacy, see [IME_FLAG_NO_PERSONALIZED_LEARNING](https://developer.android.com/reference/android/view/inputmethod/EditorInfo#IME_FLAG_NO_PERSONALIZED_LEARNING) - New video call UI on foldable device like Galaxy Z Fold - Setting to automatically record all calls diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt index dfefccc3f..30917cae6 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt @@ -19,6 +19,7 @@ */ package org.linphone.activities.main.chat.viewmodels +import android.view.inputmethod.EditorInfo import android.widget.Toast import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -37,6 +38,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.activities.main.chat.data.ChatMessageAttachmentData import org.linphone.activities.main.chat.data.ChatMessageData +import org.linphone.compatibility.Compatibility import org.linphone.core.* import org.linphone.core.tools.Log import org.linphone.utils.AppUtils @@ -88,6 +90,13 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() val voiceRecordPlayingPosition = MutableLiveData() + val imeFlags: Int = if (chatRoom.hasCapability(ChatRoomCapabilities.Encrypted.toInt())) { + // IME_FLAG_NO_PERSONALIZED_LEARNING is only available on Android 8 and newer + Compatibility.getImeFlagsForSecureChatRoom() + } else { + EditorInfo.IME_FLAG_NO_EXTRACT_UI + } + private val recorder: Recorder private var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null diff --git a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt index 11aabca98..807718ba2 100644 --- a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt @@ -34,6 +34,7 @@ import android.os.Vibrator import android.provider.MediaStore import android.provider.Settings import android.view.WindowManager +import android.view.inputmethod.EditorInfo import org.linphone.R import org.linphone.core.Content import org.linphone.core.tools.Log @@ -45,6 +46,7 @@ import org.linphone.utils.PermissionHelper @TargetApi(21) class Api21Compatibility { companion object { + @SuppressLint("MissingPermission") fun getDeviceName(context: Context): String { var name = BluetoothAdapter.getDefaultAdapter().name if (name == null) { @@ -212,5 +214,9 @@ class Api21Compatibility { fun getUpdateCurrentPendingIntentFlag(): Int { return PendingIntent.FLAG_UPDATE_CURRENT } + + fun getImeFlagsForSecureChatRoom(): Int { + return EditorInfo.IME_FLAG_NO_EXTRACT_UI + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt index 906fafbf4..78213b637 100644 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt @@ -32,6 +32,7 @@ import android.media.AudioAttributes import android.os.VibrationEffect import android.os.Vibrator import android.view.WindowManager +import android.view.inputmethod.EditorInfo import androidx.core.app.NotificationManagerCompat import org.linphone.R import org.linphone.core.tools.Log @@ -149,5 +150,9 @@ class Api26Compatibility { code ) } + + fun getImeFlagsForSecureChatRoom(): Int { + return EditorInfo.IME_FLAG_NO_EXTRACT_UI or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index ca66774c4..a38079a46 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -261,5 +261,12 @@ class Compatibility { } return Api21Compatibility.getUpdateCurrentPendingIntentFlag() } + + fun getImeFlagsForSecureChatRoom(): Int { + if (Version.sdkAboveOrEqual(Version.API26_O_80)) { + return Api26Compatibility.getImeFlagsForSecureChatRoom() + } + return Api21Compatibility.getImeFlagsForSecureChatRoom() + } } } diff --git a/app/src/main/res/layout/chat_bubble_activity.xml b/app/src/main/res/layout/chat_bubble_activity.xml index 7883ba600..a3972ae18 100644 --- a/app/src/main/res/layout/chat_bubble_activity.xml +++ b/app/src/main/res/layout/chat_bubble_activity.xml @@ -97,6 +97,7 @@ android:id="@+id/message" android:enabled="@{!chatSendingViewModel.isReadOnly}" android:text="@={chatSendingViewModel.textToSend}" + android:imeOptions="@{chatSendingViewModel.imeFlags}" android:hint="@string/chat_room_sending_message_hint" android:layout_width="0dp" android:layout_height="wrap_content" @@ -106,7 +107,6 @@ android:layout_marginStart="10dp" android:layout_weight="1" android:background="@drawable/resizable_text_field" - android:imeOptions="flagNoExtractUi" android:inputType="textShortMessage|textMultiLine|textAutoComplete|textAutoCorrect|textCapSentences" android:maxLines="6" android:padding="5dp" diff --git a/app/src/main/res/layout/chat_room_detail_fragment.xml b/app/src/main/res/layout/chat_room_detail_fragment.xml index d7094e429..d35c9d8eb 100644 --- a/app/src/main/res/layout/chat_room_detail_fragment.xml +++ b/app/src/main/res/layout/chat_room_detail_fragment.xml @@ -229,6 +229,7 @@ android:id="@+id/message" android:text="@={chatSendingViewModel.textToSend}" android:hint="@{chatSendingViewModel.isPendingAnswer ? @string/chat_room_sending_reply_hint : @string/chat_room_sending_message_hint}" + android:imeOptions="@{chatSendingViewModel.imeFlags}" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" @@ -236,7 +237,6 @@ android:layout_marginBottom="10dp" android:layout_weight="1" android:background="@drawable/resizable_text_field" - android:imeOptions="flagNoExtractUi" android:inputType="textShortMessage|textMultiLine|textAutoComplete|textAutoCorrect|textCapSentences" android:maxLines="6" android:padding="5dp" From 72ae8f2e67c32fa1834b438a1840c31f0bf15f72 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 29 Nov 2021 11:43:40 +0100 Subject: [PATCH 051/130] Fixed crash if sharedViewModel has not been initialized when saving fragment instance state --- .../main/chat/fragments/DetailChatRoomFragment.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index c18fb2f08..0a4cf8819 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -109,11 +109,15 @@ class DetailChatRoomFragment : MasterFragment Date: Mon, 29 Nov 2021 13:55:09 +0100 Subject: [PATCH 052/130] Added support of left control + enter keys to send message --- CHANGELOG.md | 1 + .../chat/fragments/DetailChatRoomFragment.kt | 8 +++++ .../main/chat/views/RichEditText.kt | 29 +++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e69a0cb..61c16738d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Group changes to describe their impact on the project, as follows: - Ask Android to not process what user types in an encrypted chat room to improve privacy, see [IME_FLAG_NO_PERSONALIZED_LEARNING](https://developer.android.com/reference/android/view/inputmethod/EditorInfo#IME_FLAG_NO_PERSONALIZED_LEARNING) - New video call UI on foldable device like Galaxy Z Fold - Setting to automatically record all calls +- When using a physical keyboard, use left control + enter keys to send message ### Changed - UI has been reworked around SlidingPane component to better handle tablets & foldable devices diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 0a4cf8819..f48bc63b8 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -53,6 +53,7 @@ import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter import org.linphone.activities.main.chat.data.ChatMessageData import org.linphone.activities.main.chat.data.EventLogData import org.linphone.activities.main.chat.viewmodels.* +import org.linphone.activities.main.chat.views.RichEditTextSendListener import org.linphone.activities.main.fragments.MasterFragment import org.linphone.activities.main.viewmodels.DialogViewModel import org.linphone.activities.main.viewmodels.SharedMainViewModel @@ -511,6 +512,13 @@ class DetailChatRoomFragment : MasterFragment + if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT) { + if (event.action == KeyEvent.ACTION_DOWN) { + controlPressed = true + } else if (event.action == KeyEvent.ACTION_UP) { + controlPressed = false + } + false + } else if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP && controlPressed) { + sendListener?.onControlEnterPressedAndReleased() + true + } else { + false + } + } } } + +interface RichEditTextSendListener { + fun onControlEnterPressedAndReleased() +} From dc442520bafc6cb6acb55619b5bb99f5a332d21d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 30 Nov 2021 15:58:36 +0100 Subject: [PATCH 053/130] Updated appcompat to 1.4.0 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d8b3cf323..4b23da8fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -215,7 +215,7 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.media:media:1.4.3' implementation 'androidx.fragment:fragment-ktx:1.4.0' implementation 'androidx.core:core-ktx:1.7.0' From 051a6fb39389bf0bbde937cfcae94e5e887b86d0 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 1 Dec 2021 13:15:23 +0100 Subject: [PATCH 054/130] Fixed incoming call ringtone not being stopped when pressing volume keys if TelecomManager is enabled --- app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt | 5 +++++ app/src/main/java/org/linphone/telecom/TelecomHelper.kt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt b/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt index 05e7fe67f..2173faff3 100644 --- a/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt +++ b/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt @@ -102,6 +102,11 @@ class NativeCallWrapper(var callId: String) : Connection() { getCall()?.terminate() ?: selfDestroy() } + override fun onSilence() { + Log.i("[Connection] Call with id: $callId asked to be silenced") + coreContext.core.stopRinging() + } + private fun getCall(): Call? { return coreContext.core.getCallByCallid(callId) } diff --git a/app/src/main/java/org/linphone/telecom/TelecomHelper.kt b/app/src/main/java/org/linphone/telecom/TelecomHelper.kt index 531cb028b..dd08ff791 100644 --- a/app/src/main/java/org/linphone/telecom/TelecomHelper.kt +++ b/app/src/main/java/org/linphone/telecom/TelecomHelper.kt @@ -208,7 +208,7 @@ class TelecomHelper private constructor(context: Context) { if (call.dir == Call.Dir.Outgoing) { extras.putString( EXTRA_CALL_BACK_NUMBER, - call.callLog.fromAddress.asStringUriOnly() + call.remoteAddress.asStringUriOnly() ) } else { extras.putParcelable(EXTRA_INCOMING_CALL_ADDRESS, Uri.parse(address.asStringUriOnly())) From 030629162696656fe6295679b0c8f815b83b63b6 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 1 Dec 2021 16:34:45 +0100 Subject: [PATCH 055/130] Fixed choose account in which to save newly created contact dialog when in dark mode --- .../main/res/layout/contact_sync_account_picker_fragment.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/contact_sync_account_picker_fragment.xml b/app/src/main/res/layout/contact_sync_account_picker_fragment.xml index ce4ed4c77..cce12b119 100644 --- a/app/src/main/res/layout/contact_sync_account_picker_fragment.xml +++ b/app/src/main/res/layout/contact_sync_account_picker_fragment.xml @@ -11,7 +11,8 @@ + android:orientation="vertical" + android:background="?attr/backgroundColor"> Date: Mon, 6 Dec 2021 14:52:54 +0100 Subject: [PATCH 056/130] Fixed issue in CHANGELOG due to bad merge probably --- CHANGELOG.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c16738d..1081df1d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,16 +91,11 @@ Group changes to describe their impact on the project, as follows: - Fixed various crashes & other issues - SDK bumped to 5.0.10 -## [4.5.1] - Unreleased - -### Added -- Reply to chat message feature -- Voice recordings messages +## [4.5.1] - 2021-07-15 ### Changed -- Navigation was reworked using SlidingPane widget, reducing code & improving UI on foldables - -### Removed +- Bugs & crashes have been fixed +- SDK bumped to 5.0.1 ## [4.5.0] - 2021-07-08 From fe9f9cbeccd19eb82064ebb2de4d06c511b63e08 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 7 Dec 2021 09:25:50 +0100 Subject: [PATCH 057/130] Make sure PhoneAccount identity is valid to prevent crash in Android OS at startup --- .../main/dialer/fragments/DialerFragment.kt | 4 ++-- .../main/settings/fragments/CallSettingsFragment.kt | 5 ++--- .../org/linphone/compatibility/Api26Compatibility.kt | 5 +++++ .../org/linphone/compatibility/Api30Compatibility.kt | 7 +++++++ .../java/org/linphone/compatibility/Compatibility.kt | 10 +++++++++- app/src/main/java/org/linphone/core/CoreContext.kt | 11 ++++++++--- .../main/java/org/linphone/telecom/TelecomHelper.kt | 12 ++++++++++-- .../main/java/org/linphone/utils/PermissionHelper.kt | 8 -------- 8 files changed, 43 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt index f2606d932..64b576610 100644 --- a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt @@ -254,11 +254,11 @@ class DialerFragment : SecureFragment() { if (corePreferences.manuallyDisabledTelecomManager) { Log.w("[Dialer] User has manually disabled Telecom Manager feature") } else { - if (PermissionHelper.get().hasTelecomManagerPermissions()) { + if (Compatibility.hasTelecomManagerPermissions(requireContext())) { enableTelecomManager() } else { Log.i("[Dialer] Asking for Telecom Manager permissions") - Compatibility.requestTelecomManagerPermission(requireActivity(), 1) + Compatibility.requestTelecomManagerPermissions(requireActivity(), 1) } } } else { diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt index 5ba103442..23bd82639 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt @@ -37,7 +37,6 @@ import org.linphone.databinding.SettingsCallFragmentBinding import org.linphone.mediastream.Version import org.linphone.telecom.TelecomHelper import org.linphone.utils.Event -import org.linphone.utils.PermissionHelper class CallSettingsFragment : GenericSettingFragment() { private lateinit var viewModel: CallSettingsViewModel @@ -93,8 +92,8 @@ class CallSettingsFragment : GenericSettingFragment viewLifecycleOwner, { it.consume { - if (!PermissionHelper.get().hasTelecomManagerPermissions()) { - Compatibility.requestTelecomManagerPermission(requireActivity(), 1) + if (!Compatibility.hasTelecomManagerPermissions(requireContext())) { + Compatibility.requestTelecomManagerPermissions(requireActivity(), 1) } else if (!TelecomHelper.exists()) { corePreferences.useTelecomManager = true Log.w("[Telecom Helper] Doesn't exists yet, creating it") diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt index 78213b637..b75bf2c4f 100644 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt @@ -151,6 +151,11 @@ class Api26Compatibility { ) } + fun hasTelecomManagerPermission(context: Context): Boolean { + return Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_STATE) && + Compatibility.hasPermission(context, Manifest.permission.MANAGE_OWN_CALLS) + } + fun getImeFlagsForSecureChatRoom(): Int { return EditorInfo.IME_FLAG_NO_EXTRACT_UI or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING } diff --git a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt index a32a5b4db..70637bbd2 100644 --- a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt @@ -50,12 +50,19 @@ class Api30Compatibility { activity.requestPermissions( arrayOf( Manifest.permission.READ_PHONE_NUMBERS, + Manifest.permission.READ_PHONE_STATE, Manifest.permission.MANAGE_OWN_CALLS ), code ) } + fun hasTelecomManagerPermission(context: Context): Boolean { + return Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_NUMBERS) && + Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_STATE) && + Compatibility.hasPermission(context, Manifest.permission.MANAGE_OWN_CALLS) + } + fun removeChatRoomShortcut(context: Context, chatRoom: ChatRoom) { val shortcutManager = context.getSystemService(ShortcutManager::class.java) val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index a38079a46..35f4e7922 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -65,7 +65,15 @@ class Compatibility { } // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers - fun requestTelecomManagerPermission(activity: Activity, code: Int) { + fun hasTelecomManagerPermissions(context: Context): Boolean { + return if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { + Api30Compatibility.hasTelecomManagerPermission(context) + } else { + Api26Compatibility.hasTelecomManagerPermission(context) + } + } + + fun requestTelecomManagerPermissions(activity: Activity, code: Int) { if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { Api30Compatibility.requestTelecomManagerPermission(activity, code) } else { diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 383a47a90..4eaf53c46 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -312,9 +312,14 @@ class CoreContext(val context: Context, coreConfig: Config) { // CoreContext listener must be added first! if (Version.sdkAboveOrEqual(Version.API26_O_80) && corePreferences.useTelecomManager) { - Log.i("[Context] Creating TelecomHelper, disabling audio focus requests in AudioHelper") - core.config.setBool("audio", "android_disable_audio_focus_requests", true) - TelecomHelper.create(context) + if (Compatibility.hasTelecomManagerPermissions(context)) { + Log.i("[Context] Creating Telecom Helper, disabling audio focus requests in AudioHelper") + core.config.setBool("audio", "android_disable_audio_focus_requests", true) + TelecomHelper.create(context) + } else { + Log.w("[Context] Can't create Telecom Helper, permissions have been revoked") + corePreferences.useTelecomManager = false + } } if (isPush) { diff --git a/app/src/main/java/org/linphone/telecom/TelecomHelper.kt b/app/src/main/java/org/linphone/telecom/TelecomHelper.kt index dd08ff791..c2a29d759 100644 --- a/app/src/main/java/org/linphone/telecom/TelecomHelper.kt +++ b/app/src/main/java/org/linphone/telecom/TelecomHelper.kt @@ -30,6 +30,7 @@ import android.telecom.PhoneAccount import android.telecom.PhoneAccountHandle import android.telecom.TelecomManager import android.telecom.TelecomManager.* +import java.lang.Exception import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.contact.Contact @@ -159,9 +160,16 @@ class TelecomHelper private constructor(context: Context) { ComponentName(context, TelecomConnectionService::class.java), context.packageName ) - val identity = coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly() ?: "" + // Take care that identity may be parsed, otherwise Android OS may crash during startup + // and user will have to do a factory reset... + val identity = coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly() + ?: coreContext.core.createPrimaryContactParsed()?.asStringUriOnly() + ?: "sip:linphone.android@sip.linphone.org" + + val address = Uri.parse(identity) + ?: throw Exception("[Telecom Helper] Identity address for phone account is null!") val account = PhoneAccount.builder(accountHandle, context.getString(R.string.app_name)) - .setAddress(Uri.parse(identity)) + .setAddress(address) .setIcon(Icon.createWithResource(context, R.drawable.linphone_logo_tinted)) .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED) .setHighlightColor(context.getColor(R.color.primary_color)) diff --git a/app/src/main/java/org/linphone/utils/PermissionHelper.kt b/app/src/main/java/org/linphone/utils/PermissionHelper.kt index 40fad7073..3d28a3b42 100644 --- a/app/src/main/java/org/linphone/utils/PermissionHelper.kt +++ b/app/src/main/java/org/linphone/utils/PermissionHelper.kt @@ -21,8 +21,6 @@ package org.linphone.utils import android.Manifest import android.content.Context -import android.os.Build -import androidx.annotation.RequiresApi import org.linphone.compatibility.Compatibility import org.linphone.core.tools.Log @@ -75,10 +73,4 @@ class PermissionHelper private constructor(private val context: Context) { fun hasRecordAudioPermission(): Boolean { return hasPermission(Manifest.permission.RECORD_AUDIO) } - - @RequiresApi(Build.VERSION_CODES.O) - fun hasTelecomManagerPermissions(): Boolean { - return hasPermission(Manifest.permission.READ_PHONE_NUMBERS) && - hasPermission(Manifest.permission.MANAGE_OWN_CALLS) - } } From b87cd8ad5bf1285230993369d5f8f92745cb596a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 7 Dec 2021 13:42:14 +0100 Subject: [PATCH 058/130] Improved text setting --- .../org/linphone/utils/DataBindingUtils.kt | 20 +++++++ .../views/SettingTextInputEditText.kt | 54 +++++++++++++++++++ .../main/res/layout/settings_widget_text.xml | 5 +- 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/linphone/views/SettingTextInputEditText.kt diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index 3be9be1be..62e2cfaf4 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -19,6 +19,7 @@ */ package org.linphone.utils +import android.app.Activity import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable @@ -29,6 +30,8 @@ import android.util.Patterns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager import android.widget.* import android.widget.SeekBar.OnSeekBarChangeListener import androidx.constraintlayout.widget.ConstraintLayout @@ -186,6 +189,23 @@ fun editTextSetting(view: EditText, lambda: () -> Unit) { }) } +@BindingAdapter("onSettingImeDone") +fun editTextImeDone(view: EditText, lambda: () -> Unit) { + view.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + lambda() + + view.clearFocus() + + val imm = view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(view.windowToken, 0) + + return@setOnEditorActionListener true + } + false + } +} + @BindingAdapter("onFocusChangeVisibilityOf") fun setEditTextOnFocusChangeVisibilityOf(editText: EditText, view: View) { editText.setOnFocusChangeListener { _, hasFocus -> diff --git a/app/src/main/java/org/linphone/views/SettingTextInputEditText.kt b/app/src/main/java/org/linphone/views/SettingTextInputEditText.kt new file mode 100644 index 000000000..6340f2896 --- /dev/null +++ b/app/src/main/java/org/linphone/views/SettingTextInputEditText.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.views + +import android.app.Activity +import android.content.Context +import android.util.AttributeSet +import android.view.inputmethod.InputMethodManager +import com.google.android.material.textfield.TextInputEditText +import org.linphone.activities.main.settings.SettingListener + +class SettingTextInputEditText : TextInputEditText { + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) + + fun fakeImeDone(listener: SettingListener) { + listener.onTextValueChanged(text.toString()) + + // Send IME action DONE to trigger onSettingImeDone binding adapter, but that doesn't work... + // val inputConnection = BaseInputConnection(this, true) + // inputConnection.performEditorAction(EditorInfo.IME_ACTION_DONE) + + // Will make check icon to disappear thanks to onFocusChangeVisibilityOf binding adapter + clearFocus() + + // Hide keyboard + val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(windowToken, 0) + } +} diff --git a/app/src/main/res/layout/settings_widget_text.xml b/app/src/main/res/layout/settings_widget_text.xml index ab33322a4..6a483763a 100644 --- a/app/src/main/res/layout/settings_widget_text.xml +++ b/app/src/main/res/layout/settings_widget_text.xml @@ -31,7 +31,7 @@ - Date: Tue, 7 Dec 2021 15:00:33 +0100 Subject: [PATCH 059/130] Fixed two icons tint in hat room menu while in dark mode --- app/src/main/res/layout/chat_room_menu.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/chat_room_menu.xml b/app/src/main/res/layout/chat_room_menu.xml index 069ec1159..00679ca0a 100644 --- a/app/src/main/res/layout/chat_room_menu.xml +++ b/app/src/main/res/layout/chat_room_menu.xml @@ -38,7 +38,7 @@ android:visibility="@{groupInfoHidden ? View.GONE : View.VISIBLE}" android:background="@drawable/menu_background" android:onClick="@{groupInfoListener}" - android:drawableRight="@drawable/menu_group_info_default" + android:drawableRight="@drawable/chat_room_menu_group_info" style="@style/popup_item_font" android:text="@string/chat_room_context_menu_group_info" /> @@ -48,7 +48,7 @@ android:visibility="@{devicesHidden ? View.GONE : View.VISIBLE}" android:background="@drawable/menu_background" android:onClick="@{devicesListener}" - android:drawableRight="@drawable/menu_security_default" + android:drawableRight="@drawable/chat_room_menu_security" style="@style/popup_item_font" android:text="@string/chat_room_context_menu_participants_devices" /> From 9572da70d415527cf71f309d1bba390daa265f30 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 6 Dec 2021 11:27:28 +0100 Subject: [PATCH 060/130] Do not show incoming call notification when we will decline it with busy reason right after --- .../java/org/linphone/core/CoreContext.kt | 52 +++++++++++-------- .../notifications/NotificationsManager.kt | 5 ++ 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 4eaf53c46..3274f7ee7 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -136,29 +136,9 @@ class CoreContext(val context: Context, coreConfig: Config) { ) { Log.i("[Context] Call state changed [$state]") if (state == Call.State.IncomingReceived || state == Call.State.IncomingEarlyMedia) { - if (!corePreferences.useTelecomManager) { // Can't use the following call with Telecom Manager API as it will "fake" GSM calls - var gsmCallActive = false - if (::phoneStateListener.isInitialized) { - gsmCallActive = phoneStateListener.isInCall() - } - - if (gsmCallActive) { - Log.w("[Context] Refusing the call with reason busy because a GSM call is active") - call.decline(Reason.Busy) - return - } - } else { - if (TelecomHelper.exists()) { - if (!TelecomHelper.get().isIncomingCallPermitted() || - TelecomHelper.get().isInManagedCall() - ) { - Log.w("[Context] Refusing the call with reason busy because Telecom Manager will reject the call") - call.decline(Reason.Busy) - return - } - } else { - Log.e("[Context] Telecom Manager singleton wasn't created!") - } + if (declineCallDueToGsmActiveCall()) { + call.decline(Reason.Busy) + return } // Starting SDK 24 (Android 7.0) we rely on the fullscreen intent of the call incoming notification @@ -437,6 +417,32 @@ class CoreContext(val context: Context, coreConfig: Config) { } } + fun declineCallDueToGsmActiveCall(): Boolean { + if (!corePreferences.useTelecomManager) { // Can't use the following call with Telecom Manager API as it will "fake" GSM calls + var gsmCallActive = false + if (::phoneStateListener.isInitialized) { + gsmCallActive = phoneStateListener.isInCall() + } + + if (gsmCallActive) { + Log.w("[Context] Refusing the call with reason busy because a GSM call is active") + return true + } + } else { + if (TelecomHelper.exists()) { + if (!TelecomHelper.get().isIncomingCallPermitted() || + TelecomHelper.get().isInManagedCall() + ) { + Log.w("[Context] Refusing the call with reason busy because Telecom Manager will reject the call") + return true + } + } else { + Log.e("[Context] Telecom Manager singleton wasn't created!") + } + } + return false + } + fun answerCallVideoUpdateRequest(call: Call, accept: Boolean) { val params = core.createCallParams(call) diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index eea1fc8cf..f298958b0 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -414,6 +414,11 @@ class NotificationsManager(private val context: Context) { } private fun displayIncomingCallNotification(call: Call, useAsForeground: Boolean = false) { + if (coreContext.declineCallDueToGsmActiveCall()) { + Log.w("[Notifications Manager] Call will be declined, do not show incoming call notification") + return + } + val address = LinphoneUtils.getDisplayableAddress(call.remoteAddress) val notifiable = getNotifiableForCall(call) From b48b9e42edc3808bba39535c344f5a1e04236364 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Sat, 11 Dec 2021 09:55:18 +0100 Subject: [PATCH 061/130] Fixed custom incoming call notification layout not working on some Xiaomi devices... --- .../compatibility/Api26Compatibility.kt | 51 ++++++++++++-- .../linphone/compatibility/Compatibility.kt | 16 +++++ .../compatibility/XiaomiCompatibility.kt | 67 +++++++++++++++++++ .../notifications/NotificationsManager.kt | 36 +--------- 4 files changed, 133 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt index b75bf2c4f..43860d978 100644 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt @@ -22,10 +22,7 @@ package org.linphone.compatibility import android.Manifest import android.annotation.SuppressLint import android.annotation.TargetApi -import android.app.Activity -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PictureInPictureParams +import android.app.* import android.content.Context import android.content.pm.PackageManager import android.media.AudioAttributes @@ -33,10 +30,19 @@ import android.os.VibrationEffect import android.os.Vibrator import android.view.WindowManager import android.view.inputmethod.EditorInfo +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import org.linphone.LinphoneApplication import org.linphone.R +import org.linphone.contact.Contact +import org.linphone.core.Call import org.linphone.core.tools.Log +import org.linphone.notifications.NotificationsManager import org.linphone.telecom.NativeCallWrapper +import org.linphone.utils.ImageUtils +import org.linphone.utils.LinphoneUtils @TargetApi(26) class Api26Compatibility { @@ -128,6 +134,43 @@ class Api26Compatibility { return WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } + fun createIncomingCallNotificationBuilder( + context: Context, + call: Call, + notificationsManager: NotificationsManager + ): NotificationCompat.Builder { + val contact: Contact? = LinphoneApplication.coreContext.contactsManager.findContactByAddress(call.remoteAddress) + val pictureUri = contact?.getContactThumbnailPictureUri() + val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) + val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) + val address = LinphoneUtils.getDisplayableAddress(call.remoteAddress) + + val notificationLayoutHeadsUp = RemoteViews(context.packageName, R.layout.call_incoming_notification_heads_up) + notificationLayoutHeadsUp.setTextViewText(R.id.caller, displayName) + notificationLayoutHeadsUp.setTextViewText(R.id.sip_uri, address) + notificationLayoutHeadsUp.setTextViewText(R.id.incoming_call_info, context.getString(R.string.incoming_call_notification_title)) + + if (roundPicture != null) { + notificationLayoutHeadsUp.setImageViewBitmap(R.id.caller_picture, roundPicture) + } + + return NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) + .setStyle(NotificationCompat.DecoratedCustomViewStyle()) + .addPerson(notificationsManager.getPerson(contact, displayName, roundPicture)) + .setSmallIcon(R.drawable.topbar_call_notification) + .setContentTitle(displayName) + .setContentText(context.getString(R.string.incoming_call_notification_title)) + .setCategory(NotificationCompat.CATEGORY_CALL) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(false) + .setShowWhen(true) + .setOngoing(true) + .setColor(ContextCompat.getColor(context, R.color.primary_color)) + .setCustomHeadsUpContentView(notificationLayoutHeadsUp) + } + @SuppressLint("MissingPermission") fun eventVibration(vibrator: Vibrator) { val effect = VibrationEffect.createWaveform(longArrayOf(0L, 100L, 100L), intArrayOf(0, VibrationEffect.DEFAULT_AMPLITUDE, 0), -1) diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index 35f4e7922..d149e0d0b 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -25,15 +25,20 @@ import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap import android.net.Uri +import android.os.Build import android.os.Vibrator import android.telephony.TelephonyManager import android.view.View import android.view.WindowManager +import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.fragment.app.Fragment +import java.util.* +import org.linphone.core.Call import org.linphone.core.ChatRoom import org.linphone.core.Content import org.linphone.mediastream.Version +import org.linphone.notifications.NotificationsManager import org.linphone.telecom.NativeCallWrapper @Suppress("DEPRECATION") @@ -165,6 +170,17 @@ class Compatibility { return WindowManager.LayoutParams.TYPE_PHONE } + fun createIncomingCallNotificationBuilder( + context: Context, + call: Call, + notificationsManager: NotificationsManager + ): NotificationCompat.Builder { + if (Build.MANUFACTURER.lowercase(Locale.getDefault()) == "xiaomi") { + return XiaomiCompatibility.createIncomingCallNotificationBuilder(context, call, notificationsManager) + } + return Api26Compatibility.createIncomingCallNotificationBuilder(context, call, notificationsManager) + } + /* Call */ fun canDrawOverlay(context: Context): Boolean { diff --git a/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt b/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt new file mode 100644 index 000000000..baced6f4f --- /dev/null +++ b/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.compatibility + +import android.annotation.TargetApi +import android.app.* +import android.content.Context +import android.graphics.BitmapFactory +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import org.linphone.LinphoneApplication +import org.linphone.R +import org.linphone.contact.Contact +import org.linphone.core.Call +import org.linphone.notifications.NotificationsManager +import org.linphone.utils.ImageUtils +import org.linphone.utils.LinphoneUtils + +@TargetApi(26) +class XiaomiCompatibility { + companion object { + fun createIncomingCallNotificationBuilder( + context: Context, + call: Call, + notificationsManager: NotificationsManager + ): NotificationCompat.Builder { + val contact: Contact? = LinphoneApplication.coreContext.contactsManager.findContactByAddress(call.remoteAddress) + val pictureUri = contact?.getContactThumbnailPictureUri() + val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) + val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) + val address = LinphoneUtils.getDisplayableAddress(call.remoteAddress) + + return NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) + .addPerson(notificationsManager.getPerson(contact, displayName, roundPicture)) + .setSmallIcon(R.drawable.topbar_call_notification) + .setLargeIcon(roundPicture ?: BitmapFactory.decodeResource(context.resources, R.drawable.avatar)) + .setContentTitle(displayName) + .setContentText(address) + .setSubText(context.getString(R.string.incoming_call_notification_title)) + .setCategory(NotificationCompat.CATEGORY_CALL) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(false) + .setShowWhen(true) + .setOngoing(true) + .setColor(ContextCompat.getColor(context, R.color.primary_color)) + } + } +} diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index f298958b0..c6ca1f0ec 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -28,7 +28,6 @@ import android.graphics.Bitmap import android.net.Uri import android.os.Bundle import android.webkit.MimeTypeMap -import android.widget.RemoteViews import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.Person @@ -397,7 +396,7 @@ class NotificationsManager(private val context: Context) { return notifiable } - private fun getPerson(contact: Contact?, displayName: String, picture: Bitmap?): Person { + fun getPerson(contact: Contact?, displayName: String, picture: Bitmap?): Person { return if (contact != null) { contact.getPerson() } else { @@ -419,19 +418,12 @@ class NotificationsManager(private val context: Context) { return } - val address = LinphoneUtils.getDisplayableAddress(call.remoteAddress) val notifiable = getNotifiableForCall(call) - if (notifiable.notificationId == currentForegroundServiceNotificationId) { Log.w("[Notifications Manager] Incoming call notification already displayed by foreground service [${notifiable.notificationId}], skipping") return } - val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress) - val pictureUri = contact?.getContactThumbnailPictureUri() - val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) - val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) - val incomingCallNotificationIntent = Intent(context, IncomingCallActivity::class.java) incomingCallNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val pendingIntent = PendingIntent.getActivity( @@ -441,33 +433,11 @@ class NotificationsManager(private val context: Context) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - val notificationLayoutHeadsUp = RemoteViews(context.packageName, R.layout.call_incoming_notification_heads_up) - notificationLayoutHeadsUp.setTextViewText(R.id.caller, displayName) - notificationLayoutHeadsUp.setTextViewText(R.id.sip_uri, address) - notificationLayoutHeadsUp.setTextViewText(R.id.incoming_call_info, context.getString(R.string.incoming_call_notification_title)) - - if (roundPicture != null) { - notificationLayoutHeadsUp.setImageViewBitmap(R.id.caller_picture, roundPicture) - } - - val builder = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) - .setStyle(NotificationCompat.DecoratedCustomViewStyle()) - .addPerson(getPerson(contact, displayName, roundPicture)) - .setSmallIcon(R.drawable.topbar_call_notification) - .setContentTitle(displayName) - .setContentText(context.getString(R.string.incoming_call_notification_title)) - .setCategory(NotificationCompat.CATEGORY_CALL) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setWhen(System.currentTimeMillis()) - .setAutoCancel(false) - .setShowWhen(true) - .setOngoing(true) - .setColor(ContextCompat.getColor(context, R.color.primary_color)) + val builder = Compatibility.createIncomingCallNotificationBuilder(context, call, this) + builder .setFullScreenIntent(pendingIntent, true) .addAction(getCallDeclineAction(notifiable)) .addAction(getCallAnswerAction(notifiable)) - .setCustomHeadsUpContentView(notificationLayoutHeadsUp) if (!corePreferences.preventInterfaceFromShowingUp) { builder.setContentIntent(pendingIntent) From 58e2fc98aa03f71adc8cf21dc59e30995d2a1f47 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Sat, 11 Dec 2021 10:24:23 +0100 Subject: [PATCH 062/130] Use Android 12 CallStyle notification for incoming calls --- .../compatibility/Api26Compatibility.kt | 23 ++++++-- .../compatibility/Api31Compatibility.kt | 55 +++++++++++++++++++ .../linphone/compatibility/Compatibility.kt | 18 ++++-- .../compatibility/XiaomiCompatibility.kt | 23 ++++++-- .../notifications/NotificationsManager.kt | 32 +++++------ 5 files changed, 116 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt index 43860d978..2f6da50f4 100644 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt @@ -34,11 +34,13 @@ import android.widget.RemoteViews import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat -import org.linphone.LinphoneApplication +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.contact.Contact import org.linphone.core.Call import org.linphone.core.tools.Log +import org.linphone.notifications.Notifiable import org.linphone.notifications.NotificationsManager import org.linphone.telecom.NativeCallWrapper import org.linphone.utils.ImageUtils @@ -134,12 +136,14 @@ class Api26Compatibility { return WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } - fun createIncomingCallNotificationBuilder( + fun createIncomingCallNotification( context: Context, call: Call, + notifiable: Notifiable, + pendingIntent: PendingIntent, notificationsManager: NotificationsManager - ): NotificationCompat.Builder { - val contact: Contact? = LinphoneApplication.coreContext.contactsManager.findContactByAddress(call.remoteAddress) + ): Notification { + val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress) val pictureUri = contact?.getContactThumbnailPictureUri() val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) @@ -154,7 +158,7 @@ class Api26Compatibility { notificationLayoutHeadsUp.setImageViewBitmap(R.id.caller_picture, roundPicture) } - return NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) + val builder = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) .setStyle(NotificationCompat.DecoratedCustomViewStyle()) .addPerson(notificationsManager.getPerson(contact, displayName, roundPicture)) .setSmallIcon(R.drawable.topbar_call_notification) @@ -168,7 +172,16 @@ class Api26Compatibility { .setShowWhen(true) .setOngoing(true) .setColor(ContextCompat.getColor(context, R.color.primary_color)) + .setFullScreenIntent(pendingIntent, true) + .addAction(notificationsManager.getCallDeclineAction(notifiable)) + .addAction(notificationsManager.getCallAnswerAction(notifiable)) .setCustomHeadsUpContentView(notificationLayoutHeadsUp) + + if (!corePreferences.preventInterfaceFromShowingUp) { + builder.setContentIntent(pendingIntent) + } + + return builder.build() } @SuppressLint("MissingPermission") diff --git a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt index a1c454e70..c5ab3b1e0 100644 --- a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt @@ -20,7 +20,20 @@ package org.linphone.compatibility import android.annotation.TargetApi +import android.app.Notification import android.app.PendingIntent +import android.app.Person +import android.content.Context +import androidx.core.content.ContextCompat +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences +import org.linphone.R +import org.linphone.contact.Contact +import org.linphone.core.Call +import org.linphone.notifications.Notifiable +import org.linphone.notifications.NotificationsManager +import org.linphone.utils.ImageUtils +import org.linphone.utils.LinphoneUtils @TargetApi(31) class Api31Compatibility { @@ -28,5 +41,47 @@ class Api31Compatibility { fun getUpdateCurrentPendingIntentFlag(): Int { return PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE } + + fun createIncomingCallNotification( + context: Context, + call: Call, + notifiable: Notifiable, + pendingIntent: PendingIntent, + notificationsManager: NotificationsManager + ): Notification { + val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress) + val pictureUri = contact?.getContactThumbnailPictureUri() + val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) + val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) + + val person = notificationsManager.getPerson(contact, displayName, roundPicture) + val caller = Person.Builder() + .setName(person.name) + .setIcon(person.icon?.toIcon(context)) + .setUri(person.uri) + .setKey(person.key) + .setImportant(person.isImportant) + .build() + val declineIntent = notificationsManager.getCallDeclinePendingIntent(notifiable) + val answerIntent = notificationsManager.getCallAnswerPendingIntent(notifiable) + val builder = Notification.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) + .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent)) + .setSmallIcon(R.drawable.topbar_call_notification) + .setCategory(Notification.CATEGORY_CALL) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setPriority(Notification.PRIORITY_HIGH) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(false) + .setShowWhen(true) + .setOngoing(true) + .setColor(ContextCompat.getColor(context, R.color.primary_color)) + .setFullScreenIntent(pendingIntent, true) + + if (!corePreferences.preventInterfaceFromShowingUp) { + builder.setContentIntent(pendingIntent) + } + + return builder.build() + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index d149e0d0b..db62fba0b 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -20,6 +20,8 @@ package org.linphone.compatibility import android.app.Activity +import android.app.Notification +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -30,7 +32,6 @@ import android.os.Vibrator import android.telephony.TelephonyManager import android.view.View import android.view.WindowManager -import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.fragment.app.Fragment import java.util.* @@ -38,6 +39,7 @@ import org.linphone.core.Call import org.linphone.core.ChatRoom import org.linphone.core.Content import org.linphone.mediastream.Version +import org.linphone.notifications.Notifiable import org.linphone.notifications.NotificationsManager import org.linphone.telecom.NativeCallWrapper @@ -170,15 +172,19 @@ class Compatibility { return WindowManager.LayoutParams.TYPE_PHONE } - fun createIncomingCallNotificationBuilder( + fun createIncomingCallNotification( context: Context, call: Call, + notifiable: Notifiable, + pendingIntent: PendingIntent, notificationsManager: NotificationsManager - ): NotificationCompat.Builder { - if (Build.MANUFACTURER.lowercase(Locale.getDefault()) == "xiaomi") { - return XiaomiCompatibility.createIncomingCallNotificationBuilder(context, call, notificationsManager) + ): Notification { + if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) { + return Api31Compatibility.createIncomingCallNotification(context, call, notifiable, pendingIntent, notificationsManager) + } else if (Build.MANUFACTURER.lowercase(Locale.getDefault()) == "xiaomi") { + return XiaomiCompatibility.createIncomingCallNotification(context, call, notifiable, pendingIntent, notificationsManager) } - return Api26Compatibility.createIncomingCallNotificationBuilder(context, call, notificationsManager) + return Api26Compatibility.createIncomingCallNotification(context, call, notifiable, pendingIntent, notificationsManager) } /* Call */ diff --git a/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt b/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt index baced6f4f..05b6a482c 100644 --- a/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt @@ -25,10 +25,12 @@ import android.content.Context import android.graphics.BitmapFactory import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat -import org.linphone.LinphoneApplication +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.contact.Contact import org.linphone.core.Call +import org.linphone.notifications.Notifiable import org.linphone.notifications.NotificationsManager import org.linphone.utils.ImageUtils import org.linphone.utils.LinphoneUtils @@ -36,18 +38,20 @@ import org.linphone.utils.LinphoneUtils @TargetApi(26) class XiaomiCompatibility { companion object { - fun createIncomingCallNotificationBuilder( + fun createIncomingCallNotification( context: Context, call: Call, + notifiable: Notifiable, + pendingIntent: PendingIntent, notificationsManager: NotificationsManager - ): NotificationCompat.Builder { - val contact: Contact? = LinphoneApplication.coreContext.contactsManager.findContactByAddress(call.remoteAddress) + ): Notification { + val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress) val pictureUri = contact?.getContactThumbnailPictureUri() val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) val address = LinphoneUtils.getDisplayableAddress(call.remoteAddress) - return NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) + val builder = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id)) .addPerson(notificationsManager.getPerson(contact, displayName, roundPicture)) .setSmallIcon(R.drawable.topbar_call_notification) .setLargeIcon(roundPicture ?: BitmapFactory.decodeResource(context.resources, R.drawable.avatar)) @@ -62,6 +66,15 @@ class XiaomiCompatibility { .setShowWhen(true) .setOngoing(true) .setColor(ContextCompat.getColor(context, R.color.primary_color)) + .setFullScreenIntent(pendingIntent, true) + .addAction(notificationsManager.getCallDeclineAction(notifiable)) + .addAction(notificationsManager.getCallAnswerAction(notifiable)) + + if (!corePreferences.preventInterfaceFromShowingUp) { + builder.setContentIntent(pendingIntent) + } + + return builder.build() } } } diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index c6ca1f0ec..94bca1ff6 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -55,7 +55,7 @@ import org.linphone.utils.FileUtils import org.linphone.utils.ImageUtils import org.linphone.utils.LinphoneUtils -private class Notifiable(val notificationId: Int) { +class Notifiable(val notificationId: Int) { val messages: ArrayList = arrayListOf() var isGroup: Boolean = false @@ -66,7 +66,7 @@ private class Notifiable(val notificationId: Int) { var dismissNotificationUponReadChatRoom: Boolean = true } -private class NotifiableMessage( +class NotifiableMessage( var message: String, val contact: Contact?, val sender: String, @@ -433,17 +433,7 @@ class NotificationsManager(private val context: Context) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - val builder = Compatibility.createIncomingCallNotificationBuilder(context, call, this) - builder - .setFullScreenIntent(pendingIntent, true) - .addAction(getCallDeclineAction(notifiable)) - .addAction(getCallAnswerAction(notifiable)) - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - val notification = builder.build() + val notification = Compatibility.createIncomingCallNotification(context, call, notifiable, pendingIntent, this) Log.i("[Notifications Manager] Notifying incoming call notification [${notifiable.notificationId}]") notify(notifiable.notificationId, notification) @@ -851,43 +841,47 @@ class NotificationsManager(private val context: Context) { /* Notifications actions */ - private fun getCallAnswerAction(notifiable: Notifiable): NotificationCompat.Action { + fun getCallAnswerPendingIntent(notifiable: Notifiable): PendingIntent { val answerIntent = Intent(context, NotificationBroadcastReceiver::class.java) answerIntent.action = INTENT_ANSWER_CALL_NOTIF_ACTION answerIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) answerIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress) - val answerPendingIntent = PendingIntent.getBroadcast( + return PendingIntent.getBroadcast( context, notifiable.notificationId, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) + } + fun getCallAnswerAction(notifiable: Notifiable): NotificationCompat.Action { return NotificationCompat.Action.Builder( R.drawable.call_audio_start, context.getString(R.string.incoming_call_notification_answer_action_label), - answerPendingIntent + getCallAnswerPendingIntent(notifiable) ).build() } - private fun getCallDeclineAction(notifiable: Notifiable): NotificationCompat.Action { + fun getCallDeclinePendingIntent(notifiable: Notifiable): PendingIntent { val hangupIntent = Intent(context, NotificationBroadcastReceiver::class.java) hangupIntent.action = INTENT_HANGUP_CALL_NOTIF_ACTION hangupIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) hangupIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress) - val hangupPendingIntent = PendingIntent.getBroadcast( + return PendingIntent.getBroadcast( context, notifiable.notificationId, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) + } + fun getCallDeclineAction(notifiable: Notifiable): NotificationCompat.Action { return NotificationCompat.Action.Builder( R.drawable.call_hangup, context.getString(R.string.incoming_call_notification_hangup_action_label), - hangupPendingIntent + getCallDeclinePendingIntent(notifiable) ).build() } From 57b0255cc484f1a238b01d9aded2e0b4ed158f76 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Sat, 11 Dec 2021 16:14:12 +0100 Subject: [PATCH 063/130] Use Android 12 CallStyle notification for ongoing calls --- CHANGELOG.md | 2 + .../compatibility/Api26Compatibility.kt | 63 ++++++++++++++++ .../compatibility/Api31Compatibility.kt | 58 ++++++++++++++- .../linphone/compatibility/Compatibility.kt | 14 ++++ .../notifications/NotificationsManager.kt | 71 ++++--------------- 5 files changed, 149 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1081df1d9..30f338611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Group changes to describe their impact on the project, as follows: - New video call UI on foldable device like Galaxy Z Fold - Setting to automatically record all calls - When using a physical keyboard, use left control + enter keys to send message +- Using CallStyle notifications for calls for devices running Android 12 or newer ### Changed - UI has been reworked around SlidingPane component to better handle tablets & foldable devices @@ -38,6 +39,7 @@ Group changes to describe their impact on the project, as follows: - "Infinite backstack", now each view is stored (at most) once in the backstack - Going back to the dialer when pressing back in a chat room after clicking on a chat message notification - Missing international prefix / phone number in assistant after granting permission +- Display issue for incoming call notification preventing to use answer/hangup actions on some Xiaomi devices (like Redmi Note 9S) ### Removed - Launcher Activity has been replaced by [Splash Screen API](https://developer.android.com/reference/kotlin/androidx/core/splashscreen/SplashScreen) diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt index 2f6da50f4..9a541f59e 100644 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt @@ -184,6 +184,69 @@ class Api26Compatibility { return builder.build() } + fun createCallNotification( + context: Context, + call: Call, + notifiable: Notifiable, + pendingIntent: PendingIntent, + channel: String, + notificationsManager: NotificationsManager + ): Notification { + val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress) + val pictureUri = contact?.getContactThumbnailPictureUri() + val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) + val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) + + val stringResourceId: Int + val iconResourceId: Int + when (call.state) { + Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> { + stringResourceId = R.string.call_notification_paused + iconResourceId = R.drawable.topbar_call_paused_notification + } + Call.State.OutgoingRinging, Call.State.OutgoingProgress, Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia -> { + stringResourceId = R.string.call_notification_outgoing + iconResourceId = if (call.params.videoEnabled()) { + R.drawable.topbar_videocall_notification + } else { + R.drawable.topbar_call_notification + } + } + else -> { + stringResourceId = R.string.call_notification_active + iconResourceId = if (call.currentParams.videoEnabled()) { + R.drawable.topbar_videocall_notification + } else { + R.drawable.topbar_call_notification + } + } + } + + val builder = NotificationCompat.Builder( + context, channel + ) + .setContentTitle(contact?.fullName ?: displayName) + .setContentText(context.getString(stringResourceId)) + .setSmallIcon(iconResourceId) + .setLargeIcon(roundPicture) + .addPerson(notificationsManager.getPerson(contact, displayName, roundPicture)) + .setAutoCancel(false) + .setCategory(NotificationCompat.CATEGORY_CALL) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setWhen(System.currentTimeMillis()) + .setShowWhen(true) + .setOngoing(true) + .setColor(ContextCompat.getColor(context, R.color.notification_led_color)) + .addAction(notificationsManager.getCallDeclineAction(notifiable)) + + if (!corePreferences.preventInterfaceFromShowingUp) { + builder.setContentIntent(pendingIntent) + } + + return builder.build() + } + @SuppressLint("MissingPermission") fun eventVibration(vibrator: Vibrator) { val effect = VibrationEffect.createWaveform(longArrayOf(0L, 100L, 100L), intArrayOf(0, VibrationEffect.DEFAULT_AMPLITUDE, 0), -1) diff --git a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt index c5ab3b1e0..7ca6dde46 100644 --- a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt @@ -69,7 +69,6 @@ class Api31Compatibility { .setSmallIcon(R.drawable.topbar_call_notification) .setCategory(Notification.CATEGORY_CALL) .setVisibility(Notification.VISIBILITY_PUBLIC) - .setPriority(Notification.PRIORITY_HIGH) .setWhen(System.currentTimeMillis()) .setAutoCancel(false) .setShowWhen(true) @@ -83,5 +82,62 @@ class Api31Compatibility { return builder.build() } + + fun createCallNotification( + context: Context, + call: Call, + notifiable: Notifiable, + pendingIntent: PendingIntent, + channel: String, + notificationsManager: NotificationsManager + ): Notification { + val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress) + val pictureUri = contact?.getContactThumbnailPictureUri() + val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) + val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) + + val isVideo = call.currentParams.videoEnabled() + val iconResourceId: Int = when (call.state) { + Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> { + R.drawable.topbar_call_paused_notification + } + else -> { + if (isVideo) { + R.drawable.topbar_videocall_notification + } else { + R.drawable.topbar_call_notification + } + } + } + + val person = notificationsManager.getPerson(contact, displayName, roundPicture) + val caller = Person.Builder() + .setName(person.name) + .setIcon(person.icon?.toIcon(context)) + .setUri(person.uri) + .setKey(person.key) + .setImportant(person.isImportant) + .build() + val declineIntent = notificationsManager.getCallDeclinePendingIntent(notifiable) + val builder = Notification.Builder( + context, channel + ) + .setStyle(Notification.CallStyle.forOngoingCall(caller, declineIntent).setIsVideo(isVideo)) + .setSmallIcon(iconResourceId) + .setAutoCancel(false) + .setCategory(Notification.CATEGORY_CALL) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setWhen(System.currentTimeMillis()) + .setShowWhen(true) + .setOngoing(true) + .setColor(ContextCompat.getColor(context, R.color.notification_led_color)) + .setFullScreenIntent(pendingIntent, true) // This is required for CallStyle notification + + if (!corePreferences.preventInterfaceFromShowingUp) { + builder.setContentIntent(pendingIntent) + } + + return builder.build() + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index db62fba0b..4a6a2e3e4 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -187,6 +187,20 @@ class Compatibility { return Api26Compatibility.createIncomingCallNotification(context, call, notifiable, pendingIntent, notificationsManager) } + fun createCallNotification( + context: Context, + call: Call, + notifiable: Notifiable, + pendingIntent: PendingIntent, + channel: String, + notificationsManager: NotificationsManager + ): Notification { + if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) { + return Api31Compatibility.createCallNotification(context, call, notifiable, pendingIntent, channel, notificationsManager) + } + return Api26Compatibility.createCallNotification(context, call, notifiable, pendingIntent, channel, notificationsManager) + } + /* Call */ fun canDrawOverlay(context: Context): Boolean { diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 94bca1ff6..0d33add8a 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -482,7 +482,6 @@ class NotificationsManager(private val context: Context) { } val notification = builder.build() - notify(MISSED_CALLS_NOTIF_ID, notification, MISSED_CALL_TAG) } @@ -493,6 +492,18 @@ class NotificationsManager(private val context: Context) { fun displayCallNotification(call: Call, useAsForeground: Boolean = false) { val notifiable = getNotifiableForCall(call) + val callActivity: Class<*> = when (call.state) { + Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> { + CallActivity::class.java + } + Call.State.OutgoingRinging, Call.State.OutgoingProgress, Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia -> { + OutgoingCallActivity::class.java + } + else -> { + CallActivity::class.java + } + } + val serviceChannel = context.getString(R.string.notification_channel_service_id) val channelToUse = when (val serviceChannelImportance = Compatibility.getChannelImportance(notificationManager, serviceChannel)) { NotificationManagerCompat.IMPORTANCE_NONE -> { @@ -510,40 +521,6 @@ class NotificationsManager(private val context: Context) { } } - val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress) - val pictureUri = contact?.getContactThumbnailPictureUri() - val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) - val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) - - val stringResourceId: Int - val iconResourceId: Int - val callActivity: Class<*> - when (call.state) { - Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> { - callActivity = CallActivity::class.java - stringResourceId = R.string.call_notification_paused - iconResourceId = R.drawable.topbar_call_paused_notification - } - Call.State.OutgoingRinging, Call.State.OutgoingProgress, Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia -> { - callActivity = OutgoingCallActivity::class.java - stringResourceId = R.string.call_notification_outgoing - iconResourceId = if (call.params.videoEnabled()) { - R.drawable.topbar_videocall_notification - } else { - R.drawable.topbar_call_notification - } - } - else -> { - callActivity = CallActivity::class.java - stringResourceId = R.string.call_notification_active - iconResourceId = if (call.currentParams.videoEnabled()) { - R.drawable.topbar_videocall_notification - } else { - R.drawable.topbar_call_notification - } - } - } - val callNotificationIntent = Intent(context, callActivity) callNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val pendingIntent = PendingIntent.getActivity( @@ -553,29 +530,7 @@ class NotificationsManager(private val context: Context) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - val builder = NotificationCompat.Builder( - context, channelToUse - ) - .setContentTitle(contact?.fullName ?: displayName) - .setContentText(context.getString(stringResourceId)) - .setSmallIcon(iconResourceId) - .setLargeIcon(roundPicture) - .addPerson(getPerson(contact, displayName, roundPicture)) - .setAutoCancel(false) - .setCategory(NotificationCompat.CATEGORY_CALL) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setWhen(System.currentTimeMillis()) - .setShowWhen(true) - .setOngoing(true) - .setColor(ContextCompat.getColor(context, R.color.notification_led_color)) - .addAction(getCallDeclineAction(notifiable)) - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - val notification = builder.build() + val notification = Compatibility.createCallNotification(context, call, notifiable, pendingIntent, channelToUse, this) Log.i("[Notifications Manager] Notifying call notification [${notifiable.notificationId}]") notify(notifiable.notificationId, notification) From d6b853ed68f45c23c0c53f7437f48848d2f169ae Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 10 Dec 2021 15:51:27 +0100 Subject: [PATCH 064/130] Trying to workaround chat room already displayed log when pane is closed issue --- .../main/chat/fragments/MasterChatRoomsFragment.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt index 54968d878..a422447ab 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt @@ -249,7 +249,14 @@ class MasterChatRoomsFragment : MasterFragment Date: Wed, 15 Dec 2021 11:22:31 +0100 Subject: [PATCH 065/130] Fixed issue in content --- .../linphone/activities/main/chat/data/ChatMessageContentData.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt index e1a34dde9..62e0f3636 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt @@ -76,7 +76,6 @@ class ChatMessageContentData( get() { var count = 0 for (content in chatMessage.contents) { - val content = getContent() if (content.isFileTransfer || content.isFile) { count += 1 } From 7b7c80373693b8ff7f44abcea3e7103586e302c8 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 15 Dec 2021 15:17:18 +0100 Subject: [PATCH 066/130] Bumped version code --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4b23da8fd..180b843d0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ plugins { def appVersionName = "4.6.0" // Uncomment for release // def appVersionCode = 40600 // 4.06.00 -def appVersionCode = 40590 // 4.05.90 +def appVersionCode = 40591 // 4.05.91 static def getPackageName() { return "org.linphone" From d9c38005961106b84ffed1e4e5412cdce345a5be Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 15 Dec 2021 15:45:10 +0100 Subject: [PATCH 067/130] Fixed STUN setting not working on non-sip.linphone.org accounts --- .../settings/viewmodels/AccountSettingsViewModel.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt index 6841ff5a3..f2e3a13a5 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt @@ -297,8 +297,15 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( val stunServerListener = object : SettingListenerStub() { override fun onTextValueChanged(newValue: String) { val params = account.params.clone() - params.natPolicy?.stunServer = newValue - if (newValue.isEmpty()) ice.value = false + if (params.natPolicy == null) { + Log.w("[Account Settings] No NAT Policy object in account params yet") + val natPolicy = core.createNatPolicy() + natPolicy.stunServer = newValue + params.natPolicy = natPolicy + } else { + params.natPolicy?.stunServer = newValue + if (newValue.isEmpty()) ice.value = false + } stunServer.value = newValue account.params = params } From 4ed589d12bfa869491fd05f7e70d1155d0af4469 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 16 Dec 2021 17:06:04 +0100 Subject: [PATCH 068/130] Updated dependencies --- app/build.gradle | 8 ++++---- .../java/org/linphone/activities/GenericActivity.kt | 11 +++++++---- build.gradle | 6 +++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 180b843d0..e571fc982 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -220,14 +220,14 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.4.0' implementation 'androidx.core:core-ktx:1.7.0' - def nav_version = "2.4.0-beta02" + def nav_version = "2.4.0-rc01" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" - implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0-beta01" - implementation "androidx.window:window:1.0.0-beta03" + implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0-rc01" + implementation "androidx.window:window:1.0.0-rc01" - implementation 'androidx.constraintlayout:constraintlayout:2.1.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' diff --git a/app/src/main/java/org/linphone/activities/GenericActivity.kt b/app/src/main/java/org/linphone/activities/GenericActivity.kt index 0eec20c2b..221dfaa2b 100644 --- a/app/src/main/java/org/linphone/activities/GenericActivity.kt +++ b/app/src/main/java/org/linphone/activities/GenericActivity.kt @@ -31,7 +31,7 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.lifecycle.lifecycleScope import androidx.navigation.ActivityNavigator import androidx.window.layout.FoldingFeature -import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository +import androidx.window.layout.WindowInfoTracker import androidx.window.layout.WindowLayoutInfo import java.util.* import kotlinx.coroutines.Dispatchers @@ -58,9 +58,12 @@ abstract class GenericActivity : AppCompatActivity() { ensureCoreExists(applicationContext) lifecycleScope.launch(Dispatchers.Main) { - windowInfoRepository().windowLayoutInfo.collect { newLayoutInfo -> - updateCurrentLayout(newLayoutInfo) - } + WindowInfoTracker + .getOrCreate(this@GenericActivity) + .windowLayoutInfo(this@GenericActivity) + .collect { newLayoutInfo -> + updateCurrentLayout(newLayoutInfo) + } } requestedOrientation = if (corePreferences.forcePortrait) { diff --git a/build.gradle b/build.gradle index 2e256b988..b06cb08b4 100644 --- a/build.gradle +++ b/build.gradle @@ -11,11 +11,11 @@ buildscript { } // for com.github.chrisbanes:PhotoView } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.0.4' classpath 'com.google.gms:google-services:4.3.10' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0" - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.0' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1' } } From fdaabf6fc0e04e43a50265a79febbf293f777681 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 17 Dec 2021 09:04:58 +0100 Subject: [PATCH 069/130] Fixed build due to API changes in 5.1 SDK --- .../viewmodels/AccountSettingsViewModel.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt index f2e3a13a5..28dc867a7 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt @@ -192,7 +192,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( val disableListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { val params = account.params.clone() - params.registerEnabled = !newValue + params.isRegisterEnabled = !newValue account.params = params } } @@ -239,7 +239,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( } val params = account.params.clone() - params.registerEnabled = false + params.isRegisterEnabled = false account.params = params if (!registered) { @@ -288,7 +288,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( val outboundProxyListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { val params = account.params.clone() - params.outboundProxyEnabled = newValue + params.isOutboundProxyEnabled = newValue account.params = params } } @@ -377,7 +377,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( val escapePlusListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { val params = account.params.clone() - params.dialEscapePlusEnabled = newValue + params.isDialEscapePlusEnabled = newValue account.params = params } } @@ -431,11 +431,11 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( userName.value = params.identityAddress?.username userId.value = account.findAuthInfo()?.userid domain.value = params.identityAddress?.domain - disable.value = !params.registerEnabled + disable.value = !params.isRegisterEnabled pushNotification.value = params.pushNotificationAllowed pushNotificationsAvailable.value = core.isPushNotificationAvailable proxy.value = params.serverAddress?.asStringUriOnly() - outboundProxy.value = params.outboundProxyEnabled + outboundProxy.value = params.isOutboundProxyEnabled stunServer.value = params.natPolicy?.stunServer ice.value = params.natPolicy?.iceEnabled() avpf.value = params.avpfMode == AVPFMode.Enabled @@ -443,7 +443,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( expires.value = params.expires prefix.value = params.internationalPrefix dialPrefix.value = params.useInternationalPrefixForCallsAndChats - escapePlus.value = params.dialEscapePlusEnabled + escapePlus.value = params.isDialEscapePlusEnabled } private fun initTransportList() { From d7f2b629da22f0cc71826fae9ce18b6a3bfc3976 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 20 Dec 2021 11:39:56 +0100 Subject: [PATCH 070/130] Make sure TelecomHelper is correctly created in CoreContext --- app/src/main/java/org/linphone/core/CoreContext.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 3274f7ee7..1d5430b1e 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -295,7 +295,8 @@ class CoreContext(val context: Context, coreConfig: Config) { if (Compatibility.hasTelecomManagerPermissions(context)) { Log.i("[Context] Creating Telecom Helper, disabling audio focus requests in AudioHelper") core.config.setBool("audio", "android_disable_audio_focus_requests", true) - TelecomHelper.create(context) + val telecomHelper = TelecomHelper.required(context) + Log.i("[Context] Telecom Helper created, account is ${if (telecomHelper.isAccountEnabled()) "enabled" else "disabled"}") } else { Log.w("[Context] Can't create Telecom Helper, permissions have been revoked") corePreferences.useTelecomManager = false From 7bd85a6f53043631da09673d1c9a054819e92bb3 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 20 Dec 2021 13:01:38 +0100 Subject: [PATCH 071/130] Fixed contacts names not always displayed in chat rooms list --- .../activities/main/chat/adapters/ChatRoomsListAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt index d99eacb7d..d4100c53f 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt @@ -116,6 +116,6 @@ private class ChatRoomDiffCallback : DiffUtil.ItemCallback() oldItem: ChatRoomViewModel, newItem: ChatRoomViewModel ): Boolean { - return newItem.unreadMessagesCount.value == 0 + return false // To force redraw when contacts are updated } } From 70575d73ba72e17f0fc567a537b0c756cb8f9617 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 20 Dec 2021 14:39:05 +0100 Subject: [PATCH 072/130] Removed unused code + attempt to workaround ANR since last dependencies update --- .../org/linphone/activities/GenericFragment.kt | 10 +++++++++- .../chat/fragments/MasterChatRoomsFragment.kt | 12 ------------ .../contact/fragments/MasterContactsFragment.kt | 15 --------------- .../history/fragments/MasterCallLogsFragment.kt | 15 --------------- 4 files changed, 9 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/GenericFragment.kt b/app/src/main/java/org/linphone/activities/GenericFragment.kt index 94bbfaffa..0ee63ec55 100644 --- a/app/src/main/java/org/linphone/activities/GenericFragment.kt +++ b/app/src/main/java/org/linphone/activities/GenericFragment.kt @@ -28,8 +28,12 @@ import androidx.core.view.doOnPreDraw import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.material.transition.MaterialSharedAxis +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.core.tools.Log @@ -40,7 +44,11 @@ abstract class GenericFragment : Fragment() { protected val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - goBack() + lifecycleScope.launch { + withContext(Dispatchers.Main) { + goBack() + } + } } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt index a422447ab..3f3f88025 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt @@ -145,18 +145,6 @@ class MasterChatRoomsFragment : MasterFragment Date: Mon, 20 Dec 2021 14:52:39 +0100 Subject: [PATCH 073/130] Fixed issue with TelecomHelper singleton deletion --- app/src/main/java/org/linphone/core/CoreContext.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 1d5430b1e..7d6d94093 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -330,6 +330,7 @@ class CoreContext(val context: Context, coreConfig: Config) { if (TelecomHelper.exists()) { Log.i("[Context] Destroying telecom helper") TelecomHelper.get().destroy() + TelecomHelper.destroy() } core.stop() From 43e6e146547f19655525f74da7a3d7b01677f9cf Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 20 Dec 2021 16:53:32 +0100 Subject: [PATCH 074/130] Added ringtone picker (hidden by default for now) --- .../viewmodels/CallSettingsViewModel.kt | 45 ++++++++++++++++++- .../java/org/linphone/core/CorePreferences.kt | 12 ++++- .../res/layout/settings_call_fragment.xml | 9 ++++ app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 65 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt index 0488a51f3..f95ab222e 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt @@ -20,22 +20,40 @@ package org.linphone.activities.main.settings.viewmodels import androidx.lifecycle.MutableLiveData +import java.io.File +import java.util.* +import kotlin.collections.ArrayList import org.linphone.R import org.linphone.activities.main.settings.SettingListenerStub import org.linphone.core.MediaEncryption import org.linphone.core.tools.Log import org.linphone.mediastream.Version import org.linphone.telecom.TelecomHelper +import org.linphone.utils.AppUtils import org.linphone.utils.Event class CallSettingsViewModel : GenericSettingsViewModel() { val deviceRingtoneListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { - core.ring = if (newValue) null else prefs.ringtonePath + core.ring = if (newValue) null else prefs.defaultRingtonePath } } val deviceRingtone = MutableLiveData() + val ringtoneListener = object : SettingListenerStub() { + override fun onListValueChanged(position: Int) { + if (position == 0) { + core.ring = null + } else { + core.ring = ringtoneValues[position] + } + } + } + val ringtoneIndex = MutableLiveData() + val ringtoneLabels = MutableLiveData>() + private val ringtoneValues = arrayListOf() + val showRingtonesList = MutableLiveData() + val vibrateOnIncomingCallListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { core.isVibrationOnIncomingCallEnabled = newValue @@ -212,7 +230,10 @@ class CallSettingsViewModel : GenericSettingsViewModel() { val goToAndroidNotificationSettingsEvent = MutableLiveData>() init { + initRingtonesList() deviceRingtone.value = core.ring == null + showRingtonesList.value = prefs.showAllRingtones + vibrateOnIncomingCall.value = core.isVibrationOnIncomingCallEnabled initEncryptionList() @@ -238,6 +259,28 @@ class CallSettingsViewModel : GenericSettingsViewModel() { pauseCallsWhenAudioFocusIsLost.value = prefs.pauseCallsWhenAudioFocusIsLost } + private fun initRingtonesList() { + val labels = arrayListOf() + labels.add(AppUtils.getString(R.string.call_settings_device_ringtone_title)) + ringtoneValues.add("") + + val directory = File(prefs.ringtonesPath) + val files = directory.listFiles() + for (ringtone in files.orEmpty()) { + if (ringtone.absolutePath.endsWith(".mkv")) { + val name = ringtone.name + .substringBefore(".") + .replace("_", " ") + .capitalize(Locale.getDefault()) + labels.add(name) + ringtoneValues.add(ringtone.absolutePath) + } + } + + ringtoneLabels.value = labels + ringtoneIndex.value = if (core.ring == null) 0 else ringtoneValues.indexOf(core.ring) + } + private fun initEncryptionList() { val labels = arrayListOf() diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 572ccf296..bc83c4e10 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -460,6 +460,11 @@ class CorePreferences constructor(private val context: Context) { val useEphemeralPerDeviceMode: Boolean get() = config.getBool("app", "ephemeral_chat_messages_settings_per_device", true) + // If enabled user will see all ringtones bundled in our SDK + // and will be able to choose which one to use if not using it's device's default + val showAllRingtones: Boolean + get() = config.getBool("app", "show_all_available_ringtones", false) + /* Default values related */ val echoCancellerCalibration: Int @@ -578,8 +583,11 @@ class CorePreferences constructor(private val context: Context) { val defaultValuesPath: String get() = context.filesDir.absolutePath + "/assistant_default_values" - val ringtonePath: String - get() = context.filesDir.absolutePath + "/share/sounds/linphone/rings/notes_of_the_optimistic.mkv" + val ringtonesPath: String + get() = context.filesDir.absolutePath + "/share/sounds/linphone/rings/" + + val defaultRingtonePath: String + get() = ringtonesPath + "notes_of_the_optimistic.mkv" val userCertificatesPath: String get() = context.filesDir.absolutePath + "/user-certs" diff --git a/app/src/main/res/layout/settings_call_fragment.xml b/app/src/main/res/layout/settings_call_fragment.xml index b8b8645c1..97c901951 100644 --- a/app/src/main/res/layout/settings_call_fragment.xml +++ b/app/src/main/res/layout/settings_call_fragment.xml @@ -72,9 +72,18 @@ + + Aller au dernier message reçu ou au premier message non lu Acheminer l\'audio vers l\'appareil bluetooth, s\'il existe Il aura la priorité sur le périphérique de sortie par défaut + Sonnerie \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6ed825ea7..74c64b572 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -402,6 +402,7 @@ Use device ringtone + Ringtone Vibrate while incoming call is ringing Media encryption None From c63a8cf2fdc75c3cb1b8086c3fd753aa298eda62 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 20 Dec 2021 17:22:37 +0100 Subject: [PATCH 075/130] Reworked CoreContext order at startup --- app/src/main/java/org/linphone/core/CoreContext.kt | 10 ++++++---- .../org/linphone/notifications/NotificationsManager.kt | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 7d6d94093..2f96b6dc4 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -286,8 +286,6 @@ class CoreContext(val context: Context, coreConfig: Config) { fun start(isPush: Boolean = false) { Log.i("[Context] Starting") - notificationsManager.onCoreReady() - core.addListener(listener) // CoreContext listener must be added first! @@ -308,14 +306,18 @@ class CoreContext(val context: Context, coreConfig: Config) { core.enterBackground() } - core.start() - configureCore() + core.start() + initPhoneStateListener() + notificationsManager.onCoreReady() + EmojiCompat.init(BundledEmojiCompatConfig(context)) collator.strength = Collator.NO_DECOMPOSITION + + Log.i("[Context] Started") } fun stop() { diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 0d33add8a..d82e59015 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -124,7 +124,7 @@ class NotificationsManager(private val context: Context) { return } - when (state) { + when (call.state) { Call.State.IncomingEarlyMedia, Call.State.IncomingReceived -> displayIncomingCallNotification(call) Call.State.End, Call.State.Error -> dismissCallNotification(call) Call.State.Released -> { From a2ac7e9f378e2799a2d304fadf1562015d01d032 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 30 Dec 2021 12:39:28 +0100 Subject: [PATCH 076/130] Migrated maven repositories to settings.gradle file, update .gitlab-ci file to allow override --- .gitlab-ci-files/job-android.yml | 1 + app/build.gradle | 18 ------------------ build.gradle | 10 ---------- settings.gradle | 24 ++++++++++++++++++++++++ 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/.gitlab-ci-files/job-android.yml b/.gitlab-ci-files/job-android.yml index 1127eff87..98fae62c7 100644 --- a/.gitlab-ci-files/job-android.yml +++ b/.gitlab-ci-files/job-android.yml @@ -7,6 +7,7 @@ job-android: before_script: - if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then eval $(ssh-agent -s); fi - if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then echo "$SCP_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null; fi + - echo "$ANDROID_SETTINGS_GRADLE" > settings.gradle script: - sdkmanager diff --git a/app/build.gradle b/app/build.gradle index e571fc982..876de539b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,24 +195,6 @@ android { } } -repositories { - maven { - name "local linphone-sdk maven repository" - url file(LinphoneSdkBuildDir + '/maven_repository/') - content { - includeGroup "org.linphone" - } - } - - maven { - name "linphone.org maven repository" - url "https://linphone.org/maven_repository" - content { - includeGroup "org.linphone" - } - } -} - dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.4.0' diff --git a/build.gradle b/build.gradle index b06cb08b4..14d212062 100644 --- a/build.gradle +++ b/build.gradle @@ -19,16 +19,6 @@ buildscript { } } -allprojects { - repositories { - mavenCentral() - google() - maven { - url "https://www.jitpack.io" - } // for com.github.chrisbanes:PhotoView - } -} - task clean(type: Delete) { delete rootProject.buildDir } diff --git a/settings.gradle b/settings.gradle index 108f46e58..630dba8e3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,26 @@ +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + google() + maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://www.jitpack.io" } // for com.github.chrisbanes:PhotoView + + maven { + name "local linphone-sdk maven repository" + url file(LinphoneSdkBuildDir + '/maven_repository/') + content { + includeGroup "org.linphone" + } + } + + maven { + name "linphone.org maven repository" + url "https://linphone.org/maven_repository" + content { + includeGroup "org.linphone" + } + } + } +} include ':app' rootProject.name='Linphone' From 0404777c32b9c7ac8da1abfc48dfd2c4fc2d60e2 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 3 Jan 2022 17:00:49 +0100 Subject: [PATCH 077/130] Reworked & improved export when usig VFS feature --- .../main/files/fragments/TopBarFragment.kt | 98 ++++++++++++++++--- .../compatibility/Api21Compatibility.kt | 24 ++++- .../compatibility/Api29Compatibility.kt | 27 ++++- .../main/java/org/linphone/utils/FileUtils.kt | 22 +++++ 4 files changed, 151 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt index 0699df0a1..dd10adf92 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt @@ -21,13 +21,21 @@ package org.linphone.activities.main.files.fragments import android.os.Bundle import android.view.View +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.linphone.R import org.linphone.activities.GenericFragment import org.linphone.activities.SnackBarActivity +import org.linphone.compatibility.Compatibility import org.linphone.core.Content import org.linphone.core.tools.Log import org.linphone.databinding.FileViewerTopBarFragmentBinding +import org.linphone.mediastream.Version import org.linphone.utils.FileUtils +import org.linphone.utils.PermissionHelper class TopBarFragment : GenericFragment() { private var content: Content? = null @@ -46,20 +54,9 @@ class TopBarFragment : GenericFragment() { } binding.setExportClickListener { - if (content != null) { - val filePath = content?.plainFilePath.orEmpty() - plainFilePath = if (filePath.isEmpty()) content?.filePath.orEmpty() else filePath - Log.i("[File Viewer] Plain file path is: $plainFilePath") - if (plainFilePath.isNotEmpty()) { - if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) { - (requireActivity() as SnackBarActivity).showSnackBar(R.string.chat_message_no_app_found_to_handle_file_mime_type) - if (plainFilePath != content?.filePath.orEmpty()) { - Log.i("[File Viewer] No app to open plain file path: $plainFilePath, destroying it") - FileUtils.deleteFile(plainFilePath) - } - plainFilePath = "" - } - } + val contentToExport = content + if (contentToExport != null) { + exportContent(contentToExport) } else { Log.e("[File Viewer] No Content set!") } @@ -89,4 +86,77 @@ class TopBarFragment : GenericFragment() { content = c binding.fileName.text = c.name } + + private fun exportContent(content: Content) { + lifecycleScope.launch { + var mediaStoreFilePath = "" + if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStoragePermission()) { + Log.i("[File Viewer] Exporting image through Media Store API") + when (content.type) { + "image" -> { + val export = lifecycleScope.async { + Compatibility.addImageToMediaStore(requireContext(), content) + } + if (export.await()) { + Log.i("[File Viewer] Adding image ${content.name} to Media Store terminated: ${content.userData}") + mediaStoreFilePath = content.userData.toString() + } else { + Log.e("[File Viewer] Something went wrong while copying file to Media Store...") + } + } + "video" -> { + val export = lifecycleScope.async { + Compatibility.addVideoToMediaStore(requireContext(), content) + } + if (export.await()) { + Log.i("[File Viewer] Adding video ${content.name} to Media Store terminated: ${content.userData}") + mediaStoreFilePath = content.userData.toString() + } else { + Log.e("[File Viewer] Something went wrong while copying file to Media Store...") + } + } + "audio" -> { + val export = lifecycleScope.async { + Compatibility.addAudioToMediaStore(requireContext(), content) + } + if (export.await()) { + Log.i("[File Viewer] Adding audio ${content.name} to Media Store terminated: ${content.userData}") + mediaStoreFilePath = content.userData.toString() + } else { + Log.e("[File Viewer] Something went wrong while copying file to Media Store...") + } + } + else -> { + Log.w("[File Viewer] File ${content.name} isn't either an image, an audio file or a video, can't add it to the Media Store") + } + } + } else { + Log.w("[File Viewer] Can't export image through Media Store API (requires Android 10 or WRITE_EXTERNAL permission, using fallback method...") + } + + withContext(Dispatchers.Main) { + if (mediaStoreFilePath.isEmpty()) { + Log.w("[File Viewer] Media store file path is empty, media store export failed?") + + val filePath = content.plainFilePath.orEmpty() + plainFilePath = if (filePath.isEmpty()) content.filePath.orEmpty() else filePath + Log.i("[File Viewer] Plain file path is: $plainFilePath") + if (plainFilePath.isNotEmpty()) { + if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) { + (requireActivity() as SnackBarActivity).showSnackBar(R.string.chat_message_no_app_found_to_handle_file_mime_type) + if (plainFilePath != content.filePath.orEmpty()) { + Log.i("[File Viewer] No app to open plain file path: $plainFilePath, destroying it") + FileUtils.deleteFile(plainFilePath) + } + plainFilePath = "" + } + } + } else { + plainFilePath = "" + Log.i("[File Viewer] Media store file path is: $mediaStoreFilePath") + FileUtils.openMediaStoreFile(requireActivity(), mediaStoreFilePath) + } + } + } + } } diff --git a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt index 807718ba2..278bc66cc 100644 --- a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt @@ -77,7 +77,9 @@ class Api21Compatibility { return false } - val filePath = content.filePath + val plainFilePath = content.plainFilePath.orEmpty() + val isVfsEncrypted = plainFilePath.isNotEmpty() + val filePath = if (isVfsEncrypted) plainFilePath else content.filePath if (filePath == null) { Log.e("[Media Store] Content doesn't have a file path!") return false @@ -95,6 +97,10 @@ class Api21Compatibility { } val collection = MediaStore.Images.Media.getContentUri("external") val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values) + if (isVfsEncrypted) { + Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath") + FileUtils.deleteFile(plainFilePath) + } if (mediaStoreFilePath.isNotEmpty()) { content.userData = mediaStoreFilePath return true @@ -108,7 +114,9 @@ class Api21Compatibility { return false } - val filePath = content.filePath + val plainFilePath = content.plainFilePath.orEmpty() + val isVfsEncrypted = plainFilePath.isNotEmpty() + val filePath = if (isVfsEncrypted) plainFilePath else content.filePath if (filePath == null) { Log.e("[Media Store] Content doesn't have a file path!") return false @@ -127,6 +135,10 @@ class Api21Compatibility { } val collection = MediaStore.Video.Media.getContentUri("external") val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values) + if (isVfsEncrypted) { + Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath") + FileUtils.deleteFile(plainFilePath) + } if (mediaStoreFilePath.isNotEmpty()) { content.userData = mediaStoreFilePath return true @@ -140,7 +152,9 @@ class Api21Compatibility { return false } - val filePath = content.filePath + val plainFilePath = content.plainFilePath.orEmpty() + val isVfsEncrypted = plainFilePath.isNotEmpty() + val filePath = if (isVfsEncrypted) plainFilePath else content.filePath if (filePath == null) { Log.e("[Media Store] Content doesn't have a file path!") return false @@ -160,6 +174,10 @@ class Api21Compatibility { val collection = MediaStore.Audio.Media.getContentUri("external") val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values) + if (isVfsEncrypted) { + Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath") + FileUtils.deleteFile(plainFilePath) + } if (mediaStoreFilePath.isNotEmpty()) { content.userData = mediaStoreFilePath return true diff --git a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt index 1d5c2831c..0e587f6ef 100644 --- a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt @@ -99,7 +99,9 @@ class Api29Compatibility { } suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { - val filePath = content.filePath + val plainFilePath = content.plainFilePath.orEmpty() + val isVfsEncrypted = plainFilePath.isNotEmpty() + val filePath = if (isVfsEncrypted) plainFilePath else content.filePath if (filePath == null) { Log.e("[Media Store] Content doesn't have a file path!") return false @@ -119,6 +121,11 @@ class Api29Compatibility { } val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Images.Media.IS_PENDING) + + if (isVfsEncrypted) { + Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath") + FileUtils.deleteFile(plainFilePath) + } if (mediaStoreFilePath.isNotEmpty()) { content.userData = mediaStoreFilePath return true @@ -127,7 +134,9 @@ class Api29Compatibility { } suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { - val filePath = content.filePath + val plainFilePath = content.plainFilePath.orEmpty() + val isVfsEncrypted = plainFilePath.isNotEmpty() + val filePath = if (isVfsEncrypted) plainFilePath else content.filePath if (filePath == null) { Log.e("[Media Store] Content doesn't have a file path!") return false @@ -148,6 +157,11 @@ class Api29Compatibility { } val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Video.Media.IS_PENDING) + + if (isVfsEncrypted) { + Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath") + FileUtils.deleteFile(plainFilePath) + } if (mediaStoreFilePath.isNotEmpty()) { content.userData = mediaStoreFilePath return true @@ -156,7 +170,9 @@ class Api29Compatibility { } suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { - val filePath = content.filePath + val plainFilePath = content.plainFilePath.orEmpty() + val isVfsEncrypted = plainFilePath.isNotEmpty() + val filePath = if (isVfsEncrypted) plainFilePath else content.filePath if (filePath == null) { Log.e("[Media Store] Content doesn't have a file path!") return false @@ -178,6 +194,11 @@ class Api29Compatibility { val collection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Audio.Media.IS_PENDING) + + if (isVfsEncrypted) { + Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath") + FileUtils.deleteFile(plainFilePath) + } if (mediaStoreFilePath.isNotEmpty()) { content.userData = mediaStoreFilePath return true diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index 0955b39b7..aa3a90f6f 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -470,5 +470,27 @@ class FileUtils { } return false } + + fun openMediaStoreFile( + activity: Activity, + contentFilePath: String, + newTask: Boolean = false + ): Boolean { + val intent = Intent(Intent.ACTION_VIEW) + val contentUri: Uri = Uri.parse(contentFilePath) + intent.data = contentUri + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + if (newTask) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + try { + activity.startActivity(intent) + return true + } catch (anfe: ActivityNotFoundException) { + Log.e("[File Viewer] Can't open media store export in third party app: $anfe") + } + return false + } } } From fc6792687ca491515bdcac5cfb573af250bce4f9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Jan 2022 11:37:52 +0100 Subject: [PATCH 078/130] Fixed issue with message received & dislay in a chat room for which the sliding pane was closed but fragment wasn't paused or destroyed --- .../activities/main/chat/fragments/DetailChatRoomFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index f48bc63b8..088128776 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -255,7 +255,7 @@ class DetailChatRoomFragment : MasterFragment Date: Tue, 4 Jan 2022 11:47:21 +0100 Subject: [PATCH 079/130] Just in case, clean up any existing plain file when core starts if VFS is enabled --- .../java/org/linphone/core/CoreContext.kt | 4 ++++ .../main/java/org/linphone/utils/FileUtils.kt | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 2f96b6dc4..f356e47bb 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -317,6 +317,10 @@ class CoreContext(val context: Context, coreConfig: Config) { EmojiCompat.init(BundledEmojiCompatConfig(context)) collator.strength = Collator.NO_DECOMPOSITION + if (corePreferences.vfsEnabled) { + FileUtils.clearExistingPlainFiles() + } + Log.i("[Context] Started") } diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index aa3a90f6f..30adf0ba1 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -99,6 +99,27 @@ class FileUtils { return type?.startsWith("audio/") ?: false } + fun clearExistingPlainFiles() { + for (file in coreContext.context.filesDir.listFiles().orEmpty()) { + if (file.path.endsWith(VFS_PLAIN_FILE_EXTENSION)) { + Log.w("[File Utils] Found forgotten plain file: ${file.path}, deleting it") + deleteFile(file.path) + } + } + for (file in coreContext.context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.listFiles().orEmpty()) { + if (file.path.endsWith(VFS_PLAIN_FILE_EXTENSION)) { + Log.w("[File Utils] Found forgotten plain file: ${file.path}, deleting it") + deleteFile(file.path) + } + } + for (file in coreContext.context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.listFiles().orEmpty()) { + if (file.path.endsWith(VFS_PLAIN_FILE_EXTENSION)) { + Log.w("[File Utils] Found forgotten plain file: ${file.path}, deleting it") + deleteFile(file.path) + } + } + } + fun getFileStorageDir(isPicture: Boolean = false): File { var path: File? = null if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { From 0e17da27e8fe704e87712eae1873ee260d7d55c7 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 4 Jan 2022 15:33:06 +0100 Subject: [PATCH 080/130] Try to prevent VFS leaks as much as possible, added logs before each plainFilePath call --- .../main/chat/data/ChatMessageContentData.kt | 27 +++++++++++-------- .../chat/fragments/DetailChatRoomFragment.kt | 2 ++ .../viewmodels/ChatMessageSendingViewModel.kt | 1 + .../files/viewmodels/FileViewerViewModel.kt | 7 ++++- .../compatibility/Api21Compatibility.kt | 3 +++ .../compatibility/Api29Compatibility.kt | 3 +++ 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt index 62e0f3636..81c13342f 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt @@ -44,7 +44,6 @@ import org.linphone.utils.ImageUtils class ChatMessageContentData( private val chatMessage: ChatMessage, private val contentIndex: Int, - ) { var listener: OnContentClickedListener? = null @@ -144,13 +143,7 @@ class ChatMessageContentData( fun destroy() { scope.cancel() - val path = filePath.value.orEmpty() - if (path.isNotEmpty() && isFileEncrypted) { - Log.i("[Content] Deleting file used for preview: $path") - FileUtils.deleteFile(path) - filePath.value = "" - } - + deletePlainFilePath() chatMessage.removeListener(chatMessageListener) if (this::voiceRecordingPlayer.isInitialized) { @@ -180,9 +173,22 @@ class ChatMessageContentData( listener?.onContentClicked(getContent()) } + private fun deletePlainFilePath() { + val path = filePath.value.orEmpty() + if (path.isNotEmpty() && isFileEncrypted) { + Log.i("[Content] Deleting file used for preview: $path") + FileUtils.deleteFile(path) + filePath.value = "" + } + } + private fun updateContent() { + Log.i("[Content] Updating content") + deletePlainFilePath() + val content = getContent() isFileEncrypted = content.isFileEncrypted + Log.i("[Content] Is content encrypted ? $isFileEncrypted") filePath.value = "" fileName.value = if (content.name.isNullOrEmpty() && !content.filePath.isNullOrEmpty()) { @@ -198,7 +204,7 @@ class ChatMessageContentData( downloadLabel.value = spannable if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) { - Log.i("[Content] Is content encrypted ? $isFileEncrypted") + Log.i("[Content] Content is encrypted, requesting plain file path") val path = if (isFileEncrypted) content.plainFilePath else content.filePath ?: "" downloadable.value = content.filePath.orEmpty().isEmpty() @@ -320,8 +326,7 @@ class ChatMessageContentData( } voiceRecordingPlayer.addListener(playerListener) - val content = getContent() - val path = if (content.isFileEncrypted) content.plainFilePath else content.filePath ?: "" + val path = filePath.value voiceRecordingPlayer.open(path.orEmpty()) voiceRecordDuration.value = voiceRecordingPlayer.duration formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(voiceRecordingPlayer.duration) // is already in milliseconds diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 088128776..472b05ec4 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -901,11 +901,13 @@ class DetailChatRoomFragment : MasterFragment Date: Wed, 5 Jan 2022 12:17:31 +0100 Subject: [PATCH 081/130] Fixed chat bubbles sometimes closing when opened --- .../chat_bubble/ChatBubbleActivity.kt | 10 +++-- .../notifications/NotificationsManager.kt | 40 ++++++++++++------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt index 714773897..a9405ef01 100644 --- a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt +++ b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt @@ -94,10 +94,6 @@ class ChatBubbleActivity : GenericActivity() { return } - // Workaround for the removed notification when a chat room is marked as read - coreContext.notificationsManager.disableDismissNotificationUponReadForChatRoom(chatRoom) - chatRoom.markAsRead() - viewModel = ViewModelProvider( this, ChatRoomViewModelFactory(chatRoom) @@ -158,6 +154,7 @@ class ChatBubbleActivity : GenericActivity() { binding.setOpenAppClickListener { coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null + coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(viewModel.chatRoom, false) val intent = Intent(this, MainActivity::class.java) intent.putExtra("RemoteSipUri", remoteSipUri) @@ -182,6 +179,10 @@ class ChatBubbleActivity : GenericActivity() { viewModel.chatRoom.addListener(listener) + // Workaround for the removed notification when a chat room is marked as read + coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(viewModel.chatRoom, true) + viewModel.chatRoom.markAsRead() + val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly() coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = peerAddress coreContext.notificationsManager.resetChatNotificationCounterForSipUri(peerAddress) @@ -197,6 +198,7 @@ class ChatBubbleActivity : GenericActivity() { viewModel.chatRoom.removeListener(listener) coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null + coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom(viewModel.chatRoom, false) super.onPause() } diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index d82e59015..4de859bb7 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -63,7 +63,6 @@ class Notifiable(val notificationId: Int) { var localIdentity: String? = null var myself: String? = null var remoteAddress: String? = null - var dismissNotificationUponReadChatRoom: Boolean = true } class NotifiableMessage( @@ -102,6 +101,7 @@ class NotificationsManager(private val context: Context) { private val chatNotificationsMap: HashMap = HashMap() private val callNotificationsMap: HashMap = HashMap() private val previousChatNotifications: ArrayList = arrayListOf() + private val chatBubbleNotifications: ArrayList = arrayListOf() private var currentForegroundServiceNotificationId: Int = 0 private var serviceNotification: Notification? = null @@ -174,15 +174,20 @@ class NotificationsManager(private val context: Context) { val address = chatRoom.peerAddress.asStringUriOnly() val notifiable = chatNotificationsMap[address] if (notifiable != null) { - if (notifiable.dismissNotificationUponReadChatRoom) { + if (chatBubbleNotifications.contains(notifiable.notificationId)) { + Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, not removing notification because of a chat bubble") + } else { Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, removing notification if any") dismissChatNotification(chatRoom) - } else { - Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, not removing notification, maybe because of a chat bubble?") } } else { - Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read but no notifiable found, removing notification if any") - dismissChatNotification(chatRoom) + val notificationId = chatRoom.creationTime.toInt() + if (chatBubbleNotifications.contains(notificationId)) { + Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read but no notifiable found, not removing notification because of a chat bubble") + } else { + Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read but no notifiable found, removing notification if any") + dismissChatNotification(chatRoom) + } } } } @@ -696,21 +701,26 @@ class NotificationsManager(private val context: Context) { } else { val previousNotificationId = previousChatNotifications.find { id -> id == room.creationTime.toInt() } if (previousNotificationId != null) { - Log.i("[Notifications Manager] Found previous notification with same ID [$previousNotificationId], canceling it") - cancel(previousNotificationId, CHAT_TAG) + if (chatBubbleNotifications.contains(previousNotificationId)) { + Log.i("[Notifications Manager] Found previous notification with same ID [$previousNotificationId] but not cancelling it as it's ID is in chat bubbles list") + } else { + Log.i("[Notifications Manager] Found previous notification with same ID [$previousNotificationId], canceling it") + cancel(previousNotificationId, CHAT_TAG) + } return true } } return false } - fun disableDismissNotificationUponReadForChatRoom(chatRoom: ChatRoom) { - val address = chatRoom.peerAddress.asStringUriOnly() - val notifiable: Notifiable? = chatNotificationsMap[address] - if (notifiable != null) { - Log.i("[Notifications Manager] Prevent notification with id [${notifiable.notificationId}] from being dismissed when chat room will be marked as read") - notifiable.dismissNotificationUponReadChatRoom = false - chatNotificationsMap[address] = notifiable + fun changeDismissNotificationUponReadForChatRoom(chatRoom: ChatRoom, dismiss: Boolean) { + val notificationId = chatRoom.creationTime.toInt() + if (dismiss) { + Log.i("[Notifications Manager] Allow notification with id [$notificationId] to be dismissed when chat room will be marked as read, used for chat bubble") + chatBubbleNotifications.add(notificationId) + } else { + Log.i("[Notifications Manager] Prevent notification with id [$notificationId] from being dismissed when chat room will be marked as read, used for chat bubble") + chatBubbleNotifications.remove(notificationId) } } From c6cc2e56d4ff16129fcdbf1404c6bdeebe6172d6 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 5 Jan 2022 14:18:15 +0100 Subject: [PATCH 082/130] Another fix for proper contact names in chat rooms list --- .../activities/main/chat/viewmodels/ChatRoomViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt index f17074a31..72e950a23 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt @@ -113,6 +113,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf override fun onContactsUpdated() { Log.i("[Chat Room] Contacts have changed") contactLookup() + lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory) } } @@ -215,7 +216,6 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf chatRoom.addListener(chatRoomListener) coreContext.contactsManager.addListener(contactsUpdatedListener) - lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory) unreadMessagesCount.value = chatRoom.unreadMessagesCount lastUpdate.value = TimestampUtils.toString(chatRoom.lastUpdateTime, true) @@ -226,6 +226,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf contactLookup() updateParticipants() + lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory) callInProgress.value = chatRoom.core.callsNb > 0 updateRemotesComposing() From 2729916ce4c94420473fdc85385b90e1fb0eb65a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 5 Jan 2022 15:58:37 +0100 Subject: [PATCH 083/130] Bumped version code --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 876de539b..b1f905003 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ plugins { def appVersionName = "4.6.0" // Uncomment for release // def appVersionCode = 40600 // 4.06.00 -def appVersionCode = 40591 // 4.05.91 +def appVersionCode = 40592 // 4.05.92 static def getPackageName() { return "org.linphone" From 5605e37121ff85044e8d81a770cef8cd2dd44698 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 6 Jan 2022 17:29:07 +0100 Subject: [PATCH 084/130] Fixed kotlin boolean properties changes in Java wrapper + allow CPIM messages in basic chat rooms for sip.linphone.org accounts --- .../assets/assistant_linphone_default_values | 1 + .../assistant/fragments/QrCodeFragment.kt | 8 +++---- .../activities/call/IncomingCallActivity.kt | 2 +- .../activities/call/OutgoingCallActivity.kt | 2 +- .../call/data/CallStatisticsData.kt | 4 ++-- .../call/fragments/ControlsFragment.kt | 4 ++-- .../call/viewmodels/CallViewModel.kt | 2 +- .../call/viewmodels/CallsViewModel.kt | 6 ++--- .../call/viewmodels/ControlsViewModel.kt | 12 +++++----- .../call/viewmodels/IncomingCallViewModel.kt | 4 ++-- .../chat/fragments/DetailChatRoomFragment.kt | 4 ++-- .../main/chat/fragments/DevicesFragment.kt | 2 +- .../main/chat/fragments/GroupInfoFragment.kt | 2 +- .../main/chat/fragments/ImdnFragment.kt | 2 +- .../viewmodels/ChatRoomCreationViewModel.kt | 4 ++-- .../main/chat/viewmodels/ChatRoomViewModel.kt | 4 ++-- .../chat/viewmodels/EphemeralViewModel.kt | 12 +++++----- .../chat/viewmodels/GroupInfoViewModel.kt | 4 ++-- .../main/dialer/viewmodels/DialerViewModel.kt | 2 +- .../viewmodels/AccountSettingsViewModel.kt | 4 ++-- .../viewmodels/AudioSettingsViewModel.kt | 10 ++++---- .../viewmodels/ContactsSettingsViewModel.kt | 2 +- .../viewmodels/NetworkSettingsViewModel.kt | 8 +++---- .../viewmodels/TunnelSettingsViewModel.kt | 4 ++-- .../viewmodels/VideoSettingsViewModel.kt | 6 ++--- .../compatibility/Api26Compatibility.kt | 4 ++-- .../compatibility/Api31Compatibility.kt | 2 +- .../org/linphone/contact/NativeContact.kt | 2 +- .../java/org/linphone/core/CoreContext.kt | 24 ++++++++++++------- .../java/org/linphone/utils/LinphoneUtils.kt | 6 ++--- .../main/res/layout/chat_bubble_activity.xml | 2 +- .../res/layout/chat_room_detail_fragment.xml | 2 +- 32 files changed, 82 insertions(+), 75 deletions(-) diff --git a/app/src/main/assets/assistant_linphone_default_values b/app/src/main/assets/assistant_linphone_default_values index f7561ee9b..9f2badee0 100644 --- a/app/src/main/assets/assistant_linphone_default_values +++ b/app/src/main/assets/assistant_linphone_default_values @@ -16,6 +16,7 @@ sip.linphone.org sip:conference-factory@sip.linphone.org 1 + 1
stun.linphone.org diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/QrCodeFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/QrCodeFragment.kt index 7b5a599e4..115ec375e 100644 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/QrCodeFragment.kt +++ b/app/src/main/java/org/linphone/activities/assistant/fragments/QrCodeFragment.kt @@ -76,14 +76,14 @@ class QrCodeFragment : GenericFragment() { super.onResume() coreContext.core.nativePreviewWindowId = binding.qrCodeCaptureTexture - coreContext.core.enableQrcodeVideoPreview(true) - coreContext.core.enableVideoPreview(true) + coreContext.core.isQrcodeVideoPreviewEnabled = true + coreContext.core.isVideoPreviewEnabled = true } override fun onPause() { coreContext.core.nativePreviewWindowId = null - coreContext.core.enableQrcodeVideoPreview(false) - coreContext.core.enableVideoPreview(false) + coreContext.core.isQrcodeVideoPreviewEnabled = false + coreContext.core.isVideoPreviewEnabled = false super.onPause() } diff --git a/app/src/main/java/org/linphone/activities/call/IncomingCallActivity.kt b/app/src/main/java/org/linphone/activities/call/IncomingCallActivity.kt index 76fb75e3d..5ddc9b93d 100644 --- a/app/src/main/java/org/linphone/activities/call/IncomingCallActivity.kt +++ b/app/src/main/java/org/linphone/activities/call/IncomingCallActivity.kt @@ -139,7 +139,7 @@ class IncomingCallActivity : GenericActivity() { permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) } - if (viewModel.call.currentParams.videoEnabled() && !PermissionHelper.get().hasCameraPermission()) { + if (viewModel.call.currentParams.isVideoEnabled && !PermissionHelper.get().hasCameraPermission()) { Log.i("[Incoming Call Activity] Asking for CAMERA permission") permissionsRequiredList.add(Manifest.permission.CAMERA) } diff --git a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt index b4223f928..c5ba36007 100644 --- a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt +++ b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt @@ -179,7 +179,7 @@ class OutgoingCallActivity : ProximitySensorActivity() { Log.i("[Outgoing Call Activity] Asking for RECORD_AUDIO permission") permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) } - if (viewModel.call.currentParams.videoEnabled() && !PermissionHelper.get().hasCameraPermission()) { + if (viewModel.call.currentParams.isVideoEnabled && !PermissionHelper.get().hasCameraPermission()) { Log.i("[Outgoing Call Activity] Asking for CAMERA permission") permissionsRequiredList.add(Manifest.permission.CAMERA) } diff --git a/app/src/main/java/org/linphone/activities/call/data/CallStatisticsData.kt b/app/src/main/java/org/linphone/activities/call/data/CallStatisticsData.kt index b99754158..bbfe7a887 100644 --- a/app/src/main/java/org/linphone/activities/call/data/CallStatisticsData.kt +++ b/app/src/main/java/org/linphone/activities/call/data/CallStatisticsData.kt @@ -36,7 +36,7 @@ class CallStatisticsData(val call: Call) : GenericContactData(call.remoteAddress private val listener = object : CoreListenerStub() { override fun onCallStatsUpdated(core: Core, call: Call, stats: CallStats) { if (call == this@CallStatisticsData.call) { - isVideoEnabled.value = call.currentParams.videoEnabled() + isVideoEnabled.value = call.currentParams.isVideoEnabled updateCallStats(stats) } } @@ -50,7 +50,7 @@ class CallStatisticsData(val call: Call) : GenericContactData(call.remoteAddress initCallStats() - val videoEnabled = call.currentParams.videoEnabled() + val videoEnabled = call.currentParams.isVideoEnabled isVideoEnabled.value = videoEnabled isExpanded.value = coreContext.core.currentCall == call diff --git a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt index 674b31ee8..8c8f89640 100644 --- a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt +++ b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt @@ -126,8 +126,8 @@ class ControlsFragment : GenericFragment() { if (call.state == Call.State.StreamsRunning) { dialog?.dismiss() } else if (call.state == Call.State.UpdatedByRemote) { - if (coreContext.core.videoCaptureEnabled() || coreContext.core.videoDisplayEnabled()) { - if (call.currentParams.videoEnabled() != call.remoteParams?.videoEnabled()) { + if (coreContext.core.isVideoCaptureEnabled || coreContext.core.isVideoDisplayEnabled) { + if (call.currentParams.isVideoEnabled != call.remoteParams?.isVideoEnabled) { showCallVideoUpdateDialog(call) } } else { diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt index 9275ade4a..59216ead9 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt @@ -139,7 +139,7 @@ open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAd } fun takeScreenshot() { - if (call.currentParams.videoEnabled()) { + if (call.currentParams.isVideoEnabled) { val fileName = System.currentTimeMillis().toString() + ".jpeg" call.takeVideoSnapshot(FileUtils.getFileStoragePath(fileName).absolutePath) } diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt index 2372b756d..e6165e651 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt @@ -75,11 +75,11 @@ class CallsViewModel : ViewModel() { } else if (call.state == Call.State.UpdatedByRemote) { // If the correspondent asks to turn on video while audio call, // defer update until user has chosen whether to accept it or not - val remoteVideo = call.remoteParams?.videoEnabled() ?: false - val localVideo = call.currentParams.videoEnabled() + val remoteVideo = call.remoteParams?.isVideoEnabled ?: false + val localVideo = call.currentParams.isVideoEnabled val autoAccept = call.core.videoActivationPolicy.automaticallyAccept if (remoteVideo && !localVideo && !autoAccept) { - if (coreContext.core.videoCaptureEnabled() || coreContext.core.videoDisplayEnabled()) { + if (coreContext.core.isVideoCaptureEnabled || coreContext.core.isVideoDisplayEnabled) { call.deferUpdate() callUpdateEvent.value = Event(call) } else { diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt index 96d597b64..094cd64c2 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsViewModel.kt @@ -253,8 +253,8 @@ class ControlsViewModel : ViewModel() { } somethingClickedEvent.value = Event(true) - val micEnabled = coreContext.core.micEnabled() - coreContext.core.enableMic(!micEnabled) + val micEnabled = coreContext.core.isMicEnabled + coreContext.core.isMicEnabled = !micEnabled updateMuteMicState() } @@ -304,7 +304,7 @@ class ControlsViewModel : ViewModel() { isVideoUpdateInProgress.value = true val params = core.createCallParams(currentCall) - params?.enableVideo(!currentCall.currentParams.videoEnabled()) + params?.isVideoEnabled = !currentCall.currentParams.isVideoEnabled currentCall.update(params) } } @@ -386,7 +386,7 @@ class ControlsViewModel : ViewModel() { somethingClickedEvent.value = Event(true) val core = coreContext.core - val currentCallVideoEnabled = core.currentCall?.currentParams?.videoEnabled() ?: false + val currentCallVideoEnabled = core.currentCall?.currentParams?.isVideoEnabled ?: false val params = core.createConferenceParams() params.isVideoEnabled = currentCallVideoEnabled @@ -419,7 +419,7 @@ class ControlsViewModel : ViewModel() { } fun updateMuteMicState() { - isMicrophoneMuted.value = !PermissionHelper.get().hasRecordAudioPermission() || !coreContext.core.micEnabled() + isMicrophoneMuted.value = !PermissionHelper.get().hasRecordAudioPermission() || !coreContext.core.isMicEnabled isMuteMicrophoneEnabled.value = coreContext.core.currentCall != null || coreContext.core.conference?.isIn == true } @@ -465,7 +465,7 @@ class ControlsViewModel : ViewModel() { private fun updateVideoAvailable() { val core = coreContext.core val currentCall = core.currentCall - isVideoAvailable.value = (core.videoCaptureEnabled() || core.videoPreviewEnabled()) && + isVideoAvailable.value = (core.isVideoCaptureEnabled || core.isVideoPreviewEnabled) && ( (currentCall != null && !currentCall.mediaInProgress()) || core.conference?.isIn == true diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/IncomingCallViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/IncomingCallViewModel.kt index 639a43a22..f1dc0bfb6 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/IncomingCallViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/IncomingCallViewModel.kt @@ -60,10 +60,10 @@ class IncomingCallViewModel(call: Call) : CallViewModel(call) { coreContext.core.addListener(listener) screenLocked.value = false - inviteWithVideo.value = call.remoteParams?.videoEnabled() == true && coreContext.core.videoActivationPolicy.automaticallyAccept + inviteWithVideo.value = call.remoteParams?.isVideoEnabled == true && coreContext.core.videoActivationPolicy.automaticallyAccept earlyMediaVideoEnabled.value = corePreferences.acceptEarlyMedia && call.state == Call.State.IncomingEarlyMedia && - call.currentParams.videoEnabled() + call.currentParams.isVideoEnabled } override fun onCleared() { diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 472b05ec4..7c613d3d8 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -173,7 +173,7 @@ class DetailChatRoomFragment : MasterFragment navigateToImageFileViewer( preventScreenshots diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt index 243c37fe1..8c83940c2 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt @@ -54,7 +54,7 @@ class DevicesFragment : SecureFragment() { return } - isSecure = chatRoom.currentParams.encryptionEnabled() + isSecure = chatRoom.currentParams.isEncryptionEnabled listViewModel = ViewModelProvider( this, diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt index 7777b379a..dede4bd89 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt @@ -61,7 +61,7 @@ class GroupInfoFragment : SecureFragment() { } val chatRoom: ChatRoom? = sharedViewModel.selectedGroupChatRoom.value - isSecure = chatRoom?.currentParams?.encryptionEnabled() ?: false + isSecure = chatRoom?.currentParams?.isEncryptionEnabled ?: false viewModel = ViewModelProvider( this, diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt index d59a75438..d07e8ca3b 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt @@ -61,7 +61,7 @@ class ImdnFragment : SecureFragment() { return } - isSecure = chatRoom.currentParams.encryptionEnabled() + isSecure = chatRoom.currentParams.isEncryptionEnabled if (arguments != null) { val messageId = arguments?.getString("MessageId") diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt index 17cefca50..bb1cc356e 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt @@ -160,9 +160,9 @@ class ChatRoomCreationViewModel : ErrorReportingViewModel() { val encrypted = isEncrypted.value == true val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() params.backend = ChatRoomBackend.Basic - params.enableGroup(false) + params.isGroupEnabled = false if (encrypted) { - params.enableEncryption(true) + params.isEncryptionEnabled = true params.backend = ChatRoomBackend.FlexisipChat params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) ChatRoomEphemeralMode.DeviceManaged diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt index 72e950a23..396adee3e 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt @@ -203,7 +203,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf } override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) { - ephemeralEnabled.value = chatRoom.ephemeralEnabled() + ephemeralEnabled.value = chatRoom.isEphemeralEnabled } override fun onParticipantAdminStatusChanged(chatRoom: ChatRoom, eventLog: EventLog) { @@ -222,7 +222,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf subject.value = chatRoom.subject updateSecurityIcon() meAdmin.value = chatRoom.me?.isAdmin ?: false - ephemeralEnabled.value = chatRoom.ephemeralEnabled() + ephemeralEnabled.value = chatRoom.isEphemeralEnabled contactLookup() updateParticipants() diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/EphemeralViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/EphemeralViewModel.kt index 3704ef1ae..ce56f8034 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/EphemeralViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/EphemeralViewModel.kt @@ -50,8 +50,8 @@ class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() { } init { - Log.i("[Ephemeral Messages] Current lifetime is ${chatRoom.ephemeralLifetime}, ephemeral enabled? ${chatRoom.ephemeralEnabled()}") - currentSelectedDuration = if (chatRoom.ephemeralEnabled()) chatRoom.ephemeralLifetime else 0 + Log.i("[Ephemeral Messages] Current lifetime is ${chatRoom.ephemeralLifetime}, ephemeral enabled? ${chatRoom.isEphemeralEnabled}") + currentSelectedDuration = if (chatRoom.isEphemeralEnabled) chatRoom.ephemeralLifetime else 0 computeEphemeralDurationValues() } @@ -65,13 +65,13 @@ class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() { Log.i("[Ephemeral Messages] Configured lifetime for ephemeral messages was already $currentSelectedDuration") } - if (!chatRoom.ephemeralEnabled()) { + if (!chatRoom.isEphemeralEnabled) { Log.i("[Ephemeral Messages] Ephemeral messages were disabled, enable them") - chatRoom.enableEphemeral(true) + chatRoom.isEphemeralEnabled = true } - } else if (chatRoom.ephemeralEnabled()) { + } else if (chatRoom.isEphemeralEnabled) { Log.i("[Ephemeral Messages] Ephemeral messages were enabled, disable them") - chatRoom.enableEphemeral(false) + chatRoom.isEphemeralEnabled = false } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt index aa5795d7a..7bfd94e48 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt @@ -117,8 +117,8 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : ErrorReportingViewModel() { fun createChatRoom() { waitForChatRoomCreation.value = true val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() - params.enableEncryption(isEncrypted.value == true) - params.enableGroup(true) + params.isEncryptionEnabled = isEncrypted.value == true + params.isGroupEnabled = true if (isEncrypted.value == true) { params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) ChatRoomEphemeralMode.DeviceManaged diff --git a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt index bccebe240..964399d6b 100644 --- a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt @@ -160,7 +160,7 @@ class DialerViewModel : LogsUploadViewModel() { fun updateShowVideoPreview() { val videoPreview = corePreferences.videoPreview showPreview.value = videoPreview - coreContext.core.enableVideoPreview(videoPreview) + coreContext.core.isVideoPreviewEnabled = videoPreview } fun eraseLastChar() { diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt index 28dc867a7..81760768b 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt @@ -315,7 +315,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( val iceListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { val params = account.params.clone() - params.natPolicy?.enableIce(newValue) + params.natPolicy?.isIceEnabled = newValue account.params = params } } @@ -437,7 +437,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( proxy.value = params.serverAddress?.asStringUriOnly() outboundProxy.value = params.isOutboundProxyEnabled stunServer.value = params.natPolicy?.stunServer - ice.value = params.natPolicy?.iceEnabled() + ice.value = params.natPolicy?.isIceEnabled avpf.value = params.avpfMode == AVPFMode.Enabled avpfRrInterval.value = params.avpfRrInterval expires.value = params.expires diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt index 5e356b891..8c684ef7d 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt @@ -41,7 +41,7 @@ class AudioSettingsViewModel : GenericSettingsViewModel() { val echoCancellationListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { - core.enableEchoCancellation(newValue) + core.isEchoCancellationEnabled = newValue } } val echoCancellation = MutableLiveData() @@ -81,7 +81,7 @@ class AudioSettingsViewModel : GenericSettingsViewModel() { val adaptiveRateControlListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { - core.enableAdaptiveRateControl(newValue) + core.isAdaptiveRateControlEnabled = newValue } } val adaptiveRateControl = MutableLiveData() @@ -153,9 +153,9 @@ class AudioSettingsViewModel : GenericSettingsViewModel() { val audioCodecs = MutableLiveData>() init { - echoCancellation.value = core.echoCancellationEnabled() - adaptiveRateControl.value = core.adaptiveRateControlEnabled() - echoCalibration.value = if (core.echoCancellationEnabled()) { + echoCancellation.value = core.isEchoCancellationEnabled + adaptiveRateControl.value = core.isAdaptiveRateControlEnabled + echoCalibration.value = if (core.isEchoCancellationEnabled) { prefs.getString(R.string.audio_settings_echo_cancellation_calibration_value).format(prefs.echoCancellerCalibration) } else { prefs.getString(R.string.audio_settings_echo_canceller_calibration_summary) diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt index 805279621..41aac8d87 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt @@ -33,7 +33,7 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() { val friendListSubscribeListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { - core.enableFriendListSubscription(newValue) + core.isFriendListSubscriptionEnabled = newValue } } val friendListSubscribe = MutableLiveData() diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/NetworkSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/NetworkSettingsViewModel.kt index 1a07e8736..c282b006e 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/NetworkSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/NetworkSettingsViewModel.kt @@ -26,14 +26,14 @@ import org.linphone.activities.main.settings.SettingListenerStub class NetworkSettingsViewModel : GenericSettingsViewModel() { val wifiOnlyListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { - core.enableWifiOnly(newValue) + core.isWifiOnlyEnabled = newValue } } val wifiOnly = MutableLiveData() val allowIpv6Listener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { - core.enableIpv6(newValue) + core.isIpv6Enabled = newValue } } val allowIpv6 = MutableLiveData() @@ -59,8 +59,8 @@ class NetworkSettingsViewModel : GenericSettingsViewModel() { val sipPort = MutableLiveData() init { - wifiOnly.value = core.wifiOnlyEnabled() - allowIpv6.value = core.ipv6Enabled() + wifiOnly.value = core.isWifiOnlyEnabled + allowIpv6.value = core.isIpv6Enabled randomPorts.value = getTransportPort() == -1 sipPort.value = getTransportPort() } diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/TunnelSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/TunnelSettingsViewModel.kt index f07177204..3822fe5fa 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/TunnelSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/TunnelSettingsViewModel.kt @@ -52,7 +52,7 @@ class TunnelSettingsViewModel : GenericSettingsViewModel() { val useDualModeListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { val tunnel = core.tunnel - tunnel?.enableDualMode(newValue) + tunnel?.isDualModeEnabled = newValue } } val useDualMode = MutableLiveData() @@ -96,7 +96,7 @@ class TunnelSettingsViewModel : GenericSettingsViewModel() { hostnameUrl.value = config.host port.value = config.port - useDualMode.value = tunnel?.dualModeEnabled() + useDualMode.value = tunnel?.isDualModeEnabled hostnameUrl2.value = config.host2 port2.value = config.port2 diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/VideoSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/VideoSettingsViewModel.kt index f8ed1bfc1..d871e341a 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/VideoSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/VideoSettingsViewModel.kt @@ -31,8 +31,8 @@ import org.linphone.core.tools.Log class VideoSettingsViewModel : GenericSettingsViewModel() { val enableVideoListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { - core.enableVideoCapture(newValue) - core.enableVideoDisplay(newValue) + core.isVideoCaptureEnabled = newValue + core.isVideoDisplayEnabled = newValue if (!newValue) { tabletPreview.value = false initiateCall.value = false @@ -115,7 +115,7 @@ class VideoSettingsViewModel : GenericSettingsViewModel() { val videoCodecs = MutableLiveData>() init { - enableVideo.value = core.videoEnabled() && core.videoSupported() + enableVideo.value = core.isVideoEnabled && core.videoSupported() tabletPreview.value = prefs.videoPreview isTablet.value = coreContext.context.resources.getBoolean(R.bool.isTablet) initiateCall.value = core.videoActivationPolicy.automaticallyInitiate diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt index 9a541f59e..4716219fd 100644 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt @@ -206,7 +206,7 @@ class Api26Compatibility { } Call.State.OutgoingRinging, Call.State.OutgoingProgress, Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia -> { stringResourceId = R.string.call_notification_outgoing - iconResourceId = if (call.params.videoEnabled()) { + iconResourceId = if (call.params.isVideoEnabled) { R.drawable.topbar_videocall_notification } else { R.drawable.topbar_call_notification @@ -214,7 +214,7 @@ class Api26Compatibility { } else -> { stringResourceId = R.string.call_notification_active - iconResourceId = if (call.currentParams.videoEnabled()) { + iconResourceId = if (call.currentParams.isVideoEnabled) { R.drawable.topbar_videocall_notification } else { R.drawable.topbar_call_notification diff --git a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt index 7ca6dde46..82262693e 100644 --- a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt @@ -96,7 +96,7 @@ class Api31Compatibility { val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri) val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress) - val isVideo = call.currentParams.videoEnabled() + val isVideo = call.currentParams.isVideoEnabled val iconResourceId: Int = when (call.state) { Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> { R.drawable.topbar_call_paused_notification diff --git a/app/src/main/java/org/linphone/contact/NativeContact.kt b/app/src/main/java/org/linphone/contact/NativeContact.kt index cb487bedd..c28f6fd6b 100644 --- a/app/src/main/java/org/linphone/contact/NativeContact.kt +++ b/app/src/main/java/org/linphone/contact/NativeContact.kt @@ -192,7 +192,7 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null) var created = false if (friend == null) { val friend = coreContext.core.createFriend() - friend.enableSubscribes(false) + friend.isSubscribesEnabled = false friend.incSubscribePolicy = SubscribePolicy.SPDeny friend.refKey = nativeId friend.userData = this diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index f356e47bb..7462ad19b 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -189,7 +189,7 @@ class CoreContext(val context: Context, coreConfig: Config) { } } - if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && call.currentParams.videoEnabled()) { + if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && call.currentParams.isVideoEnabled) { // Do not turn speaker on when video is enabled if headset or bluetooth is used if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed( call @@ -380,6 +380,12 @@ class CoreContext(val context: Context, coreConfig: Config) { core.limeX3DhServerUrl = url } } + + // Ensure we allow CPIM messages in basic chat rooms + val newParams = account.params.clone() + newParams.isCpimInBasicChatRoomEnabled = true + account.params = newParams + Log.i("[Context] CPIM allowed in basic chat rooms for account ${newParams.identityAddress?.asStringUriOnly()}") } } @@ -455,11 +461,11 @@ class CoreContext(val context: Context, coreConfig: Config) { val params = core.createCallParams(call) if (accept) { - params?.enableVideo(true) - core.enableVideoCapture(true) - core.enableVideoDisplay(true) + params?.isVideoEnabled = true + core.isVideoCaptureEnabled = true + core.isVideoDisplayEnabled = true } else { - params?.enableVideo(false) + params?.isVideoEnabled = false } call.acceptUpdate(params) @@ -471,7 +477,7 @@ class CoreContext(val context: Context, coreConfig: Config) { params?.recordFile = LinphoneUtils.getRecordingFilePathForAddress(call.remoteAddress) if (LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) { Log.w("[Context] Enabling low bandwidth mode!") - params?.enableLowBandwidth(true) + params?.isLowBandwidthEnabled = true } call.acceptWithParams(params) } @@ -548,7 +554,7 @@ class CoreContext(val context: Context, coreConfig: Config) { } if (LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) { Log.w("[Context] Enabling low bandwidth mode!") - params.enableLowBandwidth(true) + params.isLowBandwidthEnabled = true } params.recordFile = LinphoneUtils.getRecordingFilePathForAddress(address) @@ -562,7 +568,7 @@ class CoreContext(val context: Context, coreConfig: Config) { } if (corePreferences.sendEarlyMedia) { - params.enableEarlyMediaSending(true) + params.isEarlyMediaSendingEnabled = true } val call = core.inviteAddressWithParams(address, params) @@ -601,7 +607,7 @@ class CoreContext(val context: Context, coreConfig: Config) { return if (conference != null && conference.isIn) { conference.currentParams.isVideoEnabled } else { - core.currentCall?.currentParams?.videoEnabled() ?: false + core.currentCall?.currentParams?.isVideoEnabled ?: false } } diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index e939a085d..162811c99 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -66,7 +66,7 @@ class LinphoneUtils { fun isLimeAvailable(): Boolean { val core = coreContext.core - return core.limeX3DhAvailable() && core.limeX3DhEnabled() && + return core.limeX3DhAvailable() && core.isLimeX3DhEnabled && core.limeX3DhServerUrl != null && core.defaultAccount?.params?.conferenceFactoryUri != null } @@ -81,11 +81,11 @@ class LinphoneUtils { val defaultAccount = core.defaultAccount val params = core.createDefaultChatRoomParams() - params.enableGroup(false) + params.isGroupEnabled = false params.backend = ChatRoomBackend.Basic if (isSecured) { params.subject = AppUtils.getString(R.string.chat_room_dummy_subject) - params.enableEncryption(true) + params.isEncryptionEnabled = true params.backend = ChatRoomBackend.FlexisipChat } diff --git a/app/src/main/res/layout/chat_bubble_activity.xml b/app/src/main/res/layout/chat_bubble_activity.xml index a3972ae18..8ed3a2130 100644 --- a/app/src/main/res/layout/chat_bubble_activity.xml +++ b/app/src/main/res/layout/chat_bubble_activity.xml @@ -128,7 +128,7 @@ android:src="@drawable/chat_send_message" /> Date: Fri, 7 Jan 2022 14:49:17 +0100 Subject: [PATCH 085/130] Improved chat messages recyclerview scrolling below remote composing label --- .../res/layout/chat_room_detail_fragment.xml | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/layout/chat_room_detail_fragment.xml b/app/src/main/res/layout/chat_room_detail_fragment.xml index edc706e54..0947d00fa 100644 --- a/app/src/main/res/layout/chat_room_detail_fragment.xml +++ b/app/src/main/res/layout/chat_room_detail_fragment.xml @@ -276,28 +276,31 @@ - - + + Date: Fri, 7 Jan 2022 15:45:55 +0100 Subject: [PATCH 086/130] Allow to show IMDN status of error message in a group chat room --- .../activities/main/chat/adapters/ChatMessagesListAdapter.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt index 676be21b0..cade0ec29 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt @@ -272,9 +272,8 @@ class ChatMessagesListAdapter( val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt() var totalSize = itemSize * 7 - if (chatMessage.chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) || - chatMessage.state == ChatMessage.State.NotDelivered - ) { // No message id + if (chatMessage.chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) { + // No message id popupView.imdnHidden = true totalSize -= itemSize } From 2ed6fa3246d99b7296ba705b5d49e1db93ba0215 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 10 Jan 2022 10:14:59 +0100 Subject: [PATCH 087/130] Prevent crash that happened once --- .../org/linphone/activities/GenericFragment.kt | 4 ++++ .../main/chat/fragments/DetailChatRoomFragment.kt | 14 +++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/GenericFragment.kt b/app/src/main/java/org/linphone/activities/GenericFragment.kt index 0ee63ec55..8cbd2a1d0 100644 --- a/app/src/main/java/org/linphone/activities/GenericFragment.kt +++ b/app/src/main/java/org/linphone/activities/GenericFragment.kt @@ -42,6 +42,10 @@ abstract class GenericFragment : Fragment() { protected val binding get() = _binding!! protected var useMaterialSharedAxisXForwardAnimation = true + protected fun isBindingAvailable(): Boolean { + return _binding != null + } + protected val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { lifecycleScope.launch { diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 7c613d3d8..e5d7745ec 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -212,11 +212,15 @@ class DetailChatRoomFragment : MasterFragment Date: Mon, 10 Jan 2022 10:43:34 +0100 Subject: [PATCH 088/130] Fixed used of deprecated adapterPosition API --- .../chat/adapters/ChatMessagesListAdapter.kt | 20 +++++++++---------- .../chat/adapters/ChatRoomsListAdapter.kt | 4 ++-- .../chat/fragments/MasterChatRoomsFragment.kt | 8 ++++---- .../contact/adapters/ContactsListAdapter.kt | 4 ++-- .../fragments/MasterContactsFragment.kt | 4 ++-- .../history/adapters/CallLogsListAdapter.kt | 4 ++-- .../fragments/MasterCallLogsFragment.kt | 4 ++-- .../adapters/RecordingsListAdapter.kt | 4 ++-- .../utils/RecyclerViewHeaderDecoration.kt | 2 +- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt index cade0ec29..7eb6f214c 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt @@ -213,13 +213,13 @@ class ChatMessagesListAdapter( selectionViewModel.isEditionEnabled.observe( viewLifecycleOwner, { - position = adapterPosition + position = bindingAdapterPosition } ) setClickListener { if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(adapterPosition) + selectionViewModel.onToggleSelect(bindingAdapterPosition) } } @@ -234,8 +234,8 @@ class ChatMessagesListAdapter( var hasPrevious = false var hasNext = false - if (adapterPosition > 0) { - val previousItem = getItem(adapterPosition - 1) + if (bindingAdapterPosition > 0) { + val previousItem = getItem(bindingAdapterPosition - 1) if (previousItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { val previousMessage = previousItem.eventLog.chatMessage if (previousMessage != null && previousMessage.fromAddress.weakEqual(chatMessage.fromAddress)) { @@ -246,8 +246,8 @@ class ChatMessagesListAdapter( } } - if (adapterPosition >= 0 && adapterPosition < itemCount - 1) { - val nextItem = getItem(adapterPosition + 1) + if (bindingAdapterPosition >= 0 && bindingAdapterPosition < itemCount - 1) { + val nextItem = getItem(bindingAdapterPosition + 1) if (nextItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { val nextMessage = nextItem.eventLog.chatMessage if (nextMessage != null && nextMessage.fromAddress.weakEqual(chatMessage.fromAddress)) { @@ -346,7 +346,7 @@ class ChatMessagesListAdapter( private fun resendMessage() { val chatMessage = binding.data?.chatMessage if (chatMessage != null) { - chatMessage.userData = adapterPosition + chatMessage.userData = bindingAdapterPosition resendMessageEvent.value = Event(chatMessage) } } @@ -388,7 +388,7 @@ class ChatMessagesListAdapter( private fun deleteMessage() { val chatMessage = binding.data?.chatMessage if (chatMessage != null) { - chatMessage.userData = adapterPosition + chatMessage.userData = bindingAdapterPosition deleteMessageEvent.value = Event(chatMessage) } } @@ -418,13 +418,13 @@ class ChatMessagesListAdapter( selectionViewModel.isEditionEnabled.observe( viewLifecycleOwner, { - position = adapterPosition + position = bindingAdapterPosition } ) binding.setClickListener { if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(adapterPosition) + selectionViewModel.onToggleSelect(bindingAdapterPosition) } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt index d4100c53f..1d8da9029 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt @@ -75,7 +75,7 @@ class ChatRoomsListAdapter( selectionViewModel.isEditionEnabled.observe( viewLifecycleOwner, { - position = adapterPosition + position = bindingAdapterPosition } ) @@ -83,7 +83,7 @@ class ChatRoomsListAdapter( setClickListener { if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(adapterPosition) + selectionViewModel.onToggleSelect(bindingAdapterPosition) } else { selectedChatRoomEvent.value = Event(chatRoomViewModel.chatRoom) } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt index 3f3f88025..36457821e 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt @@ -174,9 +174,9 @@ class MasterChatRoomsFragment : MasterFragment Date: Mon, 10 Jan 2022 11:55:13 +0100 Subject: [PATCH 089/130] Fixed some poor quality drawables + changes to allow swipe action to reply to chat message (disabled for now) --- .../chat/fragments/DetailChatRoomFragment.kt | 40 ++++++++++-------- .../java/org/linphone/core/CorePreferences.kt | 3 ++ .../linphone/utils/RecyclerViewSwipeUtils.kt | 29 ++++++++++++- .../drawable-xhdpi/menu_forward_default.png | Bin 1421 -> 11917 bytes .../drawable-xhdpi/menu_imdn_info_default.png | Bin 1590 -> 0 bytes .../res/drawable-xhdpi/menu_reply_default.png | Bin 11588 -> 12166 bytes app/src/main/res/drawable/menu_imdn_info.xml | 2 +- 7 files changed, 54 insertions(+), 20 deletions(-) delete mode 100644 app/src/main/res/drawable-xhdpi/menu_imdn_info_default.png diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index e5d7745ec..d6473826b 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -36,6 +36,7 @@ import androidx.databinding.ViewDataBinding import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import java.io.File @@ -225,27 +226,32 @@ class DetailChatRoomFragment : MasterFragment? = null ) val iconMargin = 16f @@ -61,7 +62,7 @@ class RecyclerViewSwipeConfiguration { } private class RecyclerViewSwipeUtilsCallback( - direction: Int, + val direction: Int, val configuration: RecyclerViewSwipeConfiguration, val listener: RecyclerViewSwipeListener ) : ItemTouchHelper.SimpleCallback(0, direction) { @@ -234,6 +235,30 @@ private class RecyclerViewSwipeUtilsCallback( } } + override fun getSwipeDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + var dirFlags = direction + if (direction and ItemTouchHelper.RIGHT != 0) { + val classToPrevent = configuration.leftToRightAction.preventFor + if (classToPrevent != null) { + if (classToPrevent.isInstance(viewHolder)) { + dirFlags = dirFlags and ItemTouchHelper.RIGHT.inv() + } + } + } + if (direction or ItemTouchHelper.LEFT != 0) { + val classToPrevent = configuration.rightToLeftAction.preventFor + if (classToPrevent != null) { + if (classToPrevent.isInstance(viewHolder)) { + dirFlags = dirFlags and ItemTouchHelper.LEFT.inv() + } + } + } + return dirFlags + } + override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, diff --git a/app/src/main/res/drawable-xhdpi/menu_forward_default.png b/app/src/main/res/drawable-xhdpi/menu_forward_default.png index 4212b9df2b954a605fb12da8c1efe059a58d7bca..9a5b9d85f6f5972047d25f56f0efbede66322cc5 100644 GIT binary patch literal 11917 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 NP<1`EN|n12{|`5nfz$v1 delta 1388 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 diff --git a/app/src/main/res/drawable-xhdpi/menu_reply_default.png b/app/src/main/res/drawable-xhdpi/menu_reply_default.png index 47fc5db73e7b33dc47cc841be905cf2d63138b3a..7f898c47e45dbc4974b3d261a91dadd7c9673bc6 100644 GIT binary patch delta 8893 zcmV;uB0}B7T83Yc8Gi-<0027t*>V5?6l{7_SaechcOYISdDJEziKi_de*$D9X0{o+$BBoe-+(gld}ouYVr%5B{X+ zqKT>0+;X=3i7htY`Jvk9Pru)tjraTg6Xxrj`+D5GpYR+Cx_|QdZ_Tgk-;TTY2Rt90 zpVFrX)z^dE_V?Uq!ZTa>2 zXY?(U`|S6JeE)we?A}To4BqeHg5|pp^_f7>KW{?k2gi4v)=%ypNqmpNy_Uj;{IpM2 zk$KJ5eBKWmK7USpIsW#iS$nj!`0GQ>_wc^|jYa@`%b@8@)rDMr72;nVx+!+$+iL-EbeK#4fQ%$Ga9gceq) zWW^*;0!RJ(T-+VE-QlLI47?myddI`ulSlsV`TFgH&Qa|+XY2c3F)y!-XBvu}zH=1; zapx_M(%s;mvqP>I?ODegPQcEknh8k>Kf;J3jXcVzqkoM)NuL>Knt7I4&1|zTzrq4=R$gV* z)mGnblS(`8wDT^z?za0O)lN9^q?1oM^|aI9qvp&L)?dE<_o%tIsQDWyU0B|u#;v8i zJ;Di2l43^0d~`&-C;}w3SIlg6F?yrWDQ32Lsv>!2nG~Gm6fq(gxAW<^_tVILp*Gb9%8v_n?Y?%gcC*WC3xj#KP!b&k(G+NYKXI zcVc|!+A!-dBooku1r#~9zfS#dX7^t2-Lc$3n$D$rjnPGcU?$6oT=TXcxwCC-zMOA%CKH z1~-&hMku?u6EamY%P|$9lw7~@LKXerpXS>E5}dW4=E>;L4jI3|-foHGp3v}gVV$r% zC6sgPr;L(R@Kk?IW9$MXfLx^dI;6fS3N5rkK%BC#$1}KjF#E=GVNxl z(@diB^BTdOHcOgq*U}j>xP)#Cz<&YCY-NDI_tnsmFBv+@xQzSrdfXE0cFoZo{*FwRo4xfI#o=0vXV~rg&LV>fE2+<(^Qr~?xPa=B^a=-g)Akrb_*`B>%CssYCui`^-pzZ;<*-8+j)u+0sz!KtM6%-mU9eQp)u0c96A)=v;WX-k4aJ#HR_E+?*SaY-FnNDbg529eb4OeRr>&Q@|8bFs7x2I-TCgbq0 z3(W2podzm;*c~$a)fjd~DL6)8*N#JhbyLa&T-#iT0i1vbt;kABiaU0-XE1z39JP!I zYA9`iPk*KW5kK=20wA=I;sMO)wE+jE1oVVOA0vp=c39d407s}Vh}#duZ6E@)|2S+u zj;krtkG2G!L1IYzkuGN-T%pGbXJs`1&0ZKuf6K`$jo6TH1_1~&EJrD~ia^+hI->xi zFcSwN)E%nZ0?yQftecdWR0O$6zQ^w85JIv9O@E0zXR7SXbEjwtLSoJ>QPoSO%AFS* zYzqlkogAx=)@5ceZFwS$Z^7_@wA9I7BYEL+K$HLyGw;sri=uRgWPA z@S2U;qs@Dbc3_amd>F+EDDvu1v{<>1ye#r(LLq^KMR^BVp##9-gklZT#L&69*PPJd ztbdLCFL_AoYK@cHLepWj#>kC~V22Sb^F{zkA7Sm3IEY}!dhHt@#APUV9R~=fc=tHM zU8o4KV8!qQXjL{4x6vwH{Z96}swOO+cIJk}+&%?%`>^pDDYaFDl$w^25_7kV1_lA6i`Cyw}UE#7Dp8& zpQu^MW>LtUZJrp6;!0qXn+rl4vQ}V=lnr9y<{bOf`|6F7g4;Wr4vQ8w5W0c8t#W4u z;X4Z=2jTDq|IJ>|etCkHGPTp9ukq9D6{&y{R~|tPNN%#mP?TW+>TrQ2wc$)89DhID z#730)RFQ*N6C4cu7%j>a7&z*2*8P{QXa~7buO;O$YdMivNd|*dm2TQvZa~Nj5@9ZsVE| z{tf3v1?+5tgui4}1s%sjfNFt&A{X^08XLO?Zu0C%sURswGaxbp#ac7kgMZW+5%cFG zUp11&{)!dW7RgZ-ld#A!hc3_4)*P0e)Ou8JGf_e4Sui_O2*^T%Hpo^* zRbUt88R+1yoB?nV+CxSzJ$~vWiT3fvlR6g0WSUCCK%A?RQq!}}b z+-KdS9K_NAysbPV>3_DW8rI0{53~0uOrw?qkl3UZ;WePfD_+-Ws-4G2d}KaM1eBdw zUBAh&tpe`^mCId({A6P4aNF@LP6QTlXY5Hxz!?hU2lHvk% zE-84cwlex0$A?pX8eeKomHm-%*!okY>mAZmM2!-Jvb8D59)GG8jpi!ge{O0}e$d0! zAH5HSdCeq+5ZE{uu4mF~4isng#bCNRc@eh40OP}g9Rm^IGO+xnpJYsv6htu=Ad)5^DFbt0wKBEG zmMB-!?BWyVw0}PMbO|N(B0~b0mXhYc0fMnu15EQK`*CF^EpC}rS2efhjJ8si7(Vnm z_*ke4jfJGlXvgHp$O3I5m>1e(7tD%NBf}JJp;c*mua@|Ol5yeRAgqKmw`;GZP6r8vR0Opj)d!dyu!dNk{c;ShyT_OC_!su748C{6Z+izf?BWgwTO&WiPB( zk$J;F8QSksV|^jn5oHv@MR%S(K{x4nMEyjiJ3Cu&s1E!X<;l`zQ6m6u#?NVwTEVx1 zlB)aB;@TCmB9FbAib@tvk{2wld(34v3w``FI7z^8`AotoT(YQYKDm^(jM>WYF?s1x zQ!$b*3QdJByEZ0h+9LMfGzBj25-R4^vOXc1pRTB z#2-8$`Q?zHn73weX%piIm{C2~a!#lP*}Tw>h<{SHv9?rSuUy&KvC&xV7VX-VzF90mW$r#ic8QuH_DhYChWT^&}#889rx5kdjT-F+Rf(BtY0;FbB?UB;(?MS?HXJUgp53sBy= zZx2A+3(ZFQ495yY!7A#`s{`$1K}qUa>q)sLm_;#Eqkn!_!D~TVYldDy5l-hz#MzOOWR}B!u8`H> zWT*9R4f(#3M?|ceFWeUWmWH|^8K!~nJ5t9Y^ALZ`1H7t5T#erYoft;ll|_E519E4| zI-FZo-D5V>?_0oPdJ+J{)%$Vb5LYesfVx(ndV0T@B~UWw?yO-{vX(qPac_gtAAhY; zMUzB`L7mRgBgXeOsF0^??RHz&OR5@0-o4&1U5WqJ@?~GmhO5-3SWKkSyzHS9#%BJ{QygQ$>R4St3Y27mbNPE-E@V*-3LVf7{D`sGPrIiN#LEj7G|lnf5i z#2Uy#txsxbG4vMn%%L6wehmc%?qQLxSf#j7U23l%Se*oELm-wk?2M+1=BmWsBd#G` zHnop~*+LO#%pr~-U#HR3%0+N6Gx?2?hoFezsWLO%O^r<2)-2PSWsd>$EPtzwKL)OZ z8h{~Cgbo1|0<}4|6VorbWo5q6`+U7F4ILs(dYgge0o$;4bJQ6ij{!zg`EZix8NY`5 zS`m1S2%#g7n~sP3;d(1nI|(a=Wx{Lkq>N?5&<$LvcMtjZFU6SPv8WNCY1c;5#|9V| zth!#*jYcoRgRDT_0JTLX8*~qwS&xf)Zzd?njaDE> zR0Lg*%Qzme_97_C8+JP@se@yvrJ{!H(w#OQbupJmFW-JN2;AT_u5a?r)8g5{p&)wM0Ph2mRDI>cf^GTFm9b-(edG8rF9Fv;HM?xa_{D+G)f4&>v zH)9psP_;jv+}VGo|9M{-w2xN^^NAubzL$Ot1UqlMURPAXJ(;Z5_n7Ew{l)}@i$l*v zh>jrE3$BJFYNA}y9e-?7HxAvn$*XQKG8ESs*K#y{!?m5aLEGu z1s6X)&G!R;`so6(tizdf5{p4EgEL<5eT6DT{V_FS%X->F8B)02AyQtXp?)ArKneiu z7!hFxeB1hsF*X=P9eP&{64$ci=aC8I(MZ&*$NVOTV_od&>wnh>=*9g+<*9dfxyT4%Ye#U$`rMr*%ZKzAwM^Os)cHgTD8>s<@ zIM#bQQ+(c}U=zs!TsDw{nm_A##iC}Hevw8^dMlq33)`wU#}V4bvct$y86!c?ED~k2 zC1zGIAZF`G{C^z)cy)ad{^4pHwRHLa11JAA|4sj!egow<50Y`E5dRIFc|mwZi7rY2 z00D$)LqkwWLqi~Na&Km7Y-Iodc$|HaJxIe)6opUIic%^Lb`WvMP@OD@is+_QC_;r$ zE41oh^3p$O(vYOMI0~)>2Y(i;4ld5RI=Bjg;17tSlYf()5(OG&8>>vuLvMaUkEcYvy3@OO2N0j?x~yVF3z+3`~Iw6HE%H> zAQI0q!?cMvh^IDfgY!Odl$B(a_?&puqyrK^a$WKGjdRgufoDd|OnRO;N-P#TSm|I^ zGBx5!;(wT`>69;IJytnyan>p|);cG@VI;4wEOVXK5RzEL5+sOFP(v9N*of0#C&faV z_7gt-LDw&lOCeVoj2sK7LWAu3!QbHbY^}n?xR(@309`MR^Dzu`?*h%b<9r`GPV)o^ zJ_A>J+h1(})1Rc*+gj`h=-UP^uG^Zj2VCv|13*uPY|5?_q$Lyz!220JQx53A1;T6I z+*2vtJDo}a0rYPDSO@L-aVaj`?sexzaKlRa*^9sqG1344gIs)8%P2pGdW^3 zHaTQ1WHvT6Ei^SXV=XvlG&3zQFflVWGG%5rH#K3CMIIj{GGaMoG&5o_EiyA>WGyr` zH#IFeVlg=_Hexh6V>B{kH8nP7lZYNCBr;+!GcYqbIW1yiW@IfiVP<75Vq`gEEn;Fh zHaRk5VPZ8kGLzFDTMjfaI50UeG%_+UF*Z0dlRO_80XUOZAG;(mH#syiG%++SI50Lc zEi_|gF)cVUHZv__V>V?tVKX-|W->LCH6TS8GA%GMEio`uF)=zYGdeRhD=;xSFfbx7 zldm6QBspbdHDoq5H!U<{F)}SQVq|44I5;^oEjD2`GdVUgV=yr_Fq1VPMH4bDFflDL zFjO%yIxsalG%_nNF|&;zYX}xY5}vgH000JJOGiWiX8@G|xtla$5t9lj9)IQm3JL`( z^-m+N000XDNkl5q9GB z%mv%{fsOHFZ9Ka(_x$Quff5=cZs!P7!#3-s#e^0R7BoV)x9FJ2iObj1P%a^h=160I;|TvY|sw`a6X?; ztLo>0?*bhOMn54UPpj&~!0y4p!KqHdJ9Q0I0qef1sx=^T-x@G3BD;Z2z;CRzn{K=9 zwy;vEjLn)gtNP3{&zv)OIRn^c;Ez=GZvj8yOdJ-Ge@~~=YX$}e-kwA|RszEpK?|(| z9|08+c@20jlYhwsBC@Aasf-Q{4b{#Ya8FOqj3|okQPl^4YvVft>=%)Loj!f~qt8G8 z{E4$1hgHDP2XG7(up)BAE$F|g>Xzo_=7a0kueav}xVyXiYHRJ6RrS7v4Fz~zM4o7A zY5C{={{HIOI?m;CHvs?Wc4(q;jXNgwZw6>_HoV1If`7KN0uVW_s_R7LN5G~a2o9eW z@XD1dC6!9e1O7r){~VZ=sJ9jPhh@u_J-uel8hbIe>F&GlZaRMacqWRX=2R-x97WMg zRlORRqpEX&kBNwb2{r@x}Pd)XNtX;dd4!je%QB^+yECpsv&}2+R`iwD8w6(Rpux{PD z)8;F8+;PXuKMMh(+hz^p66|@howeUXC%ziu1qEq_V@Q+(qQ8f1i=9j`3dlMz`L=cCg6)A za(!=auc@!XE@#Z^{@#l5@$uX4y6dh?BPHj8AUJA_St}xsIpV6z0^o~L6wLuhd7gJ2 za8vBi&sNnN$HvAEb#!!;ckI|P-YB5U<$v-?&-3;J*T=m?5otHZ40d#MyqWSmZ!hpU zXWZ*@C2+H<9?NF4rBbO>X%x_l7A-pJGxipW0T2H>h#+Zn3* z8KCOu{X~Q8a^(Ad75Jnht-8z-k)Ni*FpNCU3xPerBH)@>rB+qF8OZp)|GO{@kAF1+ z=*5c{S5$Q|@R``pSBS{767ym*z)Iko!16>N6%qNdh&)m(7DuM&70m(uAWn#}GgXgx zB2}%b>PchFF;zX()YNov!-fqPe1ul7Uj1j*+V2B1-A^?UxnB~YVLFPUn_L{cDiQRL zi^zK5J3$cqb_#%R0e+HT%y@{Wu750Y*^`sNC@?A_hh665_agFYYisM)zP`S5a)9#r z{Bl)&+#OY1*5rq$~S7q z04MsJ?KoqO3q|L<@s>E2{WP##RlBRz>Sul5r>m*#5T z_)@8)3l}atV2pX$O{6{MvZa7)RduQ7d3#r^SW(`*dGnbDTnOBIo*F5RVP`r{=>Tr6 z)oNYs?d`jkE?xT8_U+sCENtDPs;hv`c=lw%n}^q5e;wcVkEYY<|5nx6E~nfabJ^wr z*N={l9_Z}sERBqeL{kJj=6}?;7W=EKwZ_281nK0Nz!FtmRjbv;eBb|l7>3nJb~pv( zfKSD_BQ*)VQ!15CWV6|gs@mdKsx>yLIlwY&?Lp_DR3`&0B8bRi#+V-gMIZovAtJvP zkyk{dBqE1IWE>cG3!^U4C@~G24csgu?_{&tZKYCaLeO1ZUA9`Su77ez9BeM>~X4g6;mMMs8*ht;)NrK&>-X3iU9er~NbE^Uj5)V~r%kt|-kSa$E; zebw05SeL5as;aA8;R)h=47e{ghw7BBT7n>WH=oZxAtHx>Z>p*n z^SwU`JgllSjWIt24u9Nx@4aUK{{2_Fu&rLN2Gk0Lf}Vl70T>=02G|Ge>+S6wverJX zs$a6!{)r>B`X()Ofx%W_f3lxD@4U15*s)_R^@VlXa%9T4hhZ4jeBa;Zf{l(ulCK$9 zE+QGv^B5f+UE_Cy;B{tR4P#}m*4H|?0ivGw|_bBDXwAHiAd1d+4<(k z$jB)qwqU`6*{XVni<9d+{DwIf>I{NlES*mGiO7S%3kj#9MOE(yo&=t7gT>{4MdZNw zP`f@X6Qi=(P~Zh!#>1_r9BR4QKo`2Ke;9yl%Ur^N-6CCo{7@5Y=eLUeytBCx@oaf+CCX@NKh&(7F&+^{W zhBL_#5&1zXmD(^gG;~3s=CoX3AnenH|2ck_+`B{#%wg_JNWwb>oo|1 z&ApjNBR2bRTM zV-eX4jHEAg5Q`PON`i6+?FBXdz^O?h8;7Ba06Km~N zsv0-3*+irSP_)(_OJ9r`1VL~D80qQhDXZ$Q0E{u?XS+f@A5qn@#14z9w$%am^z^hv zQFK#6oqr>s-xzZueK999I5-$hW@9ggxR5eA#`~%pMUe;Go+#gMLM=^$iK;en z>MH~_#+(!pa=BcKs@@cHC+myU2ONYczVW!kq&XJI)ED7|1AYzg#|f8tr-&4SAUM{L zfJJ0Fr$4GWY>Y_*w@i?&c}`W|zfEjJmCkVb!+)AgtyWtMd^J(FS44s!2;Od3z|JHc zb>S&MDvF}NcK_d!s9R9gP4OSn4XDy(r=oLW$${Pp>b)T%{l#K&e=?KMP-2XE6<7~U zmNGsGJd;YLPJ3gLYBa#*aye>mZx2=V8ka{;$1)n0_nuV!40xnaC>%_-Z^Sc9Q&ZEP z@jCJGZ-~f?*4o=ew>ZUexa`sr0}`3-(Mg2x=@N=Bg&t@ z*Vl+@Z}!X7_Se)n@pGvC_@9%-T57P&&m6s3?fl-N&&GOg4K&%iy?5X4^zFd6zrURt z8B;tdB+t*7@_%QPN&iC{yw>+KJ5CJm54|~3O7ShcCzK3<3-`~3@9^Ij^XIksu}1&k z>z|p{SDL&u_#U1WP zDr)ja50Mz(ki!lm+;HCO3X37;c%tzd<09%h)`&%qD=Fq#-!9x}si&P939!hO#NqgT zEaBa^zuPS)x&02D83Pv!eB%4J`}vQ5=lc&Ax>q$5f^Xl~igCr4ra`Ni^pmp)NVs2X zN<`gT;(zdcp@05TU=tB6Z?0)$DSN8 zT{`vHTkm}gZaTQ~;M0RM#+Y%YnP-`G+HAAWu}GhlS6Ox0YOAlY<4$f<$h&UaZTCG+ zIHc0ar<{81wA0VHq}ok4-*W4<+it((d(^(7`fJpFkKA8JEqp~SzCb0%5BnZ94x4?u zM1K&R6vd2)#T;RjN6|?ZHo-ni`c6|_ZHPB%8wIH8?g=1=)e5x{_Y3= z{)OQ1ohGGuV%%ls)ouwh>e!ySJ4-X>n}1F!>jake0EH}CI2#UMcZah58R>W<6%G*x z;YTm$^nFShxwH+&_Upo|DFWZ-Lt|aD)o}IZ(j&h(f#jh0spZkBe} zGCyZ)C=&}z`fjq-ptO7~%(;4FK2~qpZIl^qCpF7RwNHP@y|DMH)b^W!br`@7&W)b%zX_`%o5TSe<215?WCkMy)LC zrS?FjWSfD}^cpz^hO(U3sDiahZ!;V>@~I=Yp^SzDGeE$=;JM1rt|77X_5I)+g{{JkAE7RZfZ6^6#elny>-o!QQ zma5II*U-gkqgjEINj3l?WWYGGU|BaypTvrlimudQN8(Li&Pu1`vAUbk^zkfvZtMDd zM;OXCR;-<7qnyd1eUDbJ?75QN+POCjo)nHT?ksr@cc84?SC7QIP6qjRQh(}GYyano zNRwPxok&3h=#W)lQa~f?v9f48?B%u@g>?02+5|OSqQYMQ=N{Lxz^K~pWGw>V0?Tt+ zO+@_w!iAD!`EDuBR+uMDCW>-7oH|*+%Rj39o(!&bKE0+Nw{7l}$PCu;j9E5=xS%E{ z-{aaonVg@K!~~O5yOknWfPaY09hTE5xAy~|PwTxcvf1CfkjsGxRA9C*Fv5{#zP66A z>b5kk+HOpB!3=xP#I+%%1wte$1kDC|Gb1v_3Ka*MLlV?#l7`H6@GD8YHmVLNzYBSh z3Ocaq&Lk$ZHge}8%&4WH)VbZbQ_mr-fx2_1z-u!5vQiBQ+oGS9fPZ>c|L#7moC4S< z?xRVHgD&$7iR%Z%G{ttRWq6H`8PYG13@y1p_a|3TVnM= zodb>%W!L>J)6jcUe=<twAzT zC0P4d;3nX@!5pj{d6qQNy6c%=Nr_khZO9-d146ktkphtuFX73V>IT({4M6wIb;~&D zffHNf@3qrvXa`~!pSnsLT(()oBC&3UVO$h^i6qA)L{pg0S%0j_CS?$^-SGBdm&SUh zy!4r>bzc-$U3EwD2;sm=OJS10sjmY+GTVZ(xag)s2%}v}+rU?q(yVX;rIaX(5SFXXje@_}bs)t8IShF*f%dSd$T$*^sjD}j zM@QGo+&j?*&8P2zxwC zPDo_X#v?&SxR`T?%31PSOB{@t4{>or*4zo_a8f*ipMQJNjZVvZJwupK=7Zb?JP+WR zbpibLL`;XeR%Mk#Z`2uKv`HcMs>NQNT1IDc$%@8FccN(O?LJn?%U5Wkz$vZt>dG@6 zaGL~A_lhBlvY&-n$Srt{`ULIk=VNGq?X$rYD9f4<8Xi6E3yHV_X~Oj@0v*sAOQnq{ z&;pRpj(=DHP++sv3gFat36B*?Y->mcW&}O6$`xJ5_$j81iK{PrIg!)X_7`AdJPkc~ zNIkUjA>R*p25@$?;E~*c%pw90%L7;gN^oh?@|?gbO^Ch#Bjh#wY-q2fW(;y5$!r&_ z#o+yp^rE^KuHdi4>NJPo*x)7f3G(%f#Uug1uzyBeBVq~q#oU`t(g-+3P^k%x-(nvGjq@TWC5u)#)# z-iDE3p6%dafeP52IRQM-B(|m}$tx5jc}0>GXoN!a@CG-(yA?`=8CfP#;2H797)exL zAb)+XJ9B6x^U5T0r#h%c2o_d;#c{_zm@)}7*Fd&34i1dULh4mwUZK+9yYff07i8Q) z$=N)}vY000Vwkf(1QOI#%rOaJEDmlCam5|Z4BXiQD@ansF`(=X-`OdvlkT`5xx4cx z`iMAT!w7Oj$da?DOiCJWL6M`Z(PmB6D}VOf3Bkg0Be^S_o>V#&;e#4UrB5MiZC?c& zpv1fV2WMW5;YjcLybtmxR_{UJd^!q9%y4USyIRr04X9;OK`bRngTdp3B=C=6qxsPR zpuax5RJ<3Z`FM=_!YY+u4s4Xl3~)3ceqactJT?f~D(K{rWilYHuKN+;BWOhJk$*5# z(IzL*6^<1yRDcj2j#%W_oeA(k%AChweMZ(fD%r}b%YzbUtte%=5$yXZNqA(ULhFaH zk4qM@`eH~dbzlO12H(*A$^%1?)aMIs2VfX8O}T6_HTW`i==*lj zo}uppS`&=#!Z%c7#xx#m044)KZhupbWumjmIJ|YtNSdQ+CqGKUWGrZVZ@f37yk-@0 zPKaFO8u{#nAqY}y5H13T{GmiPUc=)fEBV>|0Pv7eurh17FS6+X>VS@Z?gI{YupgGa z@E)_y5Ge(E93*~DEffw2x>XAS*m%0uA4)$VApt86$~h#0b$bK!FjF}T`hS-|2L~x6 zBn$De8x>)smFk@dm$CFMJnA{g2I7x732Hbj;$r9|1L-_4TqU@2aXxtmlp2W4j6Fg+ zDLOVz`m6@-5$-va>OlsgMWYcJpf|<&Ae54J!umfdFl zrAzU`NQC_E(qA=GY{)&JloCTRqfR11h$%rwa&_qYE&D0yf~HaekDeTOz#EJMH$XU% ziAZTO>Xa?fa3EsxOwurc2B!5lGBz%WaTTSL8 z{O39mXulO%4uCEg#-d)#0hqNgpg`%C1MAgUV;p2B--CGjXjmaWy- zG+T~vL#d!0C}2uWbxfdtIYy`rJa{aI;aQ-rq)0MJ`l8BFxX3YV#Bm(YC##ZCH2IKV z#sHWv%q~W&YJb(nz=W#z>G)8~@$G>a@`oxg>bA}c_M)nF2!L9S2$3NdJ> zt~}KG?bZFGH~#7s*hBev`HrW*12K0~j{cFb+&-k;P=B`wR2?bUoCaUmCVeHn5D+XI zrG$9{>mrhbQJa#~fw<@e`3y3-;v-N8hzO6?Ma6650~1~=O8Kr$iow|M=q6oZfzBX4 z^Cz z>mno&Tz|Z<@C%s0Mq~9C13&7Jt@CGOQ5_cqZOJie5DLattg9x?6HzEOX@F$F0*(T@ zWQ}%3L?pbi{5kps;eR})BfnV- zY?nVrIgS?M9^h7{mY~RZI@kqGCiPi?(N(kRuZ9*2A?$R}dmm_W!? z9hbrUkTcx%d^~uRlvCvHR%bjT<;nhRfOk73JkkcVGqwZi#({ zw7Fm>n=kVFevPF{{ZLBO=p`E#ZucTl&wrJSc1PpEa>N__ZO91h#A0S?u--i__2sC^ zi?7?eEvy{U3xx$g6`1e!EJTMcs!8qfWSd2ra$;(nf$V5R!hpHNsS?7j5$`ZLg?UO( zvRqcxacu-KI=o{9>~cUXY0C*wMSxgIgdKHyGLo;sN;su~dsuib%IKlzmZ}C=D}S?X zPTh`PrRoa$6!fL?&^W8XR~`u+Ng9GqiebxxHn=kWqf2OYXcCs_RO5;+U+7`T7CuY( zt*XnwtS_?kPI3Wm-uY_1_<=y8tgi6vNM}IN+cpU)si{i0u00Y`re~zM8pljd4U42t zs$bEeE###H=7i3`K2n&hsbG;~o_}}+>2k!_2RuG0hV+}r4!kCAX%cfv^Nl*(5XHf1 zWv=zQk~tSg8fjyMtbQHDLsccIhoZeHrCo}69um6_h~>|On5`05cV2r9SpSV8Z+d)S zQ{hAUoG>B=)`#l3<}03q!k)r^DstcqyzYQX&W@B}F)Zon0<=X@nKViscz>?}tA-dG z4GKn~gk6#VSi(;jY$ibq){ur==nzwsF$Jmy32U_gB6vyBK@S4>tbGFO85f!iw3{1@ zZ5S+S4H(U(+#X#JCWcbLe{MC~+m+=iN-c?B@>&;1d*I}N+zZ;0%BX~!(MAz{5eja| zg{5a)4)vMgfz$cS>cA`te}6J~Bs0be>xg(m3IgLu8zGa|Ll(@7MB=wS9e4C^PnJqv zN+eu9dA$;A&RN`Bz0E3Nki`AkY#rE1>@4ttK|~o=PxY@TSxhvvW4NJd{~Db?_c{O2 z!nubpvRE1W;n-x>ji?_od6*gmSaYwSWAooM2MRgc{aP z5yk7yek}ziHXjCpStcS@1y8kDAd(vkAP@A%+p&NU-4G6QGQSgTvP^c}9!8onuI=yd zXgAqD4TMiBs>%Ja@&jiTi7a}a$W7$z?ArmA=u5JXQ!$}1_6$NZKozeXw^|bGU=xC<34h3S9fG2Rzw5#HQFBp7ojB{+m{PC#roD?W1FDTIpO-fmsj0Ps zrhKHHNF}E+e)bMOZL9=oG{id^q>8Whb8mnNqMdPKd5TqNEd$V1pP2($q@9}5DN{n& zaV>E9(JE&3UrlshLA_hF2b2NtNRi@!2UL)N;ksMSLysG*>3Zu^ zxD5C=NphoMfNjF~B$q^rdiB^GuP$N2e%N%{O(X*}DTWhJ)E{kIFL6#3cS>FE6Q2R= zOl>2P(JUUe;i8oM9#MYP0cADkrpKaI^c5e&B`Ke`2}U|RAsaRxf*j|B`J7V=hHRzy zbN5H!Ib1+l{(pqDuNC&lSy($VnK7z(!{X8-e-^hqdc1?$K>izJMS|by)`l?4#!!*E z=q-?@1I3Bz$BaJNOnUVFe?4B{l;|C8I3)ZCgkebas$HmVkoR^K@r$w$h_lNp)?a;%RM&|DX}E6{7ma)?YLlM0)?Xjk+^5Bdk~y&uU3+NilHuqVzN7;dFgT-;4Od|U zx&;ZZ%%4*7SQC=Rac~7~zAcSV0u(kTQbR2+cxAlpDNOg|B2Qr|<0A^{I z5Px7BUJWo&4l7ld2{pZd+^4ehRcisQ0UN;!46{u=KcR;i!xBuWbwRkVc51vDk^l?x zgb$4f(h|q4FwK@cxQX5vANK*1&eufX@6Qc^3)LS1Dol^RVnnApHy9D1 z_E;rnOxr2=<_9T5I~JKG(1t7OMZ4=LSbw#!if749+0$RQE^3`Y_JhwGc}U3h#?_Eb zPz1Avn1E$QMdsp3g3~@}t!nXVYxZh!;0g#`%cUJ}=#qGIC`PsE1f0Y)U3z2NxthQqX1$0}TjXNjxl(+ez-@(|^8O zTI>TU$jN_wO1k!aPJFNa>XCAhFM|bYem!A0AUR5MseFChpq`Xp(l*A3WUo|}o)Ikg zYWY{~@O^uCJU0ThBLX+^aTOeEu0Lt`#=->z8mgJ1Lr(I;oA{sEkjKnt=_%228TAB$6Yi-xKRI!2d>OtS$WN~Y zB721f(zL+bl=a*fg*yTY`sfi5R0}nc@&j)0gODD7ztZ(zBg)jep&h$V5db z{hni*|FAjb{?%^QpTCCT3|W)36Iiud8E*|A=6@S3>f6as9X?ZL* ztq(PaO2yi!5zJzfi69!;yCj6MK0zJ*tjAcg+7|z061aaai>w5M-8QV~#0ycyYG9E` zdKz#~G&FJv)<nhqAhs@Ad*xKc~LrKxYX)} zsotYWpXWoCp0S;JLVt#&sd^Tm0VQ!3H?ioD#HW9k^!S7{3)(k5a_mSCJs!2SU8?O` zVrwSPP*)jy0BSPlZMlKJm+Bld5|fP+ht4EAGd7RusM!PIfHc}L_ZtOj2_Xnhe0pB_ zHGKyOiHGaD3oygLNe8CFc(hNVN12Yc{q-yoqK&YEFrW!<7=J4*Swdbi1@H*2dk=D> zft#KX6hEOQG9(JU;X9@_h>kV^X43Gwo-g}@h;sj#C$#?GUI;X!@KGH93)$8ygN4%~ zHvj+uBWXiJP)S2WAaHVTW@&6?004NLosh9g!%!54ztmDi)WzZ;4jEhvE_QOM3PG?C zV+E^InzUHVjej9YDZYVkq7TzS5PSn)K*8DnMh6G8c;WDW9M1XwdoMKHBr-*F4@_F- zUeFs}4zJwhH(E5=piMiBimd1Rr_`R$R~)M^9qUy;_vdNF<06tSRB4puB{&D-?!3$_ zt7zX2f+O*R*o{pZi=V{1v576)-M}QX=*QFEW^J5aoPXQCY6p)K`uMnvm@y@xq$5q$ zIi}0L)FUV4o`Oi4FjjA_nUWh(At#SR(Lkiz)Z}efdA?Fc@|eoYCfs4yQ{N-Vq`NlGc_$^V>vY~G%#X0En+fcFfA}OFf=n_Gh}6BGcl8x7#}1! zGB7tYWnwffGi5ksEi^SZG%YweH8(9XWjJ9mVKg~qVP<8M+ZZP#HezCCVm3BoEigAY zWi2!?H#aRfVl_7{FgZ42VKp&jVK-qglO`Ek4l*)0F*Y## zHaapeIx{mXFfy~l8*2!EZ;+id00002VoOIv0063uBQgL0010qNS#tmY3ljhU3ljkV znw%H_000McNlirueSad^gZEa<4bO1wgWnpw>WFU8GbZ8() zNlj2!fese{00Y%YL_t(o!|j-BY*a-Q$A71bt$^BUgtdGS5fBo8HJTWrK?z_MN(xeW zNd;0R1p$qH7^5FF7Dx;sAwY>S7&tSAbreq49Ot5c46t=XnnAGPHcHfsB;+edh?#-EuVR26VY z;$6*t#P>50KSNm{JpfmmUsXNCUP<)v%u_vxF)WmgLXI0jtJ(~a9oes_raJB3%35w8H?RbAKsC2Rln0zX9FG(3Hfek zE(>J5s;x_Hz~i)RXPN8hPozHpH`&#c1fDNLRUPL8*U@vE!{v!G+yD4Y^Ove2Y!r9V z6N~|Tw294Ly+fW*b&feMk9s%&-F)M>O$0VctvLmMz!j#MeKwA)`q&lCdu(ixs*o359cu1bU0C$<3QJNfQosO=X1SyYi*+(pRfvh^ zz@ls>^fG%WF<+!vrfca^pDLNJq3ka84PRxs+TAoUGi94#ru4RT?3!cUsweTGBu@2_ zn3hL>248OL9PwvdKzxKVs605}`-%yQAEoFCeM8svq9Co?j(c_!(}NiCl(atwx*QdRRn zWU4+MxkQ|8kPtn{pVpt~)u5-h2Yc5BOuz)p_VeNT_VeL-H16ej1MYh`Ty_msH`6lk zy2s$VMX~$S(fliV4l8Z;7<_xYb(90xeJ5O-nB7&xz1VdPEq#Lk9g2#*wDAh8W#I}` zs8WY|`ChJ@vbCM98%x6Rc_62(P$InmwsNefjiFBPevP3tuJ&jG) - From 996338141985c7a0184189e54ccd8beef02101a7 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 10 Jan 2022 16:03:01 +0100 Subject: [PATCH 090/130] Added swipe actions on chat message (reply / delete), improved chat message redraw condition --- CHANGELOG.md | 1 + .../chat/adapters/ChatMessagesListAdapter.kt | 6 +- .../main/chat/data/ChatMessageData.kt | 8 ++ .../chat/fragments/DetailChatRoomFragment.kt | 61 +++++++++----- .../viewmodels/ChatMessagesListViewModel.kt | 16 ++-- .../main/chat/viewmodels/ChatRoomViewModel.kt | 10 ++- .../java/org/linphone/core/CorePreferences.kt | 3 - .../linphone/utils/RecyclerViewSwipeUtils.kt | 81 +++++++++++++------ .../main/res/layout/chat_event_list_cell.xml | 20 ++--- .../res/layout/chat_room_detail_fragment.xml | 4 +- 10 files changed, 135 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f338611..16dc18a4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Group changes to describe their impact on the project, as follows: ### Added - Reply to chat message feature (with original message preview) +- Swipe action on chat messages to reply / delete - Voice recordings in chat feature - Allow video recording in chat file sharing - Unread messages indicator in chat conversation that separates read & unread messages diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt index 7eb6f214c..95f3322dd 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt @@ -452,7 +452,11 @@ private class ChatMessageDiffCallback : DiffUtil.ItemCallback() { newItem: EventLogData ): Boolean { return if (newItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { - newItem.eventLog.chatMessage?.state == ChatMessage.State.Displayed + val oldData = (oldItem.data as ChatMessageData) + val newData = (newItem.data as ChatMessageData) + val previous = oldData.hasPreviousMessage == newData.hasPreviousMessage + val next = oldData.hasNextMessage == newData.hasNextMessage + newItem.eventLog.chatMessage?.state == ChatMessage.State.Displayed && previous && next } else true } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt index 43265f98e..7cfcf1ba7 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt @@ -59,6 +59,9 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes val replyData = MutableLiveData() + var hasPreviousMessage = false + var hasNextMessage = false + private var countDownTimer: CountDownTimer? = null private val listener = object : ChatMessageListenerStub() { @@ -106,6 +109,11 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes } fun updateBubbleBackground(hasPrevious: Boolean, hasNext: Boolean) { + hasPreviousMessage = hasPrevious + hasNextMessage = hasNext + hideTime.value = false + hideAvatar.value = false + if (hasPrevious) { hideTime.value = true } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index d6473826b..335c99500 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -29,6 +29,7 @@ import android.provider.MediaStore import android.view.* import android.view.ViewTreeObserver.OnGlobalLayoutListener import android.widget.PopupWindow +import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.view.doOnPreDraw import androidx.databinding.DataBindingUtil @@ -226,32 +227,48 @@ class DetailChatRoomFragment : MasterFragment() - list.addAll(events.value.orEmpty()) - list.removeAt(position) - events.value = list + events.value.orEmpty().forEach(EventLogData::destroy) + events.value = getEvents() } fun deleteEventLogs(listToDelete: ArrayList) { - val list = arrayListOf() - list.addAll(events.value.orEmpty()) - for (eventLog in listToDelete) { LinphoneUtils.deleteFilesAttachedToEventLog(eventLog.eventLog) eventLog.eventLog.deleteFromDatabase() - list.remove(eventLog) } - events.value = list + events.value.orEmpty().forEach(EventLogData::destroy) + events.value = getEvents() } fun loadMoreData(totalItemsCount: Int) { @@ -248,6 +242,8 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() { LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage) chatRoom.deleteMessage(chatMessage) } + + events.value.orEmpty().forEach(EventLogData::destroy) events.value = getEvents() } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt index 396adee3e..d964c0b51 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt @@ -113,7 +113,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf override fun onContactsUpdated() { Log.i("[Chat Room] Contacts have changed") contactLookup() - lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory) + updateLastMessageToDisplay() } } @@ -199,7 +199,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) { Log.i("[Chat Room] Ephemeral message deleted, updated last message displayed") - lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory) + updateLastMessageToDisplay() } override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) { @@ -226,7 +226,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf contactLookup() updateParticipants() - lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory) + updateLastMessageToDisplay() callInProgress.value = chatRoom.core.callsNb > 0 updateRemotesComposing() @@ -276,6 +276,10 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf } } + private fun updateLastMessageToDisplay() { + lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory) + } + private fun formatLastMessage(msg: ChatMessage?): String { if (msg == null) return "" diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 549d37469..bc83c4e10 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -465,9 +465,6 @@ class CorePreferences constructor(private val context: Context) { val showAllRingtones: Boolean get() = config.getBool("app", "show_all_available_ringtones", false) - val allowSwipeActionOnChatMessage: Boolean - get() = config.getBool("app", "swipe_action_on_chat_messages", false) - /* Default values related */ val echoCancellerCalibration: Int diff --git a/app/src/main/java/org/linphone/utils/RecyclerViewSwipeUtils.kt b/app/src/main/java/org/linphone/utils/RecyclerViewSwipeUtils.kt index c1b4d9ff0..52808677a 100644 --- a/app/src/main/java/org/linphone/utils/RecyclerViewSwipeUtils.kt +++ b/app/src/main/java/org/linphone/utils/RecyclerViewSwipeUtils.kt @@ -84,26 +84,38 @@ private class RecyclerViewSwipeUtilsCallback( background.draw(canvas) } - val iconHorizontalMargin: Int = TypedValue.applyDimension( + val horizontalMargin: Int = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, configuration.iconMargin, recyclerView.context.resources.displayMetrics ).toInt() - var iconSize = 0 + var iconWidth = 0 - if (configuration.leftToRightAction.icon != 0 && dX > iconHorizontalMargin) { + if (configuration.leftToRightAction.icon != 0) { val icon = - ContextCompat.getDrawable(recyclerView.context, configuration.leftToRightAction.icon) - if (icon != null) { - iconSize = icon.intrinsicHeight - val halfIcon = iconSize / 2 + ContextCompat.getDrawable( + recyclerView.context, + configuration.leftToRightAction.icon + ) + iconWidth = icon?.intrinsicWidth ?: 0 + if (icon != null && dX > iconWidth) { + val halfIcon = icon.intrinsicHeight / 2 val top = viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon) + // Icon won't move past the swipe threshold, thus indicating to the user + // it has reached the required distance for swipe action to be done + val threshold = getSwipeThreshold(viewHolder) * viewHolder.itemView.right + val left = if (dX < threshold) { + viewHolder.itemView.left + dX.toInt() - iconWidth + } else { + viewHolder.itemView.left + threshold.toInt() - iconWidth + } + icon.setBounds( - viewHolder.itemView.left + iconHorizontalMargin, + left, top, - viewHolder.itemView.left + iconHorizontalMargin + icon.intrinsicWidth, + left + iconWidth, top + icon.intrinsicHeight ) @@ -116,7 +128,7 @@ private class RecyclerViewSwipeUtilsCallback( } } - if (configuration.leftToRightAction.text.isNotEmpty() && dX > iconHorizontalMargin + iconSize) { + if (configuration.leftToRightAction.text.isNotEmpty() && dX > horizontalMargin + iconWidth) { val textPaint = TextPaint() textPaint.isAntiAlias = true textPaint.textSize = TypedValue.applyDimension( @@ -127,9 +139,9 @@ private class RecyclerViewSwipeUtilsCallback( textPaint.color = configuration.leftToRightAction.textColor textPaint.typeface = configuration.actionTextFont - val margin = if (iconSize > 0) iconHorizontalMargin / 2 else 0 + val margin = if (iconWidth > 0) horizontalMargin / 2 else 0 val textX = - (viewHolder.itemView.left + iconHorizontalMargin + iconSize + margin).toFloat() + (viewHolder.itemView.left + horizontalMargin + iconWidth + margin).toFloat() val textY = (viewHolder.itemView.top + (viewHolder.itemView.bottom - viewHolder.itemView.top) / 2.0 + textPaint.textSize / 2).toFloat() canvas.drawText( @@ -158,30 +170,44 @@ private class RecyclerViewSwipeUtilsCallback( background.draw(canvas) } - val iconHorizontalMargin: Int = TypedValue.applyDimension( + val horizontalMargin: Int = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, configuration.iconMargin, recyclerView.context.resources.displayMetrics ).toInt() - var iconSize = 0 + var iconWidth = 0 var imageLeftBorder = viewHolder.itemView.right - if (configuration.rightToLeftAction.icon != 0 && dX < -iconHorizontalMargin) { + if (configuration.rightToLeftAction.icon != 0) { val icon = - ContextCompat.getDrawable(recyclerView.context, configuration.rightToLeftAction.icon) - if (icon != null) { - iconSize = icon.intrinsicHeight - val halfIcon = iconSize / 2 + ContextCompat.getDrawable( + recyclerView.context, + configuration.rightToLeftAction.icon + ) + iconWidth = icon?.intrinsicWidth ?: 0 + if (icon != null && dX < viewHolder.itemView.right - iconWidth) { + val halfIcon = icon.intrinsicHeight / 2 val top = viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon) - imageLeftBorder = - viewHolder.itemView.right - iconHorizontalMargin - halfIcon * 2 + + // Icon won't move past the swipe threshold, thus indicating to the user + // it has reached the required distance for swipe action to be done + val threshold = -(getSwipeThreshold(viewHolder) * viewHolder.itemView.right) + val right = if (dX > threshold) { + viewHolder.itemView.right + dX.toInt() + } else { + viewHolder.itemView.right + threshold.toInt() + } + imageLeftBorder = right - icon.intrinsicWidth + icon.setBounds( imageLeftBorder, top, - viewHolder.itemView.right - iconHorizontalMargin, + right, top + icon.intrinsicHeight ) + + @Suppress("DEPRECATION") if (configuration.rightToLeftAction.iconTint != 0) icon.setColorFilter( configuration.rightToLeftAction.iconTint, PorterDuff.Mode.SRC_IN @@ -189,7 +215,8 @@ private class RecyclerViewSwipeUtilsCallback( icon.draw(canvas) } } - if (configuration.rightToLeftAction.text.isNotEmpty() && dX < -iconHorizontalMargin - iconSize) { + + if (configuration.rightToLeftAction.text.isNotEmpty() && dX < -horizontalMargin - iconWidth) { val textPaint = TextPaint() textPaint.isAntiAlias = true textPaint.textSize = TypedValue.applyDimension( @@ -201,7 +228,7 @@ private class RecyclerViewSwipeUtilsCallback( textPaint.typeface = configuration.actionTextFont val margin = - if (imageLeftBorder == viewHolder.itemView.right) iconHorizontalMargin else iconHorizontalMargin / 2 + if (imageLeftBorder == viewHolder.itemView.right) horizontalMargin else horizontalMargin / 2 val textX = imageLeftBorder - textPaint.measureText(configuration.rightToLeftAction.text) - margin val textY = @@ -239,6 +266,8 @@ private class RecyclerViewSwipeUtilsCallback( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ): Int { + // Prevent swipe actions for a specific ViewHolder class if needed + // Used to allow swipe actions on chat messages but not events var dirFlags = direction if (direction and ItemTouchHelper.RIGHT != 0) { val classToPrevent = configuration.leftToRightAction.preventFor @@ -278,6 +307,10 @@ private class RecyclerViewSwipeUtilsCallback( } } + override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float { + return .33f // A third of the screen is required to validate swipe move (default is .5f) + } + override fun onChildDraw( canvas: Canvas, recyclerView: RecyclerView, diff --git a/app/src/main/res/layout/chat_event_list_cell.xml b/app/src/main/res/layout/chat_event_list_cell.xml index 9c0e2dd2c..172ce2d65 100644 --- a/app/src/main/res/layout/chat_event_list_cell.xml +++ b/app/src/main/res/layout/chat_event_list_cell.xml @@ -37,27 +37,27 @@ + android:paddingRight="5dp" + android:text="@{data.text + ' '}" + android:textColor="@{data.security || data.groupLeft ? @color/red_color : @color/light_grey_color}" + android:textSize="13sp" + android:textStyle="italic" /> diff --git a/app/src/main/res/layout/chat_room_detail_fragment.xml b/app/src/main/res/layout/chat_room_detail_fragment.xml index 0947d00fa..1f3bbd365 100644 --- a/app/src/main/res/layout/chat_room_detail_fragment.xml +++ b/app/src/main/res/layout/chat_room_detail_fragment.xml @@ -282,12 +282,12 @@ android:layout_height="match_parent" android:layout_above="@id/footer" android:layout_below="@+id/top_bar" - android:paddingBottom="20dp" - android:clipToPadding="false" android:cacheColorHint="@color/transparent_color" android:choiceMode="multipleChoice" + android:clipToPadding="false" android:divider="@android:color/transparent" android:listSelector="@color/transparent_color" + android:paddingBottom="20dp" android:transcriptMode="normal" /> Date: Tue, 11 Jan 2022 14:49:10 +0100 Subject: [PATCH 091/130] Allow to undo last chat message swipe removal --- .../linphone/activities/SnackBarActivity.kt | 1 + .../activities/assistant/AssistantActivity.kt | 9 ++++ .../linphone/activities/main/MainActivity.kt | 10 +++++ .../chat/fragments/DetailChatRoomFragment.kt | 43 ++++++++++++++----- app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 6 files changed, 56 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/SnackBarActivity.kt b/app/src/main/java/org/linphone/activities/SnackBarActivity.kt index 7eb57e548..b28d8e5d4 100644 --- a/app/src/main/java/org/linphone/activities/SnackBarActivity.kt +++ b/app/src/main/java/org/linphone/activities/SnackBarActivity.kt @@ -21,5 +21,6 @@ package org.linphone.activities interface SnackBarActivity { fun showSnackBar(resourceId: Int) + fun showSnackBar(resourceId: Int, action: Int, listener: () -> Unit) fun showSnackBar(message: String) } diff --git a/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt b/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt index 16ec6d044..52fd813f9 100644 --- a/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt +++ b/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt @@ -49,6 +49,15 @@ class AssistantActivity : GenericActivity(), SnackBarActivity { Snackbar.make(coordinator, resourceId, Snackbar.LENGTH_LONG).show() } + override fun showSnackBar(resourceId: Int, action: Int, listener: () -> Unit) { + Snackbar + .make(findViewById(R.id.coordinator), resourceId, Snackbar.LENGTH_LONG) + .setAction(action) { + listener() + } + .show() + } + override fun showSnackBar(message: String) { Snackbar.make(coordinator, message, Snackbar.LENGTH_LONG).show() } diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index ced990d1e..b955f86a1 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -181,6 +181,16 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin Snackbar.make(findViewById(R.id.coordinator), resourceId, Snackbar.LENGTH_LONG).show() } + override fun showSnackBar(resourceId: Int, action: Int, listener: () -> Unit) { + Snackbar + .make(findViewById(R.id.coordinator), resourceId, Snackbar.LENGTH_LONG) + .setAction(action) { + Log.i("[Snack Bar] Action listener triggered") + listener() + } + .show() + } + override fun showSnackBar(message: String) { Snackbar.make(findViewById(R.id.coordinator), message, Snackbar.LENGTH_LONG).show() } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 335c99500..8f65669c0 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -42,9 +42,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import java.io.File import java.lang.IllegalArgumentException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -256,15 +254,10 @@ class DetailChatRoomFragment : MasterFragment 0) { // Scroll to first unread message if any diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 831029ed3..0e59a879e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -630,4 +630,6 @@ Acheminer l\'audio vers l\'appareil bluetooth, s\'il existe Il aura la priorité sur le périphérique de sortie par défaut Sonnerie + Le message va être supprimé + Annuler \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 74c64b572..0d40304a3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -227,6 +227,8 @@ @string/chat_room_unread_message @string/chat_room_unread_messages + Message will be deleted + Abort No recordings From 891be0eb9bcc2446623afa40407ab6c49abe3f9f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 11 Jan 2022 15:40:29 +0100 Subject: [PATCH 092/130] Properly update last chat room message for when going back to chat rooms list --- .../main/chat/adapters/ChatMessagesListAdapter.kt | 10 ++++++++-- .../main/chat/fragments/DetailChatRoomFragment.kt | 15 ++++++++++++++- .../chat/fragments/MasterChatRoomsFragment.kt | 4 +++- .../main/chat/viewmodels/ChatRoomViewModel.kt | 2 +- .../chat/viewmodels/ChatRoomsListViewModel.kt | 4 +--- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt index 95f3322dd..2b08a173c 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt @@ -451,12 +451,18 @@ private class ChatMessageDiffCallback : DiffUtil.ItemCallback() { oldItem: EventLogData, newItem: EventLogData ): Boolean { - return if (newItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { + return if (oldItem.eventLog.type == EventLog.Type.ConferenceChatMessage && + newItem.eventLog.type == EventLog.Type.ConferenceChatMessage + ) { val oldData = (oldItem.data as ChatMessageData) val newData = (newItem.data as ChatMessageData) + val previous = oldData.hasPreviousMessage == newData.hasPreviousMessage val next = oldData.hasNextMessage == newData.hasNextMessage newItem.eventLog.chatMessage?.state == ChatMessage.State.Displayed && previous && next - } else true + } else { + oldItem.eventLog.type != EventLog.Type.ConferenceChatMessage && + newItem.eventLog.type != EventLog.Type.ConferenceChatMessage + } } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 8f65669c0..59a4b1dd9 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -175,10 +175,20 @@ class DetailChatRoomFragment : MasterFragment listViewModel.deleteMessage(chatMessage) + viewModel.updateLastMessageToDisplay() } } ) @@ -607,6 +618,7 @@ class DetailChatRoomFragment : MasterFragment() for (chatRoom in coreContext.core.chatRooms) { From fa33a7f29fb4f22dded0f246d2846f1747816504 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 14 Jan 2022 10:03:33 +0100 Subject: [PATCH 093/130] Fixed chat room not scrolling to bottom when receving/sending message if we are already on the bottom --- .../activities/main/chat/fragments/DetailChatRoomFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 59a4b1dd9..4c4870196 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -556,6 +556,7 @@ class DetailChatRoomFragment : MasterFragment Date: Fri, 14 Jan 2022 10:42:32 +0000 Subject: [PATCH 094/130] Translated using Weblate (Hungarian) --- app/src/main/res/values-hu/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 2a7dce777..81865a834 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -629,4 +629,7 @@ Ennek elsőbbsége lesz az alapértelmezett kimeneti eszközzel szemben Görgetés legalulra vagy az első olvasatlan üzenetre Hang átirányítása a Bluetooth-eszközre (ha van ilyen) + Csengőhang + Megszakítás + Üzenet törlésre kerül \ No newline at end of file From f666d2ee384abc531927659adf165197aceea612 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 14 Jan 2022 14:22:30 +0100 Subject: [PATCH 095/130] Updated dependencies + reduced small sip uri font size a bit --- app/build.gradle | 8 ++++---- app/src/main/res/values/styles.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b1f905003..8e29fe79e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.media:media:1.4.3' implementation 'androidx.fragment:fragment-ktx:1.4.0' implementation 'androidx.core:core-ktx:1.7.0' @@ -209,14 +209,14 @@ dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0-rc01" implementation "androidx.window:window:1.0.0-rc01" - implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.security:security-crypto-ktx:1.1.0-alpha03" - implementation 'androidx.core:core-splashscreen:1.0.0-alpha02' + implementation 'androidx.core:core-splashscreen:1.0.0-beta01' - implementation 'com.google.android.material:material:1.4.0' + implementation 'com.google.android.material:material:1.5.0' implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation 'androidx.emoji:emoji:1.1.0' diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 51da0d1da..276db38de 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -235,7 +235,7 @@