diff --git a/changelog.d/7100.wip b/changelog.d/7100.wip
new file mode 100644
index 0000000000..47e7a6f810
--- /dev/null
+++ b/changelog.d/7100.wip
@@ -0,0 +1 @@
+[Device Management] Learn more bottom sheets
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 4ff7aae750..7925ac02c4 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -406,6 +406,7 @@
Reset
Learn more
Next
+ Got it
Copied to clipboard
@@ -2366,9 +2367,6 @@
Manage Sessions
Sign out of this session
Sessions
- Other sessions
- For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.
-
Server name
Server version
Server file upload limit
@@ -3229,6 +3227,8 @@
Show All Sessions (V2, WIP)
+ Other sessions
+ For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.
Mobile
Web
Desktop
@@ -3302,6 +3302,14 @@
Session name
Custom session names can help you recognize your devices more easily.
Please be aware that session names are also visible to people you communicate with.
+ Inactive sessions
+ Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.\n\nRemoving inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.
+ Unverified sessions
+ Unverified sessions are sessions that have logged in with your credentials but not been cross-verified.\n\nYou should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.
+ Verified sessions
+ Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.
+ Renaming sessions
+ Other users in direct messages and rooms that you join are able to view a full list of your sessions.\n\nThis provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.
%s\nis looking a little empty.
diff --git a/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml b/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml
index d3b931e44a..098ec263fc 100644
--- a/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml
+++ b/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml
@@ -4,6 +4,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 62e7140742..38b62e1511 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
@@ -89,6 +89,7 @@ import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewMode
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
import im.vector.app.features.settings.devices.DevicesViewModel
import im.vector.app.features.settings.devices.v2.details.SessionDetailsViewModel
+import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreViewModel
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
import im.vector.app.features.settings.devices.v2.rename.RenameSessionViewModel
@@ -659,4 +660,9 @@ interface MavericksViewModelModule {
@IntoMap
@MavericksViewModelKey(RenameSessionViewModel::class)
fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(SessionLearnMoreViewModel::class)
+ fun sessionLearnMoreViewModelFactory(factory: SessionLearnMoreViewModel.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 1e5c4d88e0..0fdbd40178 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
@@ -21,7 +21,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.airbnb.mvrx.Success
@@ -82,7 +81,6 @@ class VectorSettingsDevicesFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- initLearnMoreButtons()
initWaitingView()
initOtherSessionsView()
initSecurityRecommendationsView()
@@ -155,12 +153,6 @@ class VectorSettingsDevicesFragment :
super.onDestroyView()
}
- private fun initLearnMoreButtons() {
- views.deviceListHeaderOtherSessions.onLearnMoreClickListener = {
- Toast.makeText(context, "Learn more other", Toast.LENGTH_LONG).show()
- }
- }
-
private fun cleanUpLearnMoreButtonsListeners() {
views.deviceListHeaderOtherSessions.onLearnMoreClickListener = null
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
index ef8682df01..0660e7d642 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
@@ -65,14 +65,23 @@ class SessionsListHeaderView @JvmOverloads constructor(
return
}
+ val hasLearnMoreLink = typedArray.getBoolean(R.styleable.SessionsListHeaderView_sessionsListHeaderHasLearnMoreLink, true)
+ if (hasLearnMoreLink) {
+ setDescriptionWithLearnMore(description)
+ } else {
+ binding.sessionsListHeaderDescription.text = description
+ }
+
+ binding.sessionsListHeaderDescription.isVisible = true
+ }
+
+ private fun setDescriptionWithLearnMore(description: String) {
val learnMore = context.getString(R.string.action_learn_more)
val fullDescription = buildString {
append(description)
append(" ")
append(learnMore)
}
-
- binding.sessionsListHeaderDescription.isVisible = true
binding.sessionsListHeaderDescription.setTextWithColoredPart(
fullText = fullDescription,
coloredPart = learnMore,
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt
new file mode 100644
index 0000000000..22ca06eb1e
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.more
+
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentManager
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.databinding.BottomSheetSessionLearnMoreBinding
+import kotlinx.parcelize.Parcelize
+
+@AndroidEntryPoint
+class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment() {
+
+ @Parcelize
+ data class Args(
+ val title: String,
+ val description: String,
+ ) : Parcelable
+
+ private val viewModel: SessionLearnMoreViewModel by fragmentViewModel()
+
+ override val showExpanded = true
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSessionLearnMoreBinding {
+ return BottomSheetSessionLearnMoreBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initCloseButton()
+ }
+
+ private fun initCloseButton() {
+ views.bottomSheetSessionLearnMoreCloseButton.debouncedClicks {
+ dismiss()
+ }
+ }
+
+ override fun invalidate() = withState(viewModel) { viewState ->
+ super.invalidate()
+ views.bottomSheetSessionLearnMoreTitle.text = viewState.title
+ views.bottomSheetSessionLearnMoreDescription.text = viewState.description
+ }
+
+ companion object {
+
+ fun show(fragmentManager: FragmentManager, args: Args) {
+ val bottomSheet = SessionLearnMoreBottomSheet()
+ bottomSheet.isCancelable = true
+ bottomSheet.setArguments(args)
+ bottomSheet.show(fragmentManager, "SessionLearnMoreBottomSheet")
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewModel.kt
new file mode 100644
index 0000000000..09ca2df15d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewModel.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.more
+
+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.EmptyAction
+import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorViewModel
+
+class SessionLearnMoreViewModel @AssistedInject constructor(
+ @Assisted initialState: SessionLearnMoreViewState,
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: SessionLearnMoreViewState): SessionLearnMoreViewModel
+ }
+
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+
+ override fun handle(action: EmptyAction) {
+ // do nothing
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewState.kt
new file mode 100644
index 0000000000..cade2ce861
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewState.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.more
+
+import com.airbnb.mvrx.MavericksState
+
+data class SessionLearnMoreViewState(
+ val title: String,
+ val description: String,
+) : MavericksState {
+ constructor(args: SessionLearnMoreBottomSheet.Args) : this(
+ title = args.title,
+ description = args.description,
+ )
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index 5734b04089..610776e22e 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -20,6 +20,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.annotation.StringRes
import androidx.core.view.isVisible
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
@@ -37,6 +38,7 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBott
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
+import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet
import im.vector.app.features.themes.ThemeUtils
import javax.inject.Inject
@@ -121,6 +123,7 @@ class OtherSessionsFragment :
)
)
views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_verified_sessions_found)
+ updateSecurityLearnMoreButton(R.string.device_manager_learn_more_sessions_verified_title, R.string.device_manager_learn_more_sessions_verified)
}
DeviceManagerFilterType.UNVERIFIED -> {
views.otherSessionsSecurityRecommendationView.render(
@@ -132,6 +135,10 @@ class OtherSessionsFragment :
)
)
views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_unverified_sessions_found)
+ updateSecurityLearnMoreButton(
+ R.string.device_manager_learn_more_sessions_unverified_title,
+ R.string.device_manager_learn_more_sessions_unverified
+ )
}
DeviceManagerFilterType.INACTIVE -> {
views.otherSessionsSecurityRecommendationView.render(
@@ -147,8 +154,10 @@ class OtherSessionsFragment :
)
)
views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_inactive_sessions_found)
+ updateSecurityLearnMoreButton(R.string.device_manager_learn_more_sessions_inactive_title, R.string.device_manager_learn_more_sessions_inactive)
+ }
+ DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */
}
- DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */ }
}
if (devices.isNullOrEmpty()) {
@@ -161,6 +170,26 @@ class OtherSessionsFragment :
}
}
+ private fun updateSecurityLearnMoreButton(
+ @StringRes titleResId: Int,
+ @StringRes descriptionResId: Int,
+ ) {
+ views.otherSessionsSecurityRecommendationView.onLearnMoreClickListener = {
+ showLearnMoreInfo(titleResId, getString(descriptionResId))
+ }
+ }
+
+ private fun showLearnMoreInfo(
+ @StringRes titleResId: Int,
+ description: String,
+ ) {
+ val args = SessionLearnMoreBottomSheet.Args(
+ title = getString(titleResId),
+ description = description,
+ )
+ SessionLearnMoreBottomSheet.show(childFragmentManager, args)
+ }
+
override fun onOtherSessionClicked(deviceId: String) {
viewNavigator.navigateToSessionOverview(
context = requireActivity(),
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
index 4af4913183..8c3b907070 100644
--- 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
@@ -41,9 +41,11 @@ import im.vector.app.databinding.FragmentSessionOverviewBinding
import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
+import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet
import im.vector.app.features.workers.signout.SignOutUiWorker
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import javax.inject.Inject
/**
@@ -204,6 +206,9 @@ class SessionOverviewFragment :
isLastSeenDetailsVisible = true,
)
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider)
+ views.sessionOverviewInfo.onLearnMoreClickListener = {
+ showLearnMoreInfoVerificationStatus(deviceInfo.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted)
+ }
} else {
views.sessionOverviewInfo.isVisible = false
}
@@ -249,4 +254,22 @@ class SessionOverviewFragment :
reAuthActivityResultLauncher.launch(intent)
}
}
+
+ private fun showLearnMoreInfoVerificationStatus(isVerified: Boolean) {
+ val titleResId = if (isVerified) {
+ R.string.device_manager_verification_status_verified
+ } else {
+ R.string.device_manager_verification_status_unverified
+ }
+ val descriptionResId = if (isVerified) {
+ R.string.device_manager_learn_more_sessions_verified
+ } else {
+ R.string.device_manager_learn_more_sessions_unverified
+ }
+ val args = SessionLearnMoreBottomSheet.Args(
+ title = getString(titleResId),
+ description = getString(descriptionResId),
+ )
+ SessionLearnMoreBottomSheet.show(childFragmentManager, args)
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt
index df92bee100..2f671492e3 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt
@@ -24,9 +24,11 @@ import androidx.core.widget.doOnTextChanged
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.extensions.showKeyboard
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSessionRenameBinding
+import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet
import javax.inject.Inject
/**
@@ -51,6 +53,7 @@ class RenameSessionFragment :
initEditText()
initSaveButton()
initWithLastEditedName()
+ initInfoView()
}
private fun initToolbar() {
@@ -75,6 +78,20 @@ class RenameSessionFragment :
viewModel.handle(RenameSessionAction.InitWithLastEditedName)
}
+ private fun initInfoView() {
+ views.renameSessionInfo.onLearnMoreClickListener = {
+ showLearnMoreInfo()
+ }
+ }
+
+ private fun showLearnMoreInfo() {
+ val args = SessionLearnMoreBottomSheet.Args(
+ title = getString(R.string.device_manager_learn_more_session_rename_title),
+ description = getString(R.string.device_manager_learn_more_session_rename),
+ )
+ SessionLearnMoreBottomSheet.show(childFragmentManager, args)
+ }
+
private fun observeViewEvents() {
viewModel.observeViewEvents {
when (it) {
diff --git a/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml b/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml
new file mode 100644
index 0000000000..466ab5af49
--- /dev/null
+++ b/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index 037f85ad28..e25b8b185f 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -53,11 +53,12 @@
android:id="@+id/deviceListHeaderOtherSessions"
android:layout_width="0dp"
android:layout_height="wrap_content"
- app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
- app:sessionsListHeaderTitle=""
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
+ app:layout_constraintTop_toBottomOf="@id/appBarLayout"
+ app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
+ app:sessionsListHeaderHasLearnMoreLink="false"
+ app:sessionsListHeaderTitle="" />
+ app:sessionsWarningInfoHasLearnMore="true" />
diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml
index 8e2daa2239..951e9eba05 100644
--- a/vector/src/main/res/layout/fragment_settings_devices.xml
+++ b/vector/src/main/res/layout/fragment_settings_devices.xml
@@ -12,11 +12,12 @@
android:id="@+id/deviceListHeaderSectionSecurityRecommendations"
android:layout_width="0dp"
android:layout_height="wrap_content"
- app:sessionsListHeaderDescription="@string/device_manager_header_section_security_recommendations_description"
- app:sessionsListHeaderTitle="@string/device_manager_header_section_security_recommendations_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="parent"
+ app:sessionsListHeaderDescription="@string/device_manager_header_section_security_recommendations_description"
+ app:sessionsListHeaderHasLearnMoreLink="false"
+ app:sessionsListHeaderTitle="@string/device_manager_header_section_security_recommendations_title" />
+ app:layout_constraintTop_toBottomOf="@id/deviceListHeaderSectionSecurityRecommendations"
+ app:recommendationDescription="@string/device_manager_unverified_sessions_description"
+ app:recommendationImageBackgroundTint="@color/shield_color_warning_background"
+ app:recommendationImageResource="@drawable/ic_shield_warning_no_border"
+ app:recommendationTitle="@string/device_manager_unverified_sessions_title" />
+ app:layout_constraintTop_toBottomOf="@id/deviceListUnverifiedSessionsRecommendation"
+ app:recommendationDescription="@plurals/device_manager_inactive_sessions_description"
+ app:recommendationImageBackgroundTint="?vctr_system"
+ app:recommendationImageResource="@drawable/ic_inactive_sessions"
+ app:recommendationTitle="@string/device_manager_inactive_sessions_title" />
+ app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider"
+ app:sessionsListHeaderDescription=""
+ app:sessionsListHeaderHasLearnMoreLink="false"
+ app:sessionsListHeaderTitle="@string/device_manager_current_session_title" />
+ app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession"
+ app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
+ app:sessionsListHeaderHasLearnMoreLink="false"
+ app:sessionsListHeaderTitle="@string/device_manager_sessions_other_title" />