From 38ce817fdcc3f68ed6f179e347240483f1f8fbfd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Feb 2026 12:06:31 +0100 Subject: [PATCH 01/10] Upgrade io.element.android:opusencoder to 1.1.2. Part of #9086 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 784128722bf..5d339b64c27 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -95,7 +95,7 @@ ext.libs = [ 'hiltCompiler' : "com.google.dagger:hilt-compiler:$dagger" ], element : [ - 'opusencoder' : "io.element.android:opusencoder:1.1.0", + 'opusencoder' : "io.element.android:opusencoder:1.1.2", 'wysiwyg' : "io.element.android:wysiwyg:2.38.2" ], squareup : [ From d6dc373cfb159b487cc60a7d10b5c73477ad5505 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Feb 2026 09:52:28 +0100 Subject: [PATCH 02/10] version++ --- matrix-sdk-android/build.gradle | 2 +- vector-app/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index f1e3f30e978..8e522bdc036 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -62,7 +62,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.6.50\"" + buildConfigField "String", "SDK_VERSION", "\"1.6.52\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector-app/build.gradle b/vector-app/build.gradle index bdc705bd76c..192506f1426 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -37,7 +37,7 @@ ext.versionMinor = 6 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 50 +ext.versionPatch = 52 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 71fa4750673ba5658dc061f92070309d2241befa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 2 Feb 2026 15:13:37 +0100 Subject: [PATCH 03/10] Update io.element.android:wysiwyg 2.41.1-classic Part of #9086 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 5d339b64c27..3eaedb3617d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -96,7 +96,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.2", - 'wysiwyg' : "io.element.android:wysiwyg:2.38.2" + 'wysiwyg' : "io.element.android:wysiwyg:2.41.1-classic" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From b0c6d6600e094563fdfd703992bc6cab149fe03c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Feb 2026 14:20:38 +0100 Subject: [PATCH 04/10] Add a non-dismissable verification banner https://github.com/element-hq/element-meta/issues/2833 --- .../src/main/res/values/strings.xml | 2 + .../app/core/ui/views/VerifyDeviceBanner.kt | 38 ++++++++++++ .../features/home/HomeActivityViewModel.kt | 25 ++++---- .../app/features/home/HomeDetailViewModel.kt | 16 +++++ .../app/features/home/HomeDetailViewState.kt | 4 +- .../features/home/NewHomeDetailFragment.kt | 34 ++++++++-- .../main/res/drawable/banner_background.xml | 9 +++ .../res/layout/fragment_new_home_detail.xml | 28 ++++++--- .../res/layout/view_verify_session_banner.xml | 62 +++++++++++++++++++ 9 files changed, 193 insertions(+), 25 deletions(-) create mode 100755 vector/src/main/java/im/vector/app/core/ui/views/VerifyDeviceBanner.kt create mode 100644 vector/src/main/res/drawable/banner_background.xml create mode 100644 vector/src/main/res/layout/view_verify_session_banner.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index cf74250ab5e..6bcca18d660 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2717,6 +2717,8 @@ Review to ensure your account is safe Verify the new login accessing your account: %1$s + As of April 2026 unverified devices will not be able to send and receive messages. + Verify now Manually Verify by Text diff --git a/vector/src/main/java/im/vector/app/core/ui/views/VerifyDeviceBanner.kt b/vector/src/main/java/im/vector/app/core/ui/views/VerifyDeviceBanner.kt new file mode 100755 index 00000000000..249a3617ee6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/views/VerifyDeviceBanner.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package im.vector.app.core.ui.views + +import android.content.Context +import android.util.AttributeSet +import androidx.constraintlayout.widget.ConstraintLayout +import im.vector.app.R +import im.vector.app.databinding.ViewVerifySessionBannerBinding + +class VerifyDeviceBanner @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + var delegate: Delegate? = null + + init { + setupView() + } + + private fun setupView() { + inflate(context, R.layout.view_verify_session_banner, this) + val views = ViewVerifySessionBannerBinding.bind(this) + views.learnMore.setOnClickListener { delegate?.onVerificationLearnMoreClicked() } + views.verifyButton.setOnClickListener { delegate?.onVerifySession() } + } + + interface Delegate { + fun onVerificationLearnMoreClicked() + fun onVerifySession() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index ca17b95a48c..473d73ab438 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -362,11 +362,13 @@ class HomeActivityViewModel @AssistedInject constructor( // cross signing keys have been reset // Trigger a popup to re-verify // Note: user can be unknown in case of logout - session.getUserOrDefault(session.myUserId) - .toMatrixItem() - .let { user -> - _viewEvents.post(HomeActivityViewEvents.OnCrossSignedInvalidated(user)) - } + + // This is now handled by the banner in the HomeFragment + // session.getUserOrDefault(session.myUserId) + // .toMatrixItem() + // .let { user -> + // _viewEvents.post(HomeActivityViewEvents.OnCrossSignedInvalidated(user)) + // } } } @@ -441,12 +443,13 @@ class HomeActivityViewModel @AssistedInject constructor( val is4Ssetup = session.sharedSecretStorageService().isRecoverySetup() if (hasTargetDeviceToVerifyAgainst || is4Ssetup) { // New session - _viewEvents.post( - HomeActivityViewEvents.CurrentSessionNotVerified( - session.getUserOrDefault(session.myUserId).toMatrixItem(), - vectorPreferences.isOnRustCrypto() && vectorPreferences.hadExistingLegacyData() - ) - ) + // This is now handled by the banner in the HomeFragment + // _viewEvents.post( + // HomeActivityViewEvents.CurrentSessionNotVerified( + // session.getUserOrDefault(session.myUserId).toMatrixItem(), + // vectorPreferences.isOnRustCrypto() && vectorPreferences.hadExistingLegacyData() + // ) + // ) } else { _viewEvents.post( HomeActivityViewEvents.CurrentSessionCannotBeVerified( diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 0a720f29d3d..5cc55f2e781 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -93,6 +93,7 @@ class HomeDetailViewModel @AssistedInject constructor( observeRoomSummaries() updatePstnSupportFlag() observeDataStore() + observeCrossSigningState() callManager.addProtocolsCheckerListener(this) session.flow().liveUser(session.myUserId).execute { copy( @@ -101,6 +102,21 @@ class HomeDetailViewModel @AssistedInject constructor( } } + private fun observeCrossSigningState() { + session + .flow() + .liveCrossSigningInfo(session.myUserId) + .onEach { info -> + val isVerified = info.getOrNull()?.isTrusted() ?: false + setState { + copy( + isSessionVerified = isVerified, + ) + } + } + .launchIn(viewModelScope) + } + private fun observeDataStore() { vectorDataStore.pushCounterFlow.setOnEach { nbOfPush -> copy( diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt index 7b9284afc1d..352f378d5d8 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt @@ -33,7 +33,9 @@ data class HomeDetailViewState( val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState? = null, val pushCounter: Int = 0, val pstnSupportFlag: Boolean = false, - val forceDialPadTab: Boolean = false + val forceDialPadTab: Boolean = false, + // Set to true by default to avoid glitch + val isSessionVerified: Boolean = true, ) : MavericksState { val showDialPadTab = forceDialPadTab || pstnSupportFlag } diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index fbc3604e58e..082e8b65244 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -14,6 +14,7 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel @@ -33,6 +34,8 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.CurrentCallsViewPresenter import im.vector.app.core.ui.views.KeysBackupBanner +import im.vector.app.core.ui.views.VerifyDeviceBanner +import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.databinding.FragmentNewHomeDetailBinding import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.call.VectorCallActivity @@ -66,7 +69,8 @@ class NewHomeDetailFragment : KeysBackupBanner.Delegate, CurrentCallsView.Callback, OnBackPressed, - VectorMenuProvider { + VectorMenuProvider, + VerifyDeviceBanner.Delegate { @Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var colorProvider: ColorProvider @@ -132,6 +136,7 @@ class NewHomeDetailFragment : sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java) setupToolbar() setupKeysBackupBanner() + setupVerificationBanner() setupActiveCallView() setupDebugButton() setupFabs() @@ -323,6 +328,10 @@ class NewHomeDetailFragment : views.homeKeysBackupBanner.delegate = this } + private fun setupVerificationBanner() { + views.homeVerifyDevice.delegate = this + } + private fun setupActiveCallView() { currentCallsViewPresenter.bind(views.currentCallsView, this) } @@ -355,9 +364,9 @@ class NewHomeDetailFragment : }) } -/* ========================================================================================== - * KeysBackupBanner Listener - * ========================================================================================== */ + /* ========================================================================================== + * KeysBackupBanner Listener + * ========================================================================================== */ override fun onCloseClicked() { serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerClosed) @@ -378,7 +387,7 @@ class NewHomeDetailFragment : it.pushCounter, vectorPreferences.developerShowDebugInfo() ) - + views.homeVerifyDevice.isGone = it.isSessionVerified refreshAvatar() hasUnreadRooms = it.hasUnreadMessages } @@ -419,6 +428,21 @@ class NewHomeDetailFragment : private fun SpaceStateHandler.isRoot() = getSpaceBackstack().isEmpty() + override fun onVerificationLearnMoreClicked() { + openUrlInChromeCustomTab( + context = requireContext(), + session = null, + url = "https://docs.element.io/latest/element-support/device-verification/how-to-verify-devices", + ) + } + + override fun onVerifySession() { + (activity as? VectorBaseActivity<*>)?.let { vectorBaseActivity -> + vectorBaseActivity.navigator + .requestSelfSessionVerification(vectorBaseActivity) + } + } + companion object { private const val HOME_ROOM_LIST_FRAGMENT_TAG = "TAG_HOME_ROOM_LIST" } diff --git a/vector/src/main/res/drawable/banner_background.xml b/vector/src/main/res/drawable/banner_background.xml new file mode 100644 index 00000000000..d66d089d918 --- /dev/null +++ b/vector/src/main/res/drawable/banner_background.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_new_home_detail.xml b/vector/src/main/res/layout/fragment_new_home_detail.xml index 32d1dfc5d21..4621156a77e 100644 --- a/vector/src/main/res/layout/fragment_new_home_detail.xml +++ b/vector/src/main/res/layout/fragment_new_home_detail.xml @@ -44,12 +44,11 @@ + android:layout_height="wrap_content"> @@ -89,13 +88,26 @@ - + android:orientation="vertical" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> + + + + + + diff --git a/vector/src/main/res/layout/view_verify_session_banner.xml b/vector/src/main/res/layout/view_verify_session_banner.xml new file mode 100644 index 00000000000..e461e4deff6 --- /dev/null +++ b/vector/src/main/res/layout/view_verify_session_banner.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + +