From e2aa66e4c20a47fc953f2f8eeed0e22961e46e55 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 29 Aug 2022 17:23:07 +0200 Subject: [PATCH 01/28] Adding changelog entry --- changelog.d/6961.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6961.wip diff --git a/changelog.d/6961.wip b/changelog.d/6961.wip new file mode 100644 index 0000000000..2d271da8c1 --- /dev/null +++ b/changelog.d/6961.wip @@ -0,0 +1 @@ +[Devices Management] Session overview screen From 969663786b0f86f85601be1fbbbd3d8e66ac0c1e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 29 Aug 2022 17:30:59 +0200 Subject: [PATCH 02/28] Renaming header list view to be consistent --- .../stylable_devices_list_header_view.xml | 2 +- ...eaderView.kt => SessionsListHeaderView.kt} | 20 +++++++++---------- .../res/layout/fragment_settings_devices.xml | 6 +++--- ...ader.xml => view_sessions_list_header.xml} | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) rename vector/src/main/java/im/vector/app/features/settings/devices/v2/list/{DevicesListHeaderView.kt => SessionsListHeaderView.kt} (74%) rename vector/src/main/res/layout/{view_devices_list_header.xml => view_sessions_list_header.xml} (83%) diff --git a/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml b/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml index f0807f89c6..97e0290815 100644 --- a/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml +++ b/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml @@ -1,7 +1,7 @@ - + diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/DevicesListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt similarity index 74% rename from vector/src/main/java/im/vector/app/features/settings/devices/v2/list/DevicesListHeaderView.kt rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt index d6c7dbe273..547ed93f24 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/DevicesListHeaderView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt @@ -25,15 +25,15 @@ import androidx.core.content.res.use import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.setTextWithColoredPart -import im.vector.app.databinding.ViewDevicesListHeaderBinding +import im.vector.app.databinding.ViewSessionsListHeaderBinding -class DevicesListHeaderView @JvmOverloads constructor( +class SessionsListHeaderView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { - private val binding = ViewDevicesListHeaderBinding.inflate( + private val binding = ViewSessionsListHeaderBinding.inflate( LayoutInflater.from(context), this ) @@ -43,7 +43,7 @@ class DevicesListHeaderView @JvmOverloads constructor( init { context.obtainStyledAttributes( attrs, - R.styleable.DevicesListHeaderView, + R.styleable.SessionsListHeaderView, 0, 0 ).use { @@ -53,14 +53,14 @@ class DevicesListHeaderView @JvmOverloads constructor( } private fun setTitle(typedArray: TypedArray) { - val title = typedArray.getString(R.styleable.DevicesListHeaderView_devicesListHeaderTitle) - binding.devicesListHeaderTitle.text = title + val title = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderTitle) + binding.sessionsListHeaderTitle.text = title } private fun setDescription(typedArray: TypedArray) { - val description = typedArray.getString(R.styleable.DevicesListHeaderView_devicesListHeaderDescription) + val description = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderDescription) if (description.isNullOrEmpty()) { - binding.devicesListHeaderDescription.isVisible = false + binding.sessionsListHeaderDescription.isVisible = false return } @@ -70,8 +70,8 @@ class DevicesListHeaderView @JvmOverloads constructor( stringBuilder.append(" ") stringBuilder.append(learnMore) - binding.devicesListHeaderDescription.isVisible = true - binding.devicesListHeaderDescription.setTextWithColoredPart( + binding.sessionsListHeaderDescription.isVisible = true + binding.sessionsListHeaderDescription.setTextWithColoredPart( fullText = stringBuilder.toString(), coloredPart = learnMore, underline = false diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml index 6710f345ce..a289bda735 100644 --- a/vector/src/main/res/layout/fragment_settings_devices.xml +++ b/vector/src/main/res/layout/fragment_settings_devices.xml @@ -56,7 +56,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/deviceListInactiveSessionsRecommendation" /> - - - From ebf707dca9a26346450e28c6ba5470916a232224 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 29 Aug 2022 18:29:12 +0200 Subject: [PATCH 03/28] Navigation from current session --- .../src/main/res/values/strings.xml | 2 + vector/src/main/AndroidManifest.xml | 1 + .../app/core/di/MavericksViewModelModule.kt | 6 +++ .../v2/VectorSettingsDevicesFragment.kt | 26 ++++++++- .../v2/VectorSettingsDevicesViewNavigator.kt | 29 ++++++++++ .../devices/v2/list/CurrentSessionView.kt | 2 + .../v2/overview/SessionOverviewAction.kt | 21 ++++++++ .../v2/overview/SessionOverviewActivity.kt | 52 ++++++++++++++++++ .../v2/overview/SessionOverviewArgs.kt | 25 +++++++++ .../v2/overview/SessionOverviewFragment.kt | 52 ++++++++++++++++++ .../v2/overview/SessionOverviewState.kt | 28 ++++++++++ .../v2/overview/SessionOverviewViewModel.kt | 53 +++++++++++++++++++ .../res/layout/fragment_settings_devices.xml | 2 +- .../fragment_settings_session_overview.xml | 6 +++ 14 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt create mode 100644 vector/src/main/res/layout/fragment_settings_session_overview.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 8c2af01e52..823a26df74 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3248,6 +3248,8 @@ Consider signing out from old sessions (%1$d day or more) that you don’t use anymore. Consider signing out from old sessions (%1$d days or more) that you don’t use anymore. + Current Session + Session %s\nis looking a little empty. diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index c4022576c3..cd2fd52b32 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -339,6 +339,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index a40aeaaa15..40484f57e8 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -88,6 +88,7 @@ import im.vector.app.features.settings.account.deactivation.DeactivateAccountVie import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewModel import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel import im.vector.app.features.settings.devices.DevicesViewModel +import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel import im.vector.app.features.settings.devtools.AccountDataViewModel import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel import im.vector.app.features.settings.devtools.KeyRequestListViewModel @@ -630,4 +631,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(ReleaseNotesViewModel::class) fun releaseNotesViewModel(factory: ReleaseNotesViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(SessionOverviewViewModel::class) + fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 78b8c66f9c..2adf7969bf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -42,6 +42,7 @@ import im.vector.app.features.settings.devices.DevicesViewEvents import im.vector.app.features.settings.devices.DevicesViewModel import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState +import javax.inject.Inject /** * Display the list of the user's devices and sessions. @@ -50,6 +51,8 @@ import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationVie class VectorSettingsDevicesFragment : VectorBaseFragment() { + @Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator + private val viewModel: DevicesViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding { @@ -72,10 +75,10 @@ class VectorSettingsDevicesFragment : initLearnMoreButtons() initWaitingView() - observerViewEvents() + observeViewEvents() } - private fun observerViewEvents() { + private fun observeViewEvents() { viewModel.observeViewEvents { when (it) { is DevicesViewEvents.Loading -> showLoading(it.message) @@ -197,15 +200,34 @@ class VectorSettingsDevicesFragment : views.deviceListHeaderCurrentSession.isVisible = true views.deviceListCurrentSession.isVisible = true views.deviceListCurrentSession.render(it) + views.deviceListCurrentSession.debouncedClicks { + currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) } + } + views.deviceListCurrentSession.viewDetailsButton.debouncedClicks { + currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) } + } } ?: run { hideCurrentSessionView() } } + private fun navigateToSessionOverview(sessionId: String) { + viewNavigator.navigateToSessionOverview( + context = requireActivity(), + sessionId = sessionId + ) + } + private fun hideCurrentSessionView() { views.deviceListHeaderCurrentSession.isVisible = false views.deviceListCurrentSession.isVisible = false views.deviceListDividerCurrentSession.isVisible = false + views.deviceListCurrentSession.debouncedClicks { + // do nothing + } + views.deviceListCurrentSession.viewDetailsButton.debouncedClicks { + // do nothing + } } private fun handleRequestStatus(unIgnoreRequest: Async) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt new file mode 100644 index 0000000000..0e5cb87d7b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2 + +import android.content.Context +import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity +import javax.inject.Inject + +// TODO add unit tests +class VectorSettingsDevicesViewNavigator @Inject constructor() { + + fun navigateToSessionOverview(context: Context, sessionId: String) { + context.startActivity(SessionOverviewActivity.newIntent(context, sessionId)) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt index d6f81f4f79..1ce035931f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt @@ -39,6 +39,8 @@ class CurrentSessionView @JvmOverloads constructor( views = ViewCurrentSessionBinding.bind(this) } + val viewDetailsButton = views.currentSessionViewDetailsButton + fun render(currentDeviceInfo: DeviceFullInfo) { renderDeviceInfo(currentDeviceInfo.deviceInfo.displayName.orEmpty()) renderVerificationStatus(currentDeviceInfo.trustLevelForShield) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt new file mode 100644 index 0000000000..c028c08ec4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class SessionOverviewAction : VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt new file mode 100644 index 0000000000..a663c0ff2a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.airbnb.mvrx.Mavericks +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.SimpleFragmentActivity + +/** + * Display the overview info about a Session. + */ +@AndroidEntryPoint +class SessionOverviewActivity : SimpleFragmentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (isFirstCreation()) { + addFragment( + container = views.container, + fragmentClass = SessionOverviewFragment::class.java, + params = intent.getParcelableExtra(Mavericks.KEY_ARG) + ) + } + } + + companion object { + fun newIntent(context: Context, sessionId: String): Intent { + return Intent(context, SessionOverviewActivity::class.java).apply { + putExtra(Mavericks.KEY_ARG, SessionOverviewArgs(sessionId)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt new file mode 100644 index 0000000000..87ea883362 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class SessionOverviewArgs( + val sessionId: String +) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt new file mode 100644 index 0000000000..1b8b231a5c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentSettingsSessionOverviewBinding + +/** + * Display the overview info about a Session. + */ +@AndroidEntryPoint +class SessionOverviewFragment : + VectorBaseFragment() { + + private val viewModel: SessionOverviewViewModel by fragmentViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsSessionOverviewBinding { + return FragmentSettingsSessionOverviewBinding.inflate(inflater, container, false) + } + + override fun invalidate() = withState(viewModel) { state -> + updateToolbar(state.isCurrentSession) + } + + private fun updateToolbar(isCurrentSession: Boolean) { + val titleResId = if (isCurrentSession) R.string.device_manager_current_session_title else R.string.device_manager_session_title + (activity as? AppCompatActivity) + ?.supportActionBar + ?.setTitle(titleResId) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt new file mode 100644 index 0000000000..d91d6a82ce --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import com.airbnb.mvrx.MavericksState + +data class SessionOverviewState( + val sessionId: String, + val isCurrentSession: Boolean = false, +) : MavericksState { + constructor(args: SessionOverviewArgs) : this( + sessionId = args.sessionId + ) +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt new file mode 100644 index 0000000000..a95cc1a49b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel +import org.matrix.android.sdk.api.session.Session + +class SessionOverviewViewModel @AssistedInject constructor( + @Assisted val initialState: SessionOverviewState, + session: Session, +) : VectorViewModel(initialState) { + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: SessionOverviewState): SessionOverviewViewModel + } + + init { + val currentSessionId = session.sessionParams.deviceId.orEmpty() + setState { + copy( + isCurrentSession = sessionId.isNotEmpty() && sessionId == currentSessionId + ) + } + } + + override fun handle(action: SessionOverviewAction) { + TODO("Implement when adding the first action") + } +} diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml index a289bda735..b4f47302e1 100644 --- a/vector/src/main/res/layout/fragment_settings_devices.xml +++ b/vector/src/main/res/layout/fragment_settings_devices.xml @@ -61,7 +61,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" app:devicesListHeaderDescription="" - app:devicesListHeaderTitle="@string/device_manager_header_section_current_session" + app:devicesListHeaderTitle="@string/device_manager_current_session_title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" /> diff --git a/vector/src/main/res/layout/fragment_settings_session_overview.xml b/vector/src/main/res/layout/fragment_settings_session_overview.xml new file mode 100644 index 0000000000..1354408486 --- /dev/null +++ b/vector/src/main/res/layout/fragment_settings_session_overview.xml @@ -0,0 +1,6 @@ + + + + From 26dbd3171743d10dea6f3badf2ea9e34246c99ad Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 30 Aug 2022 15:03:07 +0200 Subject: [PATCH 04/28] Unit tests for navigator --- .../v2/VectorSettingsDevicesViewNavigator.kt | 1 - .../VectorSettingsDevicesViewNavigatorTest.kt | 65 +++++++++++++++++++ .../im/vector/app/test/fakes/FakeContext.kt | 7 ++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt index 0e5cb87d7b..25c971aacb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt @@ -20,7 +20,6 @@ import android.content.Context import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity import javax.inject.Inject -// TODO add unit tests class VectorSettingsDevicesViewNavigator @Inject constructor() { fun navigateToSessionOverview(context: Context, sessionId: String) { diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt new file mode 100644 index 0000000000..2a4c53f34f --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2 + +import android.content.Intent +import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity +import im.vector.app.test.fakes.FakeContext +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Test + +private const val A_SESSION_ID = "session_id" + +class VectorSettingsDevicesViewNavigatorTest { + + private val context = FakeContext() + private val vectorSettingsDevicesViewNavigator = VectorSettingsDevicesViewNavigator() + + @Before + fun setUp() { + mockkObject(SessionOverviewActivity.Companion) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a session id when navigating to overview then it starts the correct activity`() { + val intent = givenIntentForSessionOverview(A_SESSION_ID) + context.givenStartActivity(intent) + + vectorSettingsDevicesViewNavigator.navigateToSessionOverview(context.instance, A_SESSION_ID) + + verify { + context.instance.startActivity(intent) + } + } + + private fun givenIntentForSessionOverview(sessionId: String): Intent { + val intent = mockk() + every { SessionOverviewActivity.newIntent(context.instance, sessionId) } returns intent + return intent + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index 329ac1bdae..d74ebcb678 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -18,11 +18,14 @@ package im.vector.app.test.fakes import android.content.ContentResolver import android.content.Context +import android.content.Intent import android.net.ConnectivityManager import android.net.Uri import android.os.ParcelFileDescriptor import io.mockk.every +import io.mockk.just import io.mockk.mockk +import io.mockk.runs import java.io.OutputStream class FakeContext( @@ -67,4 +70,8 @@ class FakeContext( connectivityManager.givenHasActiveConnection() givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance) } + + fun givenStartActivity(intent: Intent) { + every { instance.startActivity(intent) } just runs + } } From 3eb29b46615014e7215a526422fd334aef6206d4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 30 Aug 2022 15:39:14 +0200 Subject: [PATCH 05/28] Renaming view state --- .../devices/v2/overview/SessionOverviewViewModel.kt | 10 +++++----- ...ionOverviewState.kt => SessionOverviewViewState.kt} | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/{SessionOverviewState.kt => SessionOverviewViewState.kt} (96%) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt index a95cc1a49b..f55da2819f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -27,15 +27,15 @@ import im.vector.app.core.platform.VectorViewModel import org.matrix.android.sdk.api.session.Session class SessionOverviewViewModel @AssistedInject constructor( - @Assisted val initialState: SessionOverviewState, + @Assisted val initialState: SessionOverviewViewState, session: Session, -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: SessionOverviewState): SessionOverviewViewModel + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: SessionOverviewViewState): SessionOverviewViewModel } init { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt similarity index 96% rename from vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt index d91d6a82ce..e839348800 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview import com.airbnb.mvrx.MavericksState -data class SessionOverviewState( +data class SessionOverviewViewState( val sessionId: String, val isCurrentSession: Boolean = false, ) : MavericksState { From 2a599d9760fad3af70035a5c0394bfc350ff88b0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 30 Aug 2022 16:09:03 +0200 Subject: [PATCH 06/28] Small renamings/reorganization in CryptoService --- .../matrix/android/sdk/flow/FlowSession.kt | 2 +- .../sdk/internal/crypto/E2eeSanityTests.kt | 4 ++-- .../crypto/crosssigning/XSigningTest.kt | 2 +- .../internal/crypto/verification/SASTest.kt | 4 ++-- .../sdk/api/session/crypto/CryptoService.kt | 24 +++++++++---------- .../internal/crypto/DefaultCryptoService.kt | 22 ++++++++--------- .../helper/MessageInformationDataFactory.kt | 2 +- .../VectorSettingsSecurityPrivacyFragment.kt | 2 +- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt index f22cfa369a..80ed311901 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -72,7 +72,7 @@ class FlowSession(private val session: Session) { } fun liveMyDevicesInfo(): Flow> { - return session.cryptoService().getLiveMyDevicesInfo().asFlow() + return session.cryptoService().getMyDevicesInfoLive().asFlow() .startWith(session.coroutineDispatchers.io) { session.cryptoService().getMyDevicesInfo() } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index 251c13ccbf..f883295495 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -676,8 +676,8 @@ class E2eeSanityTests : InstrumentedTest { assertEquals("Decimal code should have matched", oldCode, newCode) // Assert that devices are verified - val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId) - val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId) + val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId) + val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId) Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified) Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index 8cb38ddc87..ef3fdfeeda 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -193,7 +193,7 @@ class XSigningTest : InstrumentedTest { fail("Bob should see the new device") } - val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId) + val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobSecondDeviceId) assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) // Manually mark it as trusted from first session diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index c2e74abc59..1bffbeeeaa 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -521,9 +521,9 @@ class SASTest : InstrumentedTest { testHelper.await(bobSASLatch) // Assert that devices are verified - val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId) + val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId) val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = - bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) + bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index a5e05f69e0..ee5fe20d07 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -113,7 +113,17 @@ interface CryptoService { fun setRoomBlacklistUnverifiedDevices(roomId: String) - fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? + fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? + + fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback) + + fun getCryptoDeviceInfo(userId: String): List + + fun getLiveCryptoDeviceInfo(): LiveData> + + fun getLiveCryptoDeviceInfo(userId: String): LiveData> + + fun getLiveCryptoDeviceInfo(userIds: List): LiveData> fun requestRoomKeyForEvent(event: Event) @@ -127,9 +137,7 @@ interface CryptoService { fun getMyDevicesInfo(): List - fun getLiveMyDevicesInfo(): LiveData> - - fun getDeviceInfo(deviceId: String, callback: MatrixCallback) + fun getMyDevicesInfoLive(): LiveData> fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int @@ -156,14 +164,6 @@ interface CryptoService { fun downloadKeys(userIds: List, forceDownload: Boolean, callback: MatrixCallback>) - fun getCryptoDeviceInfo(userId: String): List - - fun getLiveCryptoDeviceInfo(): LiveData> - - fun getLiveCryptoDeviceInfo(userId: String): LiveData> - - fun getLiveCryptoDeviceInfo(userIds: List): LiveData> - fun addNewSessionListener(newSessionListener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 35c066dea8..739f86e659 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -273,7 +273,7 @@ internal class DefaultCryptoService @Inject constructor( .executeBy(taskExecutor) } - override fun getLiveMyDevicesInfo(): LiveData> { + override fun getMyDevicesInfoLive(): LiveData> { return cryptoStore.getLiveMyDevicesInfo() } @@ -281,15 +281,6 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getMyDevicesInfo() } - override fun getDeviceInfo(deviceId: String, callback: MatrixCallback) { - getDeviceInfoTask - .configureWith(GetDeviceInfoTask.Params(deviceId)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - } - .executeBy(taskExecutor) - } - override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int { return cryptoStore.inboundGroupSessionsCount(onlyBackedUp) } @@ -513,7 +504,7 @@ internal class DefaultCryptoService @Inject constructor( * @param userId the user id * @param deviceId the device id */ - override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { + override fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) { cryptoStore.getUserDevice(userId, deviceId) } else { @@ -521,6 +512,15 @@ internal class DefaultCryptoService @Inject constructor( } } + override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback) { + getDeviceInfoTask + .configureWith(GetDeviceInfoTask.Params(deviceId)) { + this.executionThread = TaskThread.CRYPTO + this.callback = callback + } + .executeBy(taskExecutor) + } + override fun getCryptoDeviceInfo(userId: String): List { return cryptoStore.getUserDeviceList(userId).orEmpty() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 6d94837f88..b711bf37bd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -162,7 +162,7 @@ class MessageInformationDataFactory @Inject constructor( .toModel() ?.deviceId ?.let { deviceId -> - session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId) + session.cryptoService().getCryptoDeviceInfo(event.root.senderId ?: "", deviceId) } when { sendingDevice == null -> { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 2b4d376f55..ecb1779a4a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -585,7 +585,7 @@ class VectorSettingsSecurityPrivacyFragment : } // crypto section: device key (fingerprint) - val deviceInfo = session.cryptoService().getDeviceInfo(userId, deviceId) + val deviceInfo = session.cryptoService().getCryptoDeviceInfo(userId, deviceId) val fingerprint = deviceInfo?.fingerprint() if (fingerprint?.isNotEmpty() == true) { From f74a0b090103c47b084ac6a2550d432309744f64 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 30 Aug 2022 16:32:32 +0200 Subject: [PATCH 07/28] Adding a method to retrieve livedata of device info for a given device id --- .../sdk/api/session/crypto/CryptoService.kt | 3 ++ .../internal/crypto/DefaultCryptoService.kt | 5 +++ .../internal/crypto/store/IMXCryptoStore.kt | 2 ++ .../crypto/store/db/RealmCryptoStore.kt | 26 +++++++++----- .../MyDeviceLastSeenInfoEntityMapper.kt | 34 +++++++++++++++++++ 5 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index ee5fe20d07..4f6a1fa02f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.SessionInfo interface CryptoService { @@ -139,6 +140,8 @@ interface CryptoService { fun getMyDevicesInfoLive(): LiveData> + fun getMyDevicesInfoLive(deviceId: String): LiveData> + fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int fun isRoomEncrypted(roomId: String): Boolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 739f86e659..39866163ce 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -73,6 +73,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityConten import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.shouldShareHistory import org.matrix.android.sdk.api.session.sync.model.SyncResponse +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting @@ -277,6 +278,10 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getLiveMyDevicesInfo() } + override fun getMyDevicesInfoLive(deviceId: String): LiveData> { + return cryptoStore.getLiveMyDevicesInfo(deviceId) + } + override fun getMyDevicesInfo(): List { return cryptoStore.getMyDevicesInfo() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 0413fc730c..3aa4e2f764 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -242,6 +242,8 @@ internal interface IMXCryptoStore { fun getLiveMyDevicesInfo(): LiveData> + fun getLiveMyDevicesInfo(deviceId: String): LiveData> + fun saveMyDevicesInfo(info: List) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index f5468634cb..736d4d495c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper +import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailMapper @@ -68,6 +69,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity @@ -112,6 +114,7 @@ internal class RealmCryptoStore @Inject constructor( @UserId private val userId: String, @DeviceId private val deviceId: String?, private val clock: Clock, + private val myDeviceLastSeenInfoEntityMapper: MyDeviceLastSeenInfoEntityMapper, ) : IMXCryptoStore { /* ========================================================================================== @@ -596,17 +599,24 @@ internal class RealmCryptoStore @Inject constructor( { realm: Realm -> realm.where() }, - { entity -> - DeviceInfo( - deviceId = entity.deviceId, - lastSeenIp = entity.lastSeenIp, - lastSeenTs = entity.lastSeenTs, - displayName = entity.displayName - ) - } + { entity -> myDeviceLastSeenInfoEntityMapper.map(entity) } ) } + override fun getLiveMyDevicesInfo(deviceId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm.where() + .equalTo(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, deviceId) + }, + { entity -> myDeviceLastSeenInfoEntityMapper.map(entity) } + ) + + return Transformations.map(liveData) { + it.firstOrNull().toOptional() + } + } + override fun saveMyDevicesInfo(info: List) { val entities = info.map { MyDeviceLastSeenInfoEntity( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt new file mode 100644 index 0000000000..ed44b0765a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.store.db.mapper + +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity +import javax.inject.Inject + +// TODO add unit tests +internal class MyDeviceLastSeenInfoEntityMapper @Inject constructor() { + + fun map(entity: MyDeviceLastSeenInfoEntity): DeviceInfo { + return DeviceInfo( + deviceId = entity.deviceId, + lastSeenIp = entity.lastSeenIp, + lastSeenTs = entity.lastSeenTs, + displayName = entity.displayName + ) + } +} From e542dc4aaccc36552830d9104b975828f57bac3d Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 30 Aug 2022 16:47:34 +0200 Subject: [PATCH 08/28] Adding a method to retrieve livedata of crypto device info for a given device id --- .../matrix/android/sdk/api/session/crypto/CryptoService.kt | 2 ++ .../android/sdk/internal/crypto/DefaultCryptoService.kt | 4 ++++ .../android/sdk/internal/crypto/store/IMXCryptoStore.kt | 2 ++ .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 6 ++++++ 4 files changed, 14 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 4f6a1fa02f..e0e662c789 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -122,6 +122,8 @@ interface CryptoService { fun getLiveCryptoDeviceInfo(): LiveData> + fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData> + fun getLiveCryptoDeviceInfo(userId: String): LiveData> fun getLiveCryptoDeviceInfo(userIds: List): LiveData> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 39866163ce..8dd7c309c6 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -534,6 +534,10 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getLiveDeviceList() } + override fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData> { + return cryptoStore.getLiveDeviceWithId(deviceId) + } + override fun getLiveCryptoDeviceInfo(userId: String): LiveData> { return cryptoStore.getLiveDeviceList(userId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 3aa4e2f764..56eba25249 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -238,6 +238,8 @@ internal interface IMXCryptoStore { // TODO temp fun getLiveDeviceList(): LiveData> + fun getLiveDeviceWithId(deviceId: String): LiveData> + fun getMyDevicesInfo(): List fun getLiveMyDevicesInfo(): LiveData> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 736d4d495c..3b8fa4cacd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -581,6 +581,12 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun getLiveDeviceWithId(deviceId: String): LiveData> { + return Transformations.map(getLiveDeviceList()) { devices -> + devices.firstOrNull { it.deviceId == deviceId }.toOptional() + } + } + override fun getMyDevicesInfo(): List { return monarchy.fetchAllCopiedSync { it.where() From 68f106efaca22254596fee23f78c052a06b3e285 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 31 Aug 2022 10:09:40 +0200 Subject: [PATCH 09/28] Adding use case to get full device info for a given device id --- .../v2/overview/GetDeviceFullInfoUseCase.kt | 54 +++++++++++++++++++ .../v2/overview/SessionOverviewViewModel.kt | 21 ++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt new file mode 100644 index 0000000000..b7d8efb59a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import androidx.lifecycle.asFlow +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.settings.devices.DeviceFullInfo +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emptyFlow +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional +import javax.inject.Inject + +// TODO add unit tests +class GetDeviceFullInfoUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun execute(deviceId: String): Flow> { + return activeSessionHolder.getSafeActiveSession()?.let { session -> + combine( + session.cryptoService().getMyDevicesInfoLive(deviceId).asFlow(), + session.cryptoService().getLiveCryptoDeviceInfoWithId(deviceId).asFlow() + ) { deviceInfo, cryptoDeviceInfo -> + val info = deviceInfo.getOrNull() + val cryptoInfo = cryptoDeviceInfo.getOrNull() + val fullInfo = if (info != null && cryptoInfo != null) { + DeviceFullInfo( + deviceInfo = info, + cryptoDeviceInfo = cryptoInfo + ) + } else { + null + } + fullInfo.toOptional() + } + } ?: emptyFlow() + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt index f55da2819f..84c15301aa 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.settings.devices.v2.overview import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.Success import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -24,11 +25,16 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.Session +// TODO add unit tests class SessionOverviewViewModel @AssistedInject constructor( @Assisted val initialState: SessionOverviewViewState, session: Session, + private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase, ) : VectorViewModel(initialState) { companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() @@ -39,12 +45,19 @@ class SessionOverviewViewModel @AssistedInject constructor( } init { - val currentSessionId = session.sessionParams.deviceId.orEmpty() + val currentDeviceId = session.sessionParams.deviceId.orEmpty() setState { - copy( - isCurrentSession = sessionId.isNotEmpty() && sessionId == currentSessionId - ) + copy(isCurrentSession = sessionId.isNotEmpty() && sessionId == currentDeviceId) } + + observeSessionInfo(currentDeviceId) + } + + private fun observeSessionInfo(deviceId: String) { + getDeviceFullInfoUseCase.execute(deviceId) + .mapNotNull { it.getOrNull() } + .onEach { setState { copy(deviceInfo = Success(it)) } } + .launchIn(viewModelScope) } override fun handle(action: SessionOverviewAction) { From 0c3310dd162ff5e23c35499a089bb645834cf679 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 31 Aug 2022 11:55:58 +0200 Subject: [PATCH 10/28] Adding unit tests for the new use case --- .../v2/overview/GetDeviceFullInfoUseCase.kt | 1 - .../GetLiveLocationShareSummaryUseCaseTest.kt | 7 +- .../GetListOfUserLiveLocationUseCaseTest.kt | 4 +- .../overview/GetDeviceFullInfoUseCaseTest.kt | 99 +++++++++++++++++++ .../app/test/TestCoroutineDispatchers.kt | 2 +- .../app/test/fakes/FakeActiveSessionHolder.kt | 4 + .../app/test/fakes/FakeCryptoService.kt | 8 ++ .../test/fakes/FakeFlowLiveDataConversions.kt | 4 +- 8 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt index b7d8efb59a..d20ca17471 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt @@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import javax.inject.Inject -// TODO add unit tests class GetDeviceFullInfoUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, ) { diff --git a/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt index ed1bcebf16..89966b5317 100644 --- a/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt @@ -18,7 +18,7 @@ package im.vector.app.features.location.live import im.vector.app.test.fakes.FakeFlowLiveDataConversions import im.vector.app.test.fakes.FakeSession -import im.vector.app.test.fakes.givenAsFlowReturns +import im.vector.app.test.fakes.givenAsFlow import io.mockk.unmockkAll import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest @@ -28,7 +28,6 @@ import org.junit.Before import org.junit.Test import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent -import org.matrix.android.sdk.api.util.Optional private const val A_ROOM_ID = "room_id" private const val AN_EVENT_ID = "event_id" @@ -64,7 +63,7 @@ class GetLiveLocationShareSummaryUseCaseTest { .getRoom(A_ROOM_ID) .locationSharingService() .givenLiveLocationShareSummaryReturns(AN_EVENT_ID, summary) - .givenAsFlowReturns(Optional(summary)) + .givenAsFlow() val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first() @@ -77,7 +76,7 @@ class GetLiveLocationShareSummaryUseCaseTest { .getRoom(A_ROOM_ID) .locationSharingService() .givenLiveLocationShareSummaryReturns(AN_EVENT_ID, null) - .givenAsFlowReturns(Optional(null)) + .givenAsFlow() val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first() diff --git a/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt index 420b8e6a06..6d24858915 100644 --- a/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt @@ -19,7 +19,7 @@ package im.vector.app.features.location.live.map import im.vector.app.features.location.LocationData import im.vector.app.test.fakes.FakeFlowLiveDataConversions import im.vector.app.test.fakes.FakeSession -import im.vector.app.test.fakes.givenAsFlowReturns +import im.vector.app.test.fakes.givenAsFlow import io.mockk.coEvery import io.mockk.mockk import io.mockk.unmockkAll @@ -81,7 +81,7 @@ class GetListOfUserLiveLocationUseCaseTest { .getRoom(A_ROOM_ID) .locationSharingService() .givenRunningLiveLocationShareSummariesReturns(summaries) - .givenAsFlowReturns(summaries) + .givenAsFlow() val viewState1 = UserLiveLocationViewState( matrixItem = MatrixItem.UserItem(id = "@userId1:matrix.org", displayName = "User 1", avatarUrl = ""), diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt new file mode 100644 index 0000000000..32d7b6edfe --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asFlow +import im.vector.app.features.settings.devices.DeviceFullInfo +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeFlowLiveDataConversions +import im.vector.app.test.fakes.givenAsFlow +import io.mockk.unmockkAll +import io.mockk.verify +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.util.Optional + +private const val A_DEVICE_ID = "device-id" + +class GetDeviceFullInfoUseCaseTest { + + private val fakeActiveSessionHolder = FakeActiveSessionHolder() + private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions() + + private val getDeviceFullInfoUseCase = GetDeviceFullInfoUseCase( + activeSessionHolder = fakeActiveSessionHolder.instance + ) + + @Before + fun setUp() { + fakeFlowLiveDataConversions.setup() + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given an active session and info for device when getting device info then the result is correct`() = runTest { + val deviceInfo = DeviceInfo() + fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(deviceInfo)) + fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow() + val cryptoDeviceInfo = CryptoDeviceInfo(deviceId = A_DEVICE_ID, userId = "") + fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(cryptoDeviceInfo)) + fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow() + + val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull() + + deviceFullInfo shouldBeEqualTo Optional(DeviceFullInfo(deviceInfo = deviceInfo, cryptoDeviceInfo = cryptoDeviceInfo)) + verify { fakeActiveSessionHolder.instance.getSafeActiveSession() } + verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() } + verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() } + } + + @Test + fun `given an active session and no info for device when getting device info then the result is null`() = runTest { + fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(null)) + fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow() + fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(null)) + fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow() + + val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull() + + deviceFullInfo shouldBeEqualTo Optional(null) + verify { fakeActiveSessionHolder.instance.getSafeActiveSession() } + verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() } + verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() } + } + + @Test + fun `given no active session when getting device info then the result is empty`() = runTest { + fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null) + + val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull() + + deviceFullInfo shouldBeEqualTo null + verify { fakeActiveSessionHolder.instance.getSafeActiveSession() } + } +} diff --git a/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt b/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt index fb3c1bb70a..c4f4c2a19a 100644 --- a/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt +++ b/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt @@ -19,7 +19,7 @@ package im.vector.app.test import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -private val testDispatcher = UnconfinedTestDispatcher() +internal val testDispatcher = UnconfinedTestDispatcher() internal val testCoroutineDispatchers = MatrixCoroutineDispatchers( io = testDispatcher, diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt index 3065c18c30..bfc36ef06d 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt @@ -33,4 +33,8 @@ class FakeActiveSessionHolder( fun expectSetsActiveSession(session: Session) { justRun { instance.setActiveSession(session) } } + + fun givenGetSafeActiveSessionReturns(session: Session?) { + every { instance.getSafeActiveSession() } returns session + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt index ed571fc2f2..2c31933464 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt @@ -20,11 +20,15 @@ import androidx.lifecycle.MutableLiveData import io.mockk.mockk import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.util.Optional class FakeCryptoService : CryptoService by mockk() { var roomKeysExport = ByteArray(size = 1) var cryptoDeviceInfos = mutableMapOf() + var cryptoDeviceInfoWithIdLiveData: MutableLiveData> = MutableLiveData() + var myDevicesInfoWithIdLiveData: MutableLiveData> = MutableLiveData() override suspend fun exportRoomKeys(password: String) = roomKeysExport @@ -35,4 +39,8 @@ class FakeCryptoService : CryptoService by mockk() { override fun getLiveCryptoDeviceInfo(userIds: List) = MutableLiveData( cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList() ) + + override fun getLiveCryptoDeviceInfoWithId(deviceId: String) = cryptoDeviceInfoWithIdLiveData + + override fun getMyDevicesInfoLive(deviceId: String) = myDevicesInfoWithIdLiveData } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt index 9abbcc174d..956a86f32e 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt @@ -28,6 +28,6 @@ class FakeFlowLiveDataConversions { } } -fun LiveData.givenAsFlowReturns(value: T) { - every { asFlow() } returns flowOf(value) +fun LiveData.givenAsFlow() { + every { asFlow() } returns flowOf(value!!) } From 611749c08f6f9e5a849e9c93a8571481fa2c71c0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 31 Aug 2022 14:12:16 +0200 Subject: [PATCH 11/28] Adding unit tests for mapper --- .../MyDeviceLastSeenInfoEntityMapper.kt | 1 - .../MyDeviceLastSeenInfoEntityMapperTest.kt | 52 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt index ed44b0765a..76e3171f4d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity import javax.inject.Inject -// TODO add unit tests internal class MyDeviceLastSeenInfoEntityMapper @Inject constructor() { fun map(entity: MyDeviceLastSeenInfoEntity): DeviceInfo { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt new file mode 100644 index 0000000000..e706fd6622 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.store.db.mapper + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity + +private const val A_DEVICE_ID = "device-id" +private const val AN_IP_ADDRESS = "ip-address" +private const val A_TIMESTAMP = 123L +private const val A_DISPLAY_NAME = "display-name" + +class MyDeviceLastSeenInfoEntityMapperTest { + + private val myDeviceLastSeenInfoEntityMapper = MyDeviceLastSeenInfoEntityMapper() + + @Test + fun `given an entity when mapping to model then all fields are correctly mapped`() { + val entity = MyDeviceLastSeenInfoEntity( + deviceId = A_DEVICE_ID, + lastSeenIp = AN_IP_ADDRESS, + lastSeenTs = A_TIMESTAMP, + displayName = A_DISPLAY_NAME + ) + val expectedDeviceInfo = DeviceInfo( + deviceId = A_DEVICE_ID, + lastSeenIp = AN_IP_ADDRESS, + lastSeenTs = A_TIMESTAMP, + displayName = A_DISPLAY_NAME + ) + + val deviceInfo = myDeviceLastSeenInfoEntityMapper.map(entity) + + deviceInfo shouldBeEqualTo expectedDeviceInfo + } +} From b995f798f993612daeb3f3d827e13de85b78aa17 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 31 Aug 2022 14:44:01 +0200 Subject: [PATCH 12/28] Adding unit tests for viewModel --- .../v2/overview/SessionOverviewViewModel.kt | 1 - .../v2/overview/SessionOverviewViewState.kt | 4 + .../overview/SessionOverviewViewModelTest.kt | 80 +++++++++++++++++++ .../im/vector/app/test/fakes/FakeSession.kt | 5 ++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt index 84c15301aa..9c40480270 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.Session -// TODO add unit tests class SessionOverviewViewModel @AssistedInject constructor( @Assisted val initialState: SessionOverviewViewState, session: Session, diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt index e839348800..8fa19a6eee 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt @@ -16,11 +16,15 @@ package im.vector.app.features.settings.devices.v2.overview +import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.settings.devices.DeviceFullInfo data class SessionOverviewViewState( val sessionId: String, val isCurrentSession: Boolean = false, + val deviceInfo: Async = Uninitialized, ) : MavericksState { constructor(args: SessionOverviewArgs) : this( sessionId = args.sessionId diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt new file mode 100644 index 0000000000..f15bc0860c --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.test.MvRxTestRule +import im.vector.app.features.settings.devices.DeviceFullInfo +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.test +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.api.util.Optional + +private const val A_SESSION_ID = "session-id" + +class SessionOverviewViewModelTest { + + @get:Rule + val mvRxTestRule = MvRxTestRule(testDispatcher = UnconfinedTestDispatcher()) + + private val args = SessionOverviewArgs( + sessionId = A_SESSION_ID + ) + private val fakeSession = FakeSession() + private val getDeviceFullInfoUseCase = mockk() + + private fun createViewModel() = SessionOverviewViewModel( + initialState = SessionOverviewViewState(args), + session = fakeSession, + getDeviceFullInfoUseCase = getDeviceFullInfoUseCase + ) + + @Test + fun `given the viewModel has been initialized then viewState is updated with session info`() = runTest { + val sessionParams = givenIdForSession(A_SESSION_ID) + val deviceFullInfo = mockk() + every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(Optional(deviceFullInfo)) + val expectedState = SessionOverviewViewState( + sessionId = A_SESSION_ID, + isCurrentSession = true, + deviceInfo = Success(deviceFullInfo) + ) + + val viewModel = createViewModel() + + viewModel.test() + .assertLatestState { state -> state == expectedState } + .finish() + verify { sessionParams.deviceId } + verify { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } + } + + private fun givenIdForSession(deviceId: String): SessionParams { + val sessionParams = mockk() + every { sessionParams.deviceId } returns deviceId + fakeSession.givenSessionParams(sessionParams) + return sessionParams + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index ee016ecae3..71bcde5807 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -26,6 +26,7 @@ import io.mockk.coJustRun import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic +import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService @@ -71,6 +72,10 @@ class FakeSession( } } + fun givenSessionParams(sessionParams: SessionParams) { + every { this@FakeSession.sessionParams } returns sessionParams + } + companion object { fun withRoomSummary(roomSummary: RoomSummary) = FakeSession().apply { From a5ee4faef4867c54d26d8ba136ae9f084f7b087c Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 1 Sep 2022 09:25:11 +0200 Subject: [PATCH 13/28] Introducing some reusable usecases --- .../devices/CurrentSessionCrossSigningInfo.kt | 26 ++++++++++ .../settings/devices/DevicesViewModel.kt | 28 +++-------- ...etCurrentSessionCrossSigningInfoUseCase.kt | 37 ++++++++++++++ ...yptionTrustLevelForCurrentDeviceUseCase.kt | 38 ++++++++++++++ ...GetEncryptionTrustLevelForDeviceUseCase.kt | 40 +++++++++++++++ ...cryptionTrustLevelForOtherDeviceUseCase.kt | 49 +++++++++++++++++++ .../features/settings/devices/TrustUtils.kt | 1 + .../v2/overview/GetDeviceFullInfoUseCase.kt | 10 +++- 8 files changed, 206 insertions(+), 23 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt b/vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt new file mode 100644 index 0000000000..790de08823 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices + +/** + * Used to hold some info about the cross signing of the current Session. + */ +data class CurrentSessionCrossSigningInfo( + val deviceId: String?, + val isCrossSigningInitialized: Boolean, + val isCrossSigningVerified: Boolean, +) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index 3b5bcb61d9..82c346b09c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -101,6 +101,8 @@ class DevicesViewModel @AssistedInject constructor( private val stringProvider: StringProvider, private val matrix: Matrix, private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase, + getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase, + private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase, ) : VectorViewModel(initialState), VerificationService.Listener { var uiaContinuation: Continuation? = null @@ -116,8 +118,9 @@ class DevicesViewModel @AssistedInject constructor( private val refreshSource = PublishDataSource() init { - val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized() - val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified() + val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute() + val hasAccountCrossSigning = currentSessionCrossSigningInfo.isCrossSigningInitialized + val accountCrossSigningIsTrusted = currentSessionCrossSigningInfo.isCrossSigningVerified setState { copy( @@ -143,12 +146,7 @@ class DevicesViewModel @AssistedInject constructor( .sortedByDescending { it.lastSeenTs } .map { deviceInfo -> val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId } - val trustLevelForShield = computeTrustLevelForShield( - currentSessionCrossTrusted = accountCrossSigningIsTrusted, - legacyMode = !hasAccountCrossSigning, - deviceTrustLevel = cryptoDeviceInfo?.trustLevel, - isCurrentDevice = deviceInfo.deviceId == session.sessionParams.deviceId - ) + val trustLevelForShield = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo) val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0) DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield, isInactive) } @@ -268,20 +266,6 @@ class DevicesViewModel @AssistedInject constructor( } } - private fun computeTrustLevelForShield( - currentSessionCrossTrusted: Boolean, - legacyMode: Boolean, - deviceTrustLevel: DeviceTrustLevel?, - isCurrentDevice: Boolean, - ): RoomEncryptionTrustLevel { - return TrustUtils.shieldForTrust( - currentDevice = isCurrentDevice, - trustMSK = currentSessionCrossTrusted, - legacyMode = legacyMode, - deviceTrustLevel = deviceTrustLevel - ) - } - private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) { val txID = session.cryptoService() .verificationService() diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt new file mode 100644 index 0000000000..aa0de9ddf1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices + +import im.vector.app.core.di.ActiveSessionHolder +import javax.inject.Inject + +// TODO add unit tests +class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun execute(): CurrentSessionCrossSigningInfo { + val session = activeSessionHolder.getActiveSession() + val isCrossSigningInitialized = session.cryptoService().crossSigningService().isCrossSigningInitialized() + val isCrossSigningVerified = session.cryptoService().crossSigningService().isCrossSigningVerified() + return CurrentSessionCrossSigningInfo( + deviceId = session.sessionParams.deviceId, + isCrossSigningInitialized = isCrossSigningInitialized, + isCrossSigningVerified = isCrossSigningVerified + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt new file mode 100644 index 0000000000..eaa72b424a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices + +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel +import javax.inject.Inject + +// TODO add unit tests +class GetEncryptionTrustLevelForCurrentDeviceUseCase @Inject constructor() { + + fun execute(trustMSK: Boolean, legacyMode: Boolean): RoomEncryptionTrustLevel { + return if (legacyMode) { + // In legacy, current session is always trusted + RoomEncryptionTrustLevel.Trusted + } else { + // If current session doesn't trust MSK, show red shield for current device + if (trustMSK) { + RoomEncryptionTrustLevel.Trusted + } else { + RoomEncryptionTrustLevel.Warning + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt new file mode 100644 index 0000000000..d988f728ae --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices + +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel +import javax.inject.Inject + +// TODO add unit tests +class GetEncryptionTrustLevelForDeviceUseCase @Inject constructor( + private val getEncryptionTrustLevelForCurrentDeviceUseCase: GetEncryptionTrustLevelForCurrentDeviceUseCase, + private val getEncryptionTrustLevelForOtherDeviceUseCase: GetEncryptionTrustLevelForOtherDeviceUseCase, +) { + + fun execute(currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo, cryptoDeviceInfo: CryptoDeviceInfo?): RoomEncryptionTrustLevel { + val legacyMode = !currentSessionCrossSigningInfo.isCrossSigningInitialized + val trustMSK = currentSessionCrossSigningInfo.isCrossSigningVerified + val isCurrentDevice = !cryptoDeviceInfo?.deviceId.isNullOrEmpty() && cryptoDeviceInfo?.deviceId == currentSessionCrossSigningInfo.deviceId + val deviceTrustLevel = cryptoDeviceInfo?.trustLevel + + return when { + isCurrentDevice -> getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK, legacyMode) + else -> getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK, legacyMode, deviceTrustLevel) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt new file mode 100644 index 0000000000..41cdae23a4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices + +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel +import javax.inject.Inject + +// TODO add unit tests +class GetEncryptionTrustLevelForOtherDeviceUseCase @Inject constructor() { + + fun execute(trustMSK: Boolean, legacyMode: Boolean, deviceTrustLevel: DeviceTrustLevel?): RoomEncryptionTrustLevel { + return if (legacyMode) { + // use local trust + if (deviceTrustLevel?.locallyVerified == true) { + RoomEncryptionTrustLevel.Trusted + } else { + RoomEncryptionTrustLevel.Warning + } + } else { + if (trustMSK) { + // use cross sign trust, put locally trusted in black + when { + deviceTrustLevel?.crossSigningVerified == true -> RoomEncryptionTrustLevel.Trusted + deviceTrustLevel?.locallyVerified == true -> RoomEncryptionTrustLevel.Default + else -> RoomEncryptionTrustLevel.Warning + } + } else { + // The current session is untrusted, so displays others in black + // as we can't know the cross-signing state + RoomEncryptionTrustLevel.Default + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt b/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt index da18154ea1..7709a63344 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt @@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel +// TODO Replace usage by the use case GetEncryptionTrustLevelForDeviceUseCase object TrustUtils { fun shieldForTrust( diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt index d20ca17471..51252de34a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt @@ -19,6 +19,8 @@ package im.vector.app.features.settings.devices.v2.overview import androidx.lifecycle.asFlow import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.settings.devices.DeviceFullInfo +import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase +import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emptyFlow @@ -26,12 +28,16 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import javax.inject.Inject +// TODO update unit test class GetDeviceFullInfoUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, + private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase, + private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase, ) { fun execute(deviceId: String): Flow> { return activeSessionHolder.getSafeActiveSession()?.let { session -> + val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute() combine( session.cryptoService().getMyDevicesInfoLive(deviceId).asFlow(), session.cryptoService().getLiveCryptoDeviceInfoWithId(deviceId).asFlow() @@ -39,9 +45,11 @@ class GetDeviceFullInfoUseCase @Inject constructor( val info = deviceInfo.getOrNull() val cryptoInfo = cryptoDeviceInfo.getOrNull() val fullInfo = if (info != null && cryptoInfo != null) { + val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo) DeviceFullInfo( deviceInfo = info, - cryptoDeviceInfo = cryptoInfo + cryptoDeviceInfo = cryptoInfo, + trustLevelForShield = roomEncryptionTrustLevel ) } else { null From 52006c1bb453057f2c3cbe7195c819edea3da2c2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 1 Sep 2022 09:32:14 +0200 Subject: [PATCH 14/28] Renaming CurrentSessionView into SessionInfoView to be more generic --- .../devices/v2/list/CurrentSessionView.kt | 78 ------------------- .../devices/v2/list/SessionInfoView.kt | 78 +++++++++++++++++++ ...rent_session.xml => view_session_info.xml} | 26 +++---- 3 files changed, 91 insertions(+), 91 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt rename vector/src/main/res/layout/{view_current_session.xml => view_session_info.xml} (78%) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt deleted file mode 100644 index 1ce035931f..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.settings.devices.v2.list - -import android.content.Context -import android.util.AttributeSet -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.view.isVisible -import im.vector.app.R -import im.vector.app.databinding.ViewCurrentSessionBinding -import im.vector.app.features.settings.devices.DeviceFullInfo -import im.vector.app.features.themes.ThemeUtils -import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel - -class CurrentSessionView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : ConstraintLayout(context, attrs, defStyleAttr) { - - private val views: ViewCurrentSessionBinding - - init { - inflate(context, R.layout.view_current_session, this) - views = ViewCurrentSessionBinding.bind(this) - } - - val viewDetailsButton = views.currentSessionViewDetailsButton - - fun render(currentDeviceInfo: DeviceFullInfo) { - renderDeviceInfo(currentDeviceInfo.deviceInfo.displayName.orEmpty()) - renderVerificationStatus(currentDeviceInfo.trustLevelForShield) - } - - private fun renderVerificationStatus(trustLevelForShield: RoomEncryptionTrustLevel) { - views.currentSessionVerificationStatusImageView.render(trustLevelForShield) - if (trustLevelForShield == RoomEncryptionTrustLevel.Trusted) { - renderCrossSigningVerified() - } else { - renderCrossSigningUnverified() - } - } - - private fun renderCrossSigningVerified() { - views.currentSessionVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_verified) - views.currentSessionVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorPrimary)) - views.currentSessionVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_verified) - views.currentSessionVerifySessionButton.isVisible = false - } - - private fun renderCrossSigningUnverified() { - views.currentSessionVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unverified) - views.currentSessionVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorError)) - views.currentSessionVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_unverified) - views.currentSessionVerifySessionButton.isVisible = true - } - - // TODO. We don't have this info yet. Update later accordingly. - private fun renderDeviceInfo(sessionName: String) { - views.currentSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile) - views.currentSessionDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile) - views.currentSessionNameTextView.text = sessionName - } -} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt new file mode 100644 index 0000000000..b79adfb2d4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.list + +import android.content.Context +import android.util.AttributeSet +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import im.vector.app.R +import im.vector.app.databinding.ViewSessionInfoBinding +import im.vector.app.features.settings.devices.DeviceFullInfo +import im.vector.app.features.themes.ThemeUtils +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel + +class SessionInfoView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private val views: ViewSessionInfoBinding + + init { + inflate(context, R.layout.view_session_info, this) + views = ViewSessionInfoBinding.bind(this) + } + + val viewDetailsButton = views.sessionInfoViewDetailsButton + + fun render(deviceInfo: DeviceFullInfo) { + renderDeviceInfo(deviceInfo.deviceInfo.displayName.orEmpty()) + renderVerificationStatus(deviceInfo.trustLevelForShield) + } + + private fun renderVerificationStatus(trustLevelForShield: RoomEncryptionTrustLevel) { + views.sessionInfoVerificationStatusImageView.render(trustLevelForShield) + if (trustLevelForShield == RoomEncryptionTrustLevel.Trusted) { + renderCrossSigningVerified() + } else { + renderCrossSigningUnverified() + } + } + + private fun renderCrossSigningVerified() { + views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_verified) + views.sessionInfoVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorPrimary)) + views.sessionInfoVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_verified) + views.sessionInfoVerifySessionButton.isVisible = false + } + + private fun renderCrossSigningUnverified() { + views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unverified) + views.sessionInfoVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorError)) + views.sessionInfoVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_unverified) + views.sessionInfoVerifySessionButton.isVisible = true + } + + // TODO. We don't have this info yet. Update later accordingly. + private fun renderDeviceInfo(sessionName: String) { + views.sessionInfoDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile) + views.sessionInfoDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile) + views.sessionInfoNameTextView.text = sessionName + } +} diff --git a/vector/src/main/res/layout/view_current_session.xml b/vector/src/main/res/layout/view_session_info.xml similarity index 78% rename from vector/src/main/res/layout/view_current_session.xml rename to vector/src/main/res/layout/view_session_info.xml index 91977eba40..015f4961c9 100644 --- a/vector/src/main/res/layout/view_current_session.xml +++ b/vector/src/main/res/layout/view_session_info.xml @@ -8,7 +8,7 @@ android:paddingBottom="16dp"> + app:layout_constraintTop_toBottomOf="@id/sessionInfoNameTextView">