diff --git a/.editorconfig b/.editorconfig
index 1cd48550..e15a1ece 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -14,6 +14,7 @@ ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand = unset
+ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
ktlint_code_style = android_studio
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_function-expression-body = disabled
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a1698f89..c41bbdfe 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -164,40 +164,17 @@
-
-
-
-
-
-
-
-
+ android:theme="@style/Theme.Compose">
+ android:value="com.android.messaging.ui.conversationlist.ConversationListActivity" />
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9a2f88b5..4ba8d3b3 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -168,6 +168,11 @@ dependencies {
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
+ androidTestImplementation(libs.androidx.test.espresso.contrib)
+ androidTestImplementation(libs.androidx.test.espresso.core)
+ androidTestImplementation(libs.androidx.test.ext.junit)
+ androidTestImplementation(libs.androidx.test.rules)
+ androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.hilt.android.testing)
kspAndroidTest(libs.hilt.compiler)
diff --git a/app/src/androidTest/java/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreenTest.kt b/app/src/androidTest/java/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreenTest.kt
new file mode 100644
index 00000000..8851e322
--- /dev/null
+++ b/app/src/androidTest/java/com/android/messaging/ui/appsettings/general/ui/AppSettingsScreenTest.kt
@@ -0,0 +1,224 @@
+package com.android.messaging.ui.appsettings.general.ui
+
+import androidx.activity.ComponentActivity
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import com.android.messaging.R
+import com.android.messaging.ui.appsettings.general.model.AppSettingsUiState
+import com.android.messaging.ui.appsettings.screen.SettingsScreenModel
+import com.android.messaging.ui.appsettings.screen.model.SettingsAction as Action
+import com.android.messaging.ui.core.AppTheme
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class AppSettingsScreenTest {
+
+ @get:Rule
+ val composeTestRule = createAndroidComposeRule()
+
+ private lateinit var screenModel: SettingsScreenModel
+
+ @Before
+ fun setup() {
+ screenModel = mockk(relaxed = true)
+ }
+
+ @Test
+ fun defaultSmsAppItem_displaysLabel() {
+ val appSettings = AppSettingsUiState(
+ isDefaultSmsApp = true,
+ defaultSmsAppLabel = "Messaging",
+ )
+
+ setContent(appSettings = appSettings)
+
+ composeTestRule.onNodeWithText("Messaging").assertIsDisplayed()
+ }
+
+ @Test
+ fun defaultSmsAppClick_delegatesToScreenModel() {
+ val appSettings = AppSettingsUiState(
+ isDefaultSmsApp = true,
+ defaultSmsAppLabel = "Messaging",
+ )
+
+ setContent(appSettings = appSettings)
+
+ val title = composeTestRule.activity.getString(R.string.sms_disabled_pref_title)
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.DefaultSmsAppClicked(true))
+ }
+ }
+
+ @Test
+ fun notificationsClick_delegatesToScreenModel() {
+ setContent()
+
+ val title = composeTestRule.activity.getString(
+ R.string.notifications_enabled_conversation_pref_title,
+ )
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.NotificationsClicked)
+ }
+ }
+
+ @Test
+ fun sendSoundToggle_delegatesToScreenModel() {
+ val appSettings = AppSettingsUiState(sendSoundEnabled = true)
+
+ setContent(appSettings = appSettings)
+
+ val title = composeTestRule.activity.getString(R.string.send_sound_pref_title)
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.SendSoundChanged(false))
+ }
+ }
+
+ @Test
+ fun debugSection_hiddenWhenDebugDisabled() {
+ val appSettings = AppSettingsUiState(isDebugEnabled = false)
+
+ setContent(appSettings = appSettings)
+
+ val debugTitle = composeTestRule.activity.getString(R.string.debug_category_pref_title)
+ composeTestRule.onNodeWithText(debugTitle).assertDoesNotExist()
+
+ val dumpSmsTitle = composeTestRule.activity.getString(R.string.dump_sms_pref_title)
+ composeTestRule.onNodeWithText(dumpSmsTitle).assertDoesNotExist()
+ }
+
+ @Test
+ fun debugSection_shownWhenDebugEnabled() {
+ val appSettings = AppSettingsUiState(
+ isDebugEnabled = true,
+ dumpSmsEnabled = false,
+ dumpMmsEnabled = false,
+ )
+
+ setContent(appSettings = appSettings)
+
+ val debugTitle = composeTestRule.activity.getString(R.string.debug_category_pref_title)
+ composeTestRule.onNodeWithText(debugTitle).assertIsDisplayed()
+
+ val dumpSmsTitle = composeTestRule.activity.getString(R.string.dump_sms_pref_title)
+ composeTestRule.onNodeWithText(dumpSmsTitle).assertIsDisplayed()
+
+ val dumpMmsTitle = composeTestRule.activity.getString(R.string.dump_mms_pref_title)
+ composeTestRule.onNodeWithText(dumpMmsTitle).assertIsDisplayed()
+ }
+
+ @Test
+ fun dumpSmsToggle_delegatesToScreenModel() {
+ val appSettings = AppSettingsUiState(
+ isDebugEnabled = true,
+ dumpSmsEnabled = false,
+ )
+
+ setContent(appSettings = appSettings)
+
+ val title = composeTestRule.activity.getString(R.string.dump_sms_pref_title)
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.DumpSmsChanged(true))
+ }
+ }
+
+ @Test
+ fun dumpMmsToggle_delegatesToScreenModel() {
+ val appSettings = AppSettingsUiState(
+ isDebugEnabled = true,
+ dumpMmsEnabled = false,
+ )
+
+ setContent(appSettings = appSettings)
+
+ val title = composeTestRule.activity.getString(R.string.dump_mms_pref_title)
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.DumpMmsChanged(true))
+ }
+ }
+
+ @Test
+ fun licensesClick_delegatesToScreenModel() {
+ setContent()
+
+ val title = composeTestRule.activity.getString(R.string.menu_license)
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.LicensesClicked)
+ }
+ }
+
+ @Test
+ fun advancedSettings_shownWhenTopLevel() {
+ var advancedClicks = 0
+
+ composeTestRule.setContent {
+ AppTheme {
+ AppSettingsScreen(
+ appSettings = AppSettingsUiState(),
+ screenModel = screenModel,
+ onNavigateBack = {},
+ isTopLevel = true,
+ onAdvancedClick = { advancedClicks += 1 },
+ )
+ }
+ }
+
+ val advancedTitle = composeTestRule.activity.getString(R.string.advanced_settings)
+ composeTestRule.onNodeWithText(advancedTitle).assertIsDisplayed()
+ composeTestRule.onNodeWithText(advancedTitle).performClick()
+
+ composeTestRule.runOnIdle {
+ assertEquals(1, advancedClicks)
+ }
+ }
+
+ @Test
+ fun advancedSettings_hiddenWhenNotTopLevel() {
+ composeTestRule.setContent {
+ AppTheme {
+ AppSettingsScreen(
+ appSettings = AppSettingsUiState(),
+ screenModel = screenModel,
+ onNavigateBack = {},
+ isTopLevel = false,
+ onAdvancedClick = null,
+ )
+ }
+ }
+
+ val advancedTitle = composeTestRule.activity.getString(R.string.advanced_settings)
+ composeTestRule.onNodeWithText(advancedTitle).assertDoesNotExist()
+ }
+
+ private fun setContent(
+ appSettings: AppSettingsUiState = AppSettingsUiState(),
+ ) {
+ composeTestRule.setContent {
+ AppTheme {
+ AppSettingsScreen(
+ appSettings = appSettings,
+ screenModel = screenModel,
+ onNavigateBack = {},
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/androidTest/java/com/android/messaging/ui/appsettings/screen/SettingsScreenTest.kt b/app/src/androidTest/java/com/android/messaging/ui/appsettings/screen/SettingsScreenTest.kt
new file mode 100644
index 00000000..a031129b
--- /dev/null
+++ b/app/src/androidTest/java/com/android/messaging/ui/appsettings/screen/SettingsScreenTest.kt
@@ -0,0 +1,196 @@
+package com.android.messaging.ui.appsettings.screen
+
+import androidx.activity.ComponentActivity
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.compose.LocalLifecycleOwner
+import com.android.messaging.R
+import com.android.messaging.ui.appsettings.general.model.AppSettingsUiState
+import com.android.messaging.ui.appsettings.screen.SettingsScreen
+import com.android.messaging.ui.appsettings.screen.SettingsScreenModel
+import com.android.messaging.ui.appsettings.screen.model.SettingsNavRoute
+import com.android.messaging.ui.appsettings.screen.model.SettingsUiState
+import com.android.messaging.ui.appsettings.subscription.model.SubscriptionSettingsUiState
+import com.android.messaging.ui.core.AppTheme
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class SettingsScreenTest {
+
+ @get:Rule
+ val composeTestRule = createAndroidComposeRule()
+
+ private val fakeUiStateFlow = MutableStateFlow(createSingleSimState())
+ private lateinit var screenModel: SettingsScreenModel
+
+ @Before
+ fun setup() {
+ screenModel = mockk(relaxed = true)
+ every { screenModel.uiState } returns fakeUiStateFlow
+ }
+
+ @Test
+ fun singleSim_skipsMainScreen_showsAppSettings() {
+ fakeUiStateFlow.value = createSingleSimState()
+
+ setScreenContent()
+
+ val generalTitle = composeTestRule.activity.getString(R.string.settings_activity_title)
+ composeTestRule.onNodeWithText(generalTitle).assertIsDisplayed()
+
+ val sendSoundTitle = composeTestRule.activity.getString(R.string.send_sound_pref_title)
+ composeTestRule.onNodeWithText(sendSoundTitle).assertIsDisplayed()
+ }
+
+ @Test
+ fun multiSim_showsMainScreen_withSubscriptions() {
+ fakeUiStateFlow.value = createMultiSimState()
+
+ setScreenContent()
+
+ val settingsTitle = composeTestRule.activity.getString(R.string.settings_activity_title)
+ composeTestRule.onNodeWithText(settingsTitle).assertIsDisplayed()
+
+ composeTestRule.onNodeWithText("SIM 1").assertIsDisplayed()
+ composeTestRule.onNodeWithText("SIM 2").assertIsDisplayed()
+ }
+
+ @Test
+ fun multiSim_generalSettingsClick_navigatesToAppSettings() {
+ fakeUiStateFlow.value = createMultiSimState()
+
+ setScreenContent()
+
+ val generalSettings = composeTestRule.activity.getString(R.string.general_settings)
+ composeTestRule.onNodeWithText(generalSettings).performClick()
+ composeTestRule.waitForIdle()
+
+ val sendSoundTitle = composeTestRule.activity.getString(R.string.send_sound_pref_title)
+ composeTestRule.onNodeWithText(sendSoundTitle).assertIsDisplayed()
+ }
+
+ @Test
+ fun lifecycleResume_refreshesState() {
+ fakeUiStateFlow.value = createSingleSimState()
+ lateinit var lifecycleOwner: TestLifecycleOwner
+
+ composeTestRule.runOnIdle {
+ lifecycleOwner = TestLifecycleOwner(
+ initialState = Lifecycle.State.STARTED,
+ )
+ }
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
+ AppTheme {
+ SettingsScreen(
+ onNavigateBack = {},
+ screenModel = screenModel,
+ )
+ }
+ }
+ }
+
+ composeTestRule.runOnIdle {
+ lifecycleOwner.moveTo(state = Lifecycle.State.RESUMED)
+ }
+ composeTestRule.waitForIdle()
+
+ verify(atLeast = 1) {
+ screenModel.refreshState()
+ }
+ }
+
+ @Test
+ fun singleSim_showsAdvancedSettings() {
+ fakeUiStateFlow.value = createSingleSimState()
+
+ setScreenContent()
+
+ val advancedTitle = composeTestRule.activity.getString(R.string.advanced_settings)
+ composeTestRule.onNodeWithText(advancedTitle).assertIsDisplayed()
+ }
+
+ private fun setScreenContent(
+ initialRoute: SettingsNavRoute = SettingsNavRoute.Main,
+ ) {
+ composeTestRule.setContent {
+ AppTheme {
+ SettingsScreen(
+ onNavigateBack = {},
+ initialRoute = initialRoute,
+ screenModel = screenModel,
+ )
+ }
+ }
+ }
+
+ private fun createSingleSimState(): SettingsUiState {
+ return SettingsUiState(
+ appSettings = AppSettingsUiState(
+ isDefaultSmsApp = true,
+ defaultSmsAppLabel = "Messaging",
+ sendSoundEnabled = true,
+ ),
+ subscriptionSettings = listOf(
+ SubscriptionSettingsUiState(
+ subId = 1,
+ displayName = "Advanced Settings",
+ displayDetail = "+1234567890",
+ ),
+ ),
+ isMultiSim = false,
+ )
+ }
+
+ private fun createMultiSimState(): SettingsUiState {
+ return SettingsUiState(
+ appSettings = AppSettingsUiState(
+ isDefaultSmsApp = true,
+ defaultSmsAppLabel = "Messaging",
+ sendSoundEnabled = true,
+ ),
+ subscriptionSettings = listOf(
+ SubscriptionSettingsUiState(
+ subId = 1,
+ displayName = "SIM 1",
+ displayDetail = "+1234567890",
+ ),
+ SubscriptionSettingsUiState(
+ subId = 2,
+ displayName = "SIM 2",
+ displayDetail = "+0987654321",
+ ),
+ ),
+ isMultiSim = true,
+ )
+ }
+
+ private class TestLifecycleOwner(
+ initialState: Lifecycle.State,
+ ) : LifecycleOwner {
+ private val lifecycleRegistry = LifecycleRegistry(this)
+
+ init {
+ lifecycleRegistry.currentState = initialState
+ }
+
+ override val lifecycle: Lifecycle
+ get() = lifecycleRegistry
+
+ fun moveTo(state: Lifecycle.State) {
+ lifecycleRegistry.currentState = state
+ }
+ }
+}
diff --git a/app/src/androidTest/java/com/android/messaging/ui/appsettings/subscription/ui/SubscriptionSettingsScreenTest.kt b/app/src/androidTest/java/com/android/messaging/ui/appsettings/subscription/ui/SubscriptionSettingsScreenTest.kt
new file mode 100644
index 00000000..cb146b5f
--- /dev/null
+++ b/app/src/androidTest/java/com/android/messaging/ui/appsettings/subscription/ui/SubscriptionSettingsScreenTest.kt
@@ -0,0 +1,306 @@
+package com.android.messaging.ui.appsettings.subscription.ui
+
+import androidx.activity.ComponentActivity
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import com.android.messaging.R
+import com.android.messaging.ui.appsettings.screen.SettingsScreenModel
+import com.android.messaging.ui.appsettings.screen.model.SettingsAction as Action
+import com.android.messaging.ui.appsettings.subscription.model.SubscriptionSettingsUiState
+import com.android.messaging.ui.core.AppTheme
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class SubscriptionSettingsScreenTest {
+
+ @get:Rule
+ val composeTestRule = createAndroidComposeRule()
+
+ private lateinit var screenModel: SettingsScreenModel
+
+ @Before
+ fun setup() {
+ screenModel = mockk(relaxed = true)
+ }
+
+ @Test
+ fun mmsCategoryHeader_isDisplayed() {
+ setContent(subscriptionSettings = createDefaultSubscription())
+
+ val mmsTitle = composeTestRule.activity.getString(
+ R.string.mms_messaging_category_pref_title,
+ )
+ composeTestRule.onNodeWithText(mmsTitle).assertIsDisplayed()
+ }
+
+ @Test
+ fun groupMms_shownWhenSupported() {
+ val sub = createDefaultSubscription(isGroupMmsSupported = true)
+ setContent(subscriptionSettings = sub)
+
+ val groupMmsTitle = composeTestRule.activity.getString(R.string.group_mms_pref_title)
+ composeTestRule.onNodeWithText(groupMmsTitle).assertIsDisplayed()
+ }
+
+ @Test
+ fun groupMms_hiddenWhenNotSupported() {
+ val sub = createDefaultSubscription(isGroupMmsSupported = false)
+ setContent(subscriptionSettings = sub)
+
+ val groupMmsTitle = composeTestRule.activity.getString(R.string.group_mms_pref_title)
+ composeTestRule.onNodeWithText(groupMmsTitle).assertDoesNotExist()
+ }
+
+ @Test
+ fun groupMmsClick_showsDialog() {
+ val sub = createDefaultSubscription(
+ isGroupMmsSupported = true,
+ isDefaultSmsApp = true,
+ )
+ setContent(subscriptionSettings = sub)
+
+ val groupMmsTitle = composeTestRule.activity.getString(R.string.group_mms_pref_title)
+ composeTestRule.onNodeWithText(groupMmsTitle).performClick()
+ composeTestRule.waitForIdle()
+
+ val disableLabel = composeTestRule.activity.getString(R.string.disable_group_mms)
+ composeTestRule.onNodeWithText(disableLabel).assertIsDisplayed()
+
+ val okText = composeTestRule.activity.getString(android.R.string.ok)
+ composeTestRule.onNodeWithText(okText).assertIsDisplayed()
+
+ val cancelText = composeTestRule.activity.getString(android.R.string.cancel)
+ composeTestRule.onNodeWithText(cancelText).assertIsDisplayed()
+ }
+
+ @Test
+ fun phoneNumberItem_displaysCurrentNumber() {
+ val sub = createDefaultSubscription(displayDetail = "+1234567890")
+ setContent(subscriptionSettings = sub)
+
+ composeTestRule.onNodeWithText("+1234567890").assertIsDisplayed()
+ }
+
+ @Test
+ fun phoneNumberClick_showsDialog() {
+ val sub = createDefaultSubscription(phoneNumber = "+1234567890")
+ setContent(subscriptionSettings = sub)
+
+ val phoneTitle = composeTestRule.activity.getString(R.string.mms_phone_number_pref_title)
+ composeTestRule.onNodeWithText(phoneTitle).performClick()
+ composeTestRule.waitForIdle()
+
+ val okText = composeTestRule.activity.getString(android.R.string.ok)
+ composeTestRule.onNodeWithText(okText).assertIsDisplayed()
+ }
+
+ @Test
+ fun autoRetrieveMms_toggleDelegatesToScreenModel() {
+ val sub = createDefaultSubscription(
+ isDefaultSmsApp = true,
+ autoRetrieveMms = true,
+ )
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(R.string.auto_retrieve_mms_pref_title)
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.AutoRetrieveMmsChanged(1, false))
+ }
+ }
+
+ @Test
+ fun autoRetrieveMmsWhenRoaming_disabledWhenAutoRetrieveOff() {
+ val sub = createDefaultSubscription(
+ isDefaultSmsApp = true,
+ autoRetrieveMms = false,
+ )
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(
+ R.string.auto_retrieve_mms_when_roaming_pref_title,
+ )
+ composeTestRule.onNodeWithText(title).assertIsNotEnabled()
+ }
+
+ @Test
+ fun autoRetrieveMmsWhenRoaming_enabledWhenAutoRetrieveOn() {
+ val sub = createDefaultSubscription(
+ isDefaultSmsApp = true,
+ autoRetrieveMms = true,
+ )
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(
+ R.string.auto_retrieve_mms_when_roaming_pref_title,
+ )
+ composeTestRule.onNodeWithText(title).assertIsEnabled()
+ }
+
+ @Test
+ fun deliveryReports_shownWhenSupported() {
+ val sub = createDefaultSubscription(isDeliveryReportsSupported = true)
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(R.string.delivery_reports_pref_title)
+ composeTestRule.onNodeWithText(title).assertIsDisplayed()
+ }
+
+ @Test
+ fun deliveryReports_hiddenWhenNotSupported() {
+ val sub = createDefaultSubscription(isDeliveryReportsSupported = false)
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(R.string.delivery_reports_pref_title)
+ composeTestRule.onNodeWithText(title).assertDoesNotExist()
+ }
+
+ @Test
+ fun deliveryReportsToggle_delegatesToScreenModel() {
+ val sub = createDefaultSubscription(
+ isDeliveryReportsSupported = true,
+ isDefaultSmsApp = true,
+ deliveryReportsEnabled = false,
+ )
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(R.string.delivery_reports_pref_title)
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.DeliveryReportsChanged(1, true))
+ }
+ }
+
+ @Test
+ fun wirelessAlerts_shownWhenSupported() {
+ val sub = createDefaultSubscription(isWirelessAlertsSupported = true)
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(R.string.wireless_alerts_title)
+ composeTestRule.onNodeWithText(title).assertIsDisplayed()
+ }
+
+ @Test
+ fun wirelessAlerts_hiddenWhenNotSupported() {
+ val sub = createDefaultSubscription(isWirelessAlertsSupported = false)
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(R.string.wireless_alerts_title)
+ composeTestRule.onNodeWithText(title).assertDoesNotExist()
+ }
+
+ @Test
+ fun wirelessAlertsClick_delegatesToScreenModel() {
+ val sub = createDefaultSubscription(isWirelessAlertsSupported = true)
+ setContent(subscriptionSettings = sub)
+
+ val title = composeTestRule.activity.getString(R.string.wireless_alerts_title)
+ composeTestRule.onNodeWithText(title).performClick()
+
+ verify(exactly = 1) {
+ screenModel.onAction(Action.WirelessAlertsClicked(1))
+ }
+ }
+
+ @Test
+ fun advancedCategory_shownWhenDeliveryReportsOrWirelessAlertsSupported() {
+ val sub = createDefaultSubscription(
+ isDeliveryReportsSupported = true,
+ isWirelessAlertsSupported = false,
+ )
+ setContent(subscriptionSettings = sub)
+
+ val advancedTitle =
+ composeTestRule.activity.getString(R.string.advanced_category_pref_title)
+ composeTestRule.onNodeWithText(advancedTitle).assertIsDisplayed()
+ }
+
+ @Test
+ fun advancedCategory_hiddenWhenNeitherSupported() {
+ val sub = createDefaultSubscription(
+ isDeliveryReportsSupported = false,
+ isWirelessAlertsSupported = false,
+ )
+ setContent(subscriptionSettings = sub)
+
+ val advancedTitle =
+ composeTestRule.activity.getString(R.string.advanced_category_pref_title)
+ composeTestRule.onNodeWithText(advancedTitle).assertDoesNotExist()
+ }
+
+ @Test
+ fun settingsDisabled_whenNotDefaultSmsApp() {
+ val sub = createDefaultSubscription(
+ isDefaultSmsApp = false,
+ isGroupMmsSupported = true,
+ isDeliveryReportsSupported = true,
+ )
+ setContent(subscriptionSettings = sub)
+
+ val groupMmsTitle = composeTestRule.activity.getString(R.string.group_mms_pref_title)
+ composeTestRule.onNodeWithText(groupMmsTitle).assertIsNotEnabled()
+
+ val autoRetrieveTitle = composeTestRule.activity.getString(
+ R.string.auto_retrieve_mms_pref_title,
+ )
+ composeTestRule.onNodeWithText(autoRetrieveTitle).assertIsNotEnabled()
+
+ val deliveryTitle = composeTestRule.activity.getString(R.string.delivery_reports_pref_title)
+ composeTestRule.onNodeWithText(deliveryTitle).assertIsNotEnabled()
+ }
+
+ private fun setContent(
+ subscriptionSettings: SubscriptionSettingsUiState = createDefaultSubscription(),
+ ) {
+ composeTestRule.setContent {
+ AppTheme {
+ SubscriptionSettingsScreen(
+ subscriptionSettings = subscriptionSettings,
+ title = "Advanced Settings",
+ screenModel = screenModel,
+ onNavigateBack = {},
+ )
+ }
+ }
+ }
+
+ private fun createDefaultSubscription(
+ subId: Int = 1,
+ displayDetail: String = "+1234567890",
+ phoneNumber: String = "+1234567890",
+ defaultPhoneNumber: String = "+1234567890",
+ isGroupMmsSupported: Boolean = false,
+ isGroupMmsEnabled: Boolean = true,
+ autoRetrieveMms: Boolean = true,
+ autoRetrieveMmsWhenRoaming: Boolean = false,
+ isDeliveryReportsSupported: Boolean = false,
+ deliveryReportsEnabled: Boolean = false,
+ isWirelessAlertsSupported: Boolean = false,
+ isDefaultSmsApp: Boolean = true,
+ ): SubscriptionSettingsUiState {
+ return SubscriptionSettingsUiState(
+ subId = subId,
+ displayName = "SIM 1",
+ displayDetail = displayDetail,
+ phoneNumber = phoneNumber,
+ defaultPhoneNumber = defaultPhoneNumber,
+ isGroupMmsSupported = isGroupMmsSupported,
+ isGroupMmsEnabled = isGroupMmsEnabled,
+ autoRetrieveMms = autoRetrieveMms,
+ autoRetrieveMmsWhenRoaming = autoRetrieveMmsWhenRoaming,
+ isDeliveryReportsSupported = isDeliveryReportsSupported,
+ deliveryReportsEnabled = deliveryReportsEnabled,
+ isWirelessAlertsSupported = isWirelessAlertsSupported,
+ isDefaultSmsApp = isDefaultSmsApp,
+ )
+ }
+}
diff --git a/app/src/androidTest/java/com/android/messaging/ui/conversation/ConversationUserFlowTest.kt b/app/src/androidTest/java/com/android/messaging/ui/conversation/ConversationUserFlowTest.kt
new file mode 100644
index 00000000..981c40ca
--- /dev/null
+++ b/app/src/androidTest/java/com/android/messaging/ui/conversation/ConversationUserFlowTest.kt
@@ -0,0 +1,76 @@
+package com.android.messaging.ui.conversation
+
+import android.os.ParcelFileDescriptor
+import android.widget.EditText
+import android.widget.ImageButton
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.core.app.ActivityScenario
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.contrib.RecyclerViewActions
+import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.messaging.R
+import com.android.messaging.debug.seedTestData
+import com.android.messaging.ui.conversationlist.ConversationListActivity
+import org.hamcrest.Matchers.allOf
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ConversationUserFlowTest {
+
+ @Before
+ fun setUpDefaultSmsApp() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val packageName = instrumentation.targetContext.packageName
+ val command = "cmd role add-role-holder android.app.role.SMS $packageName"
+ val parcelFileDescriptor = instrumentation.uiAutomation.executeShellCommand(command)
+
+ ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor).use { inputStream ->
+ val result = String(inputStream.readBytes())
+ println("Role assignment result: $result")
+ }
+ }
+
+ @Test
+ fun conversationListToConversation_uiElementsArePresent() {
+ val scenario = ActivityScenario.launch(
+ ConversationListActivity::class.java,
+ )
+
+ onView(withId(android.R.id.list))
+ .check(matches(isDisplayed()))
+
+ onView(withId(R.id.start_new_conversation_button))
+ .check(matches(isDisplayed()))
+
+ onView(withId(android.R.id.list))
+ .perform(
+ RecyclerViewActions.actionOnItemAtPosition(0, click()),
+ )
+
+ onView(allOf(withId(R.id.compose_message_text), isAssignableFrom(EditText::class.java)))
+ .check(matches(isDisplayed()))
+
+ onView(allOf(withId(R.id.attach_media_button), isAssignableFrom(ImageButton::class.java)))
+ .check(matches(isDisplayed()))
+
+ scenario.close()
+ }
+
+ companion object {
+ @JvmStatic
+ @BeforeClass
+ fun seedOnce() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ seedTestData(context)
+ }
+ }
+}
diff --git a/app/src/test/java/com/android/messaging/testutil/MainDispatcherRule.kt b/app/src/test/java/com/android/messaging/testutil/MainDispatcherRule.kt
new file mode 100644
index 00000000..6780db1c
--- /dev/null
+++ b/app/src/test/java/com/android/messaging/testutil/MainDispatcherRule.kt
@@ -0,0 +1,24 @@
+package com.android.messaging.testutil
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class MainDispatcherRule(
+ val testDispatcher: TestDispatcher = StandardTestDispatcher(),
+) : TestWatcher() {
+
+ override fun starting(description: Description) {
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ override fun finished(description: Description) {
+ Dispatchers.resetMain()
+ }
+}
diff --git a/app/src/test/java/com/android/messaging/ui/appsettings/screen/SettingsViewModelTest.kt b/app/src/test/java/com/android/messaging/ui/appsettings/screen/SettingsViewModelTest.kt
new file mode 100644
index 00000000..8479b559
--- /dev/null
+++ b/app/src/test/java/com/android/messaging/ui/appsettings/screen/SettingsViewModelTest.kt
@@ -0,0 +1,356 @@
+package com.android.messaging.ui.appsettings.screen
+
+import app.cash.turbine.test
+import com.android.messaging.testutil.MainDispatcherRule
+import com.android.messaging.ui.appsettings.general.delegate.AppSettingsDelegate
+import com.android.messaging.ui.appsettings.general.model.AppSettingsUiState
+import com.android.messaging.ui.appsettings.screen.model.SettingsAction as Action
+import com.android.messaging.ui.appsettings.screen.model.SettingsScreenEffect
+import com.android.messaging.ui.appsettings.screen.model.SettingsUiState
+import com.android.messaging.ui.appsettings.subscription.delegate.SubscriptionSettingsDelegate
+import com.android.messaging.ui.appsettings.subscription.delegate.SubscriptionSettingsState
+import com.android.messaging.ui.appsettings.subscription.model.SubscriptionSettingsUiState
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(RobolectricTestRunner::class)
+class SettingsViewModelTest {
+
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
+ @Test
+ fun init_bindsAllDelegates() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val appDelegate = FakeAppSettingsDelegate()
+ val subDelegate = FakeSubscriptionSettingsDelegate()
+
+ createViewModel(
+ appSettingsDelegate = appDelegate,
+ subscriptionSettingsDelegate = subDelegate,
+ )
+ advanceUntilIdle()
+
+ assertEquals(1, appDelegate.bindCalls)
+ assertEquals(1, subDelegate.bindCalls)
+ }
+ }
+
+ @Test
+ fun uiState_combinesDelegateStates() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val appDelegate = FakeAppSettingsDelegate()
+ val subDelegate = FakeSubscriptionSettingsDelegate()
+ val viewModel = createViewModel(
+ appSettingsDelegate = appDelegate,
+ subscriptionSettingsDelegate = subDelegate,
+ )
+
+ val appState = AppSettingsUiState(
+ isDefaultSmsApp = true,
+ defaultSmsAppLabel = "Messaging",
+ sendSoundEnabled = false,
+ )
+ val subscription = SubscriptionSettingsUiState(
+ subId = 1,
+ displayName = "SIM 1",
+ )
+ appDelegate.stateFlow.value = appState
+ subDelegate.stateFlow.value = SubscriptionSettingsState(
+ subscriptions = listOf(subscription),
+ isMultiSim = false,
+ )
+
+ viewModel.uiState.test {
+ assertEquals(SettingsUiState(), awaitItem())
+
+ val mappedState = awaitItem()
+ assertEquals(appState, mappedState.appSettings)
+ assertEquals(listOf(subscription), mappedState.subscriptionSettings)
+ assertEquals(false, mappedState.isMultiSim)
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+ }
+
+ @Test
+ fun refreshState_refreshesBothDelegates() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val appDelegate = FakeAppSettingsDelegate()
+ val subDelegate = FakeSubscriptionSettingsDelegate()
+ val viewModel = createViewModel(
+ appSettingsDelegate = appDelegate,
+ subscriptionSettingsDelegate = subDelegate,
+ )
+
+ viewModel.refreshState()
+
+ assertEquals(1, appDelegate.refreshCalls)
+ assertEquals(1, subDelegate.refreshCalls)
+ }
+ }
+
+ @Test
+ fun onSendSoundChanged_delegatesToAppSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val appDelegate = FakeAppSettingsDelegate()
+ val viewModel = createViewModel(appSettingsDelegate = appDelegate)
+
+ viewModel.onAction(Action.SendSoundChanged(enabled = false))
+
+ assertEquals(listOf(false), appDelegate.sendSoundChanges)
+ }
+ }
+
+ @Test
+ fun onDumpSmsChanged_delegatesToAppSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val appDelegate = FakeAppSettingsDelegate()
+ val viewModel = createViewModel(appSettingsDelegate = appDelegate)
+
+ viewModel.onAction(Action.DumpSmsChanged(enabled = true))
+
+ assertEquals(listOf(true), appDelegate.dumpSmsChanges)
+ }
+ }
+
+ @Test
+ fun onDumpMmsChanged_delegatesToAppSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val appDelegate = FakeAppSettingsDelegate()
+ val viewModel = createViewModel(appSettingsDelegate = appDelegate)
+
+ viewModel.onAction(Action.DumpMmsChanged(enabled = true))
+
+ assertEquals(listOf(true), appDelegate.dumpMmsChanges)
+ }
+ }
+
+ @Test
+ fun onGroupMmsChanged_delegatesToSubscriptionSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val subDelegate = FakeSubscriptionSettingsDelegate()
+ val viewModel = createViewModel(subscriptionSettingsDelegate = subDelegate)
+
+ viewModel.onAction(Action.GroupMmsChanged(subId = 1, enabled = false))
+
+ assertEquals(listOf(1 to false), subDelegate.groupMmsChanges)
+ }
+ }
+
+ @Test
+ fun onPhoneNumberChanged_delegatesToSubscriptionSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val subDelegate = FakeSubscriptionSettingsDelegate()
+ val viewModel = createViewModel(subscriptionSettingsDelegate = subDelegate)
+
+ viewModel.onAction(Action.PhoneNumberChanged(subId = 1, phoneNumber = "+1555000111"))
+
+ assertEquals(listOf(1 to "+1555000111"), subDelegate.phoneNumberChanges)
+ }
+ }
+
+ @Test
+ fun onAutoRetrieveMmsChanged_delegatesToSubscriptionSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val subDelegate = FakeSubscriptionSettingsDelegate()
+ val viewModel = createViewModel(subscriptionSettingsDelegate = subDelegate)
+
+ viewModel.onAction(Action.AutoRetrieveMmsChanged(subId = 2, enabled = true))
+
+ assertEquals(listOf(2 to true), subDelegate.autoRetrieveMmsChanges)
+ }
+ }
+
+ @Test
+ fun onAutoRetrieveMmsWhenRoamingChanged_delegatesToSubscriptionSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val subDelegate = FakeSubscriptionSettingsDelegate()
+ val viewModel = createViewModel(subscriptionSettingsDelegate = subDelegate)
+
+ viewModel.onAction(Action.AutoRetrieveMmsWhenRoamingChanged(subId = 1, enabled = true))
+
+ assertEquals(listOf(1 to true), subDelegate.autoRetrieveMmsWhenRoamingChanges)
+ }
+ }
+
+ @Test
+ fun onDeliveryReportsChanged_delegatesToSubscriptionSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val subDelegate = FakeSubscriptionSettingsDelegate()
+ val viewModel = createViewModel(subscriptionSettingsDelegate = subDelegate)
+
+ viewModel.onAction(Action.DeliveryReportsChanged(subId = 1, enabled = true))
+
+ assertEquals(listOf(1 to true), subDelegate.deliveryReportsChanges)
+ }
+ }
+
+ @Test
+ fun onDefaultSmsAppClick_whenDefault_emitsOpenManageDefaultApps() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val viewModel = createViewModel()
+ advanceUntilIdle()
+
+ viewModel.effects.test {
+ viewModel.onAction(Action.DefaultSmsAppClicked(isCurrentlyDefault = true))
+
+ assertEquals(SettingsScreenEffect.OpenManageDefaultApps, awaitItem())
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+ }
+
+ @Test
+ fun onDefaultSmsAppClick_whenNotDefault_emitsRequestDefaultSmsApp() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val viewModel = createViewModel()
+ advanceUntilIdle()
+
+ viewModel.effects.test {
+ viewModel.onAction(Action.DefaultSmsAppClicked(isCurrentlyDefault = false))
+
+ assertEquals(SettingsScreenEffect.RequestDefaultSmsApp, awaitItem())
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+ }
+
+ @Test
+ fun onNotificationsClick_emitsOpenNotificationSettings() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val viewModel = createViewModel()
+ advanceUntilIdle()
+
+ viewModel.effects.test {
+ viewModel.onAction(Action.NotificationsClicked)
+
+ assertEquals(SettingsScreenEffect.OpenNotificationSettings, awaitItem())
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+ }
+
+ @Test
+ fun onWirelessAlertsClick_emitsOpenWirelessAlerts() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val viewModel = createViewModel()
+ advanceUntilIdle()
+
+ viewModel.effects.test {
+ viewModel.onAction(Action.WirelessAlertsClicked(subId = 1))
+
+ assertEquals(SettingsScreenEffect.OpenWirelessAlerts(subId = 1), awaitItem())
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+ }
+
+ @Test
+ fun onLicensesClick_emitsOpenLicenses() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val viewModel = createViewModel()
+ advanceUntilIdle()
+
+ viewModel.effects.test {
+ viewModel.onAction(Action.LicensesClicked)
+
+ assertEquals(SettingsScreenEffect.OpenLicenses, awaitItem())
+ cancelAndIgnoreRemainingEvents()
+ }
+ }
+ }
+
+ private fun createViewModel(
+ appSettingsDelegate: AppSettingsDelegate = FakeAppSettingsDelegate(),
+ subscriptionSettingsDelegate: SubscriptionSettingsDelegate =
+ FakeSubscriptionSettingsDelegate(),
+ ): SettingsViewModel {
+ return SettingsViewModel(
+ appSettingsDelegate = appSettingsDelegate,
+ subscriptionSettingsDelegate = subscriptionSettingsDelegate,
+ )
+ }
+
+ private class FakeAppSettingsDelegate : AppSettingsDelegate {
+ val stateFlow = MutableStateFlow(AppSettingsUiState())
+ override val state: StateFlow = stateFlow
+
+ var bindCalls = 0
+ var refreshCalls = 0
+ val sendSoundChanges = mutableListOf()
+ val dumpSmsChanges = mutableListOf()
+ val dumpMmsChanges = mutableListOf()
+
+ override fun bind(scope: CoroutineScope) {
+ bindCalls += 1
+ }
+
+ override fun refresh() {
+ refreshCalls += 1
+ }
+
+ override fun onSendSoundChanged(enabled: Boolean) {
+ sendSoundChanges += enabled
+ }
+
+ override fun onDumpSmsChanged(enabled: Boolean) {
+ dumpSmsChanges += enabled
+ }
+
+ override fun onDumpMmsChanged(enabled: Boolean) {
+ dumpMmsChanges += enabled
+ }
+ }
+
+ private class FakeSubscriptionSettingsDelegate : SubscriptionSettingsDelegate {
+ val stateFlow = MutableStateFlow(SubscriptionSettingsState())
+ override val state: StateFlow = stateFlow
+
+ var bindCalls = 0
+ var refreshCalls = 0
+ val groupMmsChanges = mutableListOf>()
+ val phoneNumberChanges = mutableListOf>()
+ val autoRetrieveMmsChanges = mutableListOf>()
+ val autoRetrieveMmsWhenRoamingChanges = mutableListOf>()
+ val deliveryReportsChanges = mutableListOf>()
+
+ override fun bind(scope: CoroutineScope) {
+ bindCalls += 1
+ }
+
+ override fun refresh() {
+ refreshCalls += 1
+ }
+
+ override fun onGroupMmsChanged(subId: Int, enabled: Boolean) {
+ groupMmsChanges += subId to enabled
+ }
+
+ override fun onPhoneNumberChanged(subId: Int, phoneNumber: String) {
+ phoneNumberChanges += subId to phoneNumber
+ }
+
+ override fun onAutoRetrieveMmsChanged(subId: Int, enabled: Boolean) {
+ autoRetrieveMmsChanges += subId to enabled
+ }
+
+ override fun onAutoRetrieveMmsWhenRoamingChanged(subId: Int, enabled: Boolean) {
+ autoRetrieveMmsWhenRoamingChanges += subId to enabled
+ }
+
+ override fun onDeliveryReportsChanged(subId: Int, enabled: Boolean) {
+ deliveryReportsChanges += subId to enabled
+ }
+ }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 99fe9844..6cfa7ff0 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -27,6 +27,10 @@ mockk = "1.14.9"
robolectric = "4.16.1"
turbine = "1.2.1"
+androidx-test-espresso = "3.7.0"
+androidx-test-ext-junit = "1.3.0"
+androidx-test-runner = "1.7.0"
+
[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
@@ -77,6 +81,12 @@ hilt-gradle-plugin = { module = "com.google.dagger:hilt-android-gradle-plugin",
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
ksp-gradle-plugin = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
+androidx-test-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "androidx-test-espresso" }
+androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" }
+androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext-junit" }
+androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-runner" }
+androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
+
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 96f92386..687ec417 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -86,6 +86,11 @@
+
+
+
+
+
@@ -100,9 +105,6 @@
-
-
-
@@ -143,9 +145,6 @@
-
-
-
@@ -206,9 +205,6 @@
-
-
-
@@ -254,13 +250,21 @@
+
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -282,17 +286,11 @@
-
-
-
-
-
-
@@ -309,9 +307,6 @@
-
-
-
@@ -322,9 +317,6 @@
-
-
-
@@ -808,9 +800,6 @@
-
-
-
@@ -823,6 +812,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -848,6 +870,11 @@
+
+
+
+
+
@@ -910,9 +937,6 @@
-
-
-
@@ -965,9 +989,6 @@
-
-
-
@@ -1017,9 +1038,6 @@
-
-
-
@@ -1137,9 +1155,6 @@
-
-
-
@@ -1155,9 +1170,6 @@
-
-
-
@@ -1174,9 +1186,6 @@
-
-
-
@@ -1239,9 +1248,6 @@
-
-
-
@@ -1296,9 +1302,6 @@
-
-
-
@@ -1320,9 +1323,6 @@
-
-
-
@@ -1339,9 +1339,6 @@
-
-
-
@@ -1406,9 +1403,6 @@
-
-
-
@@ -1425,9 +1419,6 @@
-
-
-
@@ -1484,9 +1475,6 @@
-
-
-
@@ -1531,9 +1519,6 @@
-
-
-
@@ -1613,9 +1598,6 @@
-
-
-
@@ -1632,9 +1614,6 @@
-
-
-
@@ -1718,12 +1697,6 @@
-
-
-
-
-
-
@@ -1743,9 +1716,6 @@
-
-
-
@@ -1756,9 +1726,6 @@
-
-
-
@@ -1786,9 +1753,6 @@
-
-
-
@@ -1801,53 +1765,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
-
-
+
+
@@ -1861,46 +1787,48 @@
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
+
+
-
-
-
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
@@ -1914,32 +1842,26 @@
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
+
+
-
-
-
-
-
-
+
+
+
-
-
+
+
-
-
+
+
@@ -1953,6 +1875,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -1964,13 +1897,21 @@
+
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -2027,13 +1968,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -2283,6 +2243,14 @@
+
+
+
+
+
+
+
+
@@ -2463,9 +2431,6 @@
-
-
-
@@ -2563,9 +2528,6 @@
-
-
-
@@ -2574,9 +2536,6 @@
-
-
-
@@ -2585,9 +2544,6 @@
-
-
-
@@ -2596,9 +2552,6 @@
-
-
-
@@ -2607,9 +2560,6 @@
-
-
-
@@ -2618,9 +2568,6 @@
-
-
-
@@ -2629,9 +2576,6 @@
-
-
-
@@ -2640,9 +2584,6 @@
-
-
-
@@ -2651,9 +2592,6 @@
-
-
-
@@ -2662,9 +2600,6 @@
-
-
-
@@ -2673,9 +2608,6 @@
-
-
-
@@ -2684,9 +2616,6 @@
-
-
-
@@ -2695,9 +2624,6 @@
-
-
-
@@ -2706,9 +2632,6 @@
-
-
-
@@ -2717,9 +2640,6 @@
-
-
-
@@ -2728,9 +2648,6 @@
-
-
-
@@ -2739,9 +2656,6 @@
-
-
-
@@ -2761,9 +2675,6 @@
-
-
-
@@ -2831,6 +2742,11 @@
+
+
+
+
+
@@ -2852,11 +2768,24 @@
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2874,9 +2803,6 @@
-
-
-
@@ -2885,9 +2811,6 @@
-
-
-
@@ -2901,9 +2824,6 @@
-
-
-
@@ -2917,9 +2837,6 @@
-
-
-
@@ -2985,9 +2902,6 @@
-
-
-
@@ -2999,12 +2913,6 @@
-
-
-
-
-
-
@@ -3028,9 +2936,6 @@
-
-
-
@@ -3061,9 +2966,6 @@
-
-
-
@@ -3259,9 +3161,6 @@
-
-
-
@@ -3270,9 +3169,6 @@
-
-
-
@@ -3284,12 +3180,6 @@
-
-
-
-
-
-
@@ -3366,12 +3256,6 @@
-
-
-
-
-
-
@@ -3380,9 +3264,6 @@
-
-
-
@@ -3503,9 +3384,6 @@
-
-
-
@@ -3531,9 +3409,6 @@
-
-
-
@@ -3570,9 +3445,6 @@
-
-
-
@@ -3589,9 +3461,6 @@
-
-
-
@@ -3619,9 +3488,6 @@
-
-
-
@@ -3630,8 +3496,24 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3661,9 +3543,6 @@
-
-
-
@@ -3672,9 +3551,6 @@
-
-
-
@@ -3683,9 +3559,6 @@
-
-
-
@@ -3694,9 +3567,6 @@
-
-
-
@@ -3705,9 +3575,6 @@
-
-
-
@@ -3716,9 +3583,6 @@
-
-
-
@@ -3936,20 +3800,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -4002,9 +3852,6 @@
-
-
-
@@ -4036,9 +3883,6 @@
-
-
-
@@ -4077,9 +3921,6 @@
-
-
-
@@ -4320,9 +4161,6 @@
-
-
-
@@ -4450,9 +4288,6 @@
-
-
-
@@ -4474,9 +4309,6 @@
-
-
-
@@ -4493,9 +4325,6 @@
-
-
-
@@ -4512,9 +4341,6 @@
-
-
-
@@ -4531,9 +4357,6 @@
-
-
-
@@ -4550,9 +4373,6 @@
-
-
-
@@ -4561,9 +4381,6 @@
-
-
-
@@ -4580,9 +4397,6 @@
-
-
-
@@ -4591,9 +4405,6 @@
-
-
-
@@ -4698,9 +4509,6 @@
-
-
-
@@ -4717,9 +4525,6 @@
-
-
-
@@ -4736,9 +4541,6 @@
-
-
-
@@ -4755,9 +4557,6 @@
-
-
-
@@ -4774,9 +4573,6 @@
-
-
-
@@ -4793,9 +4589,6 @@
-
-
-
@@ -4812,9 +4605,6 @@
-
-
-
@@ -4831,9 +4621,6 @@
-
-
-
@@ -4860,9 +4647,6 @@
-
-
-
@@ -4879,9 +4663,6 @@
-
-
-
@@ -4898,9 +4679,6 @@
-
-
-
@@ -4917,9 +4695,6 @@
-
-
-
@@ -4928,9 +4703,6 @@
-
-
-
@@ -4947,9 +4719,6 @@
-
-
-
@@ -4958,9 +4727,6 @@
-
-
-
@@ -4986,9 +4752,6 @@
-
-
-
@@ -5085,9 +4848,6 @@
-
-
-
@@ -5099,9 +4859,6 @@
-
-
-
@@ -5132,9 +4889,6 @@
-
-
-
@@ -5229,12 +4983,6 @@
-
-
-
-
-
-
@@ -5268,9 +5016,6 @@
-
-
-
@@ -5282,9 +5027,6 @@
-
-
-
@@ -5307,9 +5049,6 @@
-
-
-
@@ -5321,9 +5060,6 @@
-
-
-
@@ -5349,9 +5085,6 @@
-
-
-
@@ -5364,16 +5097,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
@@ -5398,9 +5139,6 @@
-
-
-
@@ -5412,9 +5150,6 @@
-
-
-
@@ -5431,9 +5166,6 @@
-
-
-
@@ -5509,9 +5241,6 @@
-
-
-
@@ -5523,13 +5252,24 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -5544,18 +5284,18 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -5572,6 +5312,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -5584,9 +5338,6 @@
-
-
-
@@ -5595,9 +5346,6 @@
-
-
-
@@ -5611,9 +5359,6 @@
-
-
-
@@ -5622,9 +5367,6 @@
-
-
-
@@ -5677,49 +5419,31 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -5760,9 +5484,6 @@
-
-
-
@@ -5893,9 +5614,6 @@
-
-
-
@@ -5904,9 +5622,6 @@
-
-
-
@@ -5915,9 +5630,6 @@
-
-
-
@@ -5942,9 +5654,6 @@
-
-
-
@@ -5991,9 +5700,6 @@
-
-
-
@@ -6117,9 +5823,6 @@
-
-
-
@@ -6142,9 +5845,6 @@
-
-
-
@@ -6172,9 +5872,6 @@
-
-
-
@@ -6207,9 +5904,6 @@
-
-
-
@@ -6218,9 +5912,6 @@
-
-
-
@@ -6229,9 +5920,6 @@
-
-
-
@@ -6312,9 +6000,6 @@
-
-
-
@@ -6323,9 +6008,6 @@
-
-
-
@@ -6337,12 +6019,6 @@
-
-
-
-
-
-
@@ -6365,9 +6041,6 @@
-
-
-
@@ -6376,9 +6049,6 @@
-
-
-
@@ -6390,12 +6060,6 @@
-
-
-
-
-
-
@@ -6450,9 +6114,6 @@
-
-
-
@@ -6461,9 +6122,6 @@
-
-
-
@@ -6504,6 +6162,11 @@
+
+
+
+
+
@@ -6513,9 +6176,6 @@
-
-
-
@@ -6531,9 +6191,6 @@
-
-
-
@@ -6553,9 +6210,6 @@
-
-
-
@@ -6576,6 +6230,11 @@
+
+
+
+
+
@@ -6680,6 +6339,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -6725,9 +6398,6 @@
-
-
-
diff --git a/res/layout/group_mms_setting_dialog.xml b/res/layout/group_mms_setting_dialog.xml
deleted file mode 100644
index efc3b004..00000000
--- a/res/layout/group_mms_setting_dialog.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/res/layout/settings_item_view.xml b/res/layout/settings_item_view.xml
deleted file mode 100644
index a434c111..00000000
--- a/res/layout/settings_item_view.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 19ee2ec8..fe9c0723 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -151,11 +151,6 @@
#03a9f4
#4d4d4d
- #4d4d4d
- #6d6d6d
- #333333
- #000000
-
@color/primary_color
#424242
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 59a4148f..a69dd85d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -143,11 +143,6 @@
10dp
5dp
- 16sp
- 14sp
- 60dp
- 14sp
-
40dp
30dp
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fa5884d4..cc9ff474 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -67,10 +67,6 @@
- @color/archived_conversation_action_bar_background_color_dark
-
-
-
-
-
-
-
-