diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1a28947f3..7bd516873 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -91,7 +91,7 @@
diff --git a/app/src/androidTest/kotlin/com/android/common/test/helpers/SmsWarningHelper.kt b/app/src/androidTest/kotlin/com/android/common/test/helpers/SmsWarningHelper.kt
new file mode 100644
index 000000000..572b3567d
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/android/common/test/helpers/SmsWarningHelper.kt
@@ -0,0 +1,28 @@
+package com.android.common.test.helpers
+
+import com.android.messaging.BuildConfig
+import com.android.messaging.util.BuglePrefs
+import com.android.messaging.util.BuglePrefsKeys
+
+object SmsWarningHelper {
+
+ fun acknowledgeSmsWarning(): Int {
+ val previousAcknowledgedVersion = BuglePrefs.getApplicationPrefs().getInt(
+ BuglePrefsKeys.SMS_WARNING_ACKNOWLEDGED_VERSION,
+ BuglePrefsKeys.SMS_WARNING_ACKNOWLEDGED_VERSION_DEFAULT,
+ )
+ BuglePrefs.getApplicationPrefs().putInt(
+ BuglePrefsKeys.SMS_WARNING_ACKNOWLEDGED_VERSION,
+ BuildConfig.VERSION_CODE,
+ )
+
+ return previousAcknowledgedVersion
+ }
+
+ fun restoreSmsWarning(previousAcknowledgedVersion: Int) {
+ BuglePrefs.getApplicationPrefs().putInt(
+ BuglePrefsKeys.SMS_WARNING_ACKNOWLEDGED_VERSION,
+ previousAcknowledgedVersion,
+ )
+ }
+}
diff --git a/app/src/androidTest/kotlin/com/android/common/test/rules/AppTestRule.kt b/app/src/androidTest/kotlin/com/android/common/test/rules/AppTestRule.kt
index c52832f93..843a0c744 100644
--- a/app/src/androidTest/kotlin/com/android/common/test/rules/AppTestRule.kt
+++ b/app/src/androidTest/kotlin/com/android/common/test/rules/AppTestRule.kt
@@ -1,6 +1,7 @@
package com.android.common.test.rules
import com.android.common.test.helpers.ShellCommandHelper
+import com.android.common.test.helpers.SmsWarningHelper
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
@@ -14,6 +15,7 @@ class AppTestRule : TestRule {
return object : Statement() {
override fun evaluate() {
val previousSmsRoleHolders = ShellCommandHelper.setupSmsDefaultRole()
+ val previousAcknowledgedVersion = SmsWarningHelper.acknowledgeSmsWarning()
var baseFailure: Throwable? = null
try {
base.evaluate()
@@ -21,6 +23,9 @@ class AppTestRule : TestRule {
baseFailure = throwable
} finally {
try {
+ SmsWarningHelper.restoreSmsWarning(
+ previousAcknowledgedVersion = previousAcknowledgedVersion,
+ )
ShellCommandHelper.restoreSmsDefaultRole(
previousRoleHolders = previousSmsRoleHolders,
)
diff --git a/app/src/test/java/com/android/messaging/ui/permissioncheck/screen/PermissionCheckViewModelTest.kt b/app/src/test/java/com/android/messaging/ui/onboarding/screen/OnboardingViewModelTest.kt
similarity index 67%
rename from app/src/test/java/com/android/messaging/ui/permissioncheck/screen/PermissionCheckViewModelTest.kt
rename to app/src/test/java/com/android/messaging/ui/onboarding/screen/OnboardingViewModelTest.kt
index 5f5179b29..9d06dc6dc 100644
--- a/app/src/test/java/com/android/messaging/ui/permissioncheck/screen/PermissionCheckViewModelTest.kt
+++ b/app/src/test/java/com/android/messaging/ui/onboarding/screen/OnboardingViewModelTest.kt
@@ -1,17 +1,20 @@
-package com.android.messaging.ui.permissioncheck.screen
+package com.android.messaging.ui.onboarding.screen
import app.cash.turbine.test
-import com.android.messaging.data.permissioncheck.GetMissingPermissionLabels
-import com.android.messaging.data.permissioncheck.RequiredPermissionsChecker
-import com.android.messaging.domain.permissioncheck.model.PermissionRequest
-import com.android.messaging.domain.permissioncheck.usecase.DeterminePermissionRequest
+import com.android.messaging.data.onboarding.GetMissingPermissionLabels
+import com.android.messaging.data.onboarding.RequiredPermissionsChecker
+import com.android.messaging.data.onboarding.store.SmsWarningStore
+import com.android.messaging.domain.onboarding.model.PermissionRequest
+import com.android.messaging.domain.onboarding.usecase.DeterminePermissionRequest
import com.android.messaging.testutil.MainDispatcherRule
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckAction as Action
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckScreenEffect as Effect
-import com.android.messaging.ui.permissioncheck.screen.model.SettingsGuidance
+import com.android.messaging.ui.onboarding.screen.model.OnboardingAction as Action
+import com.android.messaging.ui.onboarding.screen.model.OnboardingScreenEffect as Effect
+import com.android.messaging.ui.onboarding.screen.model.OnboardingUiState as State
+import com.android.messaging.ui.onboarding.screen.model.SettingsGuidance
import com.android.messaging.util.core.ElapsedRealtimeProvider
import io.mockk.every
import io.mockk.mockk
+import io.mockk.verify
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -21,11 +24,93 @@ import org.junit.Rule
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
-class PermissionCheckViewModelTest {
+class OnboardingViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
+ @Test
+ fun initialState_whenWarningNotAcknowledged_isSmsWarning() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val viewModel = createViewModel(
+ smsWarningStore = mockSmsWarningStore(isAcknowledged = false),
+ )
+
+ assertEquals(State.SmsWarning, viewModel.uiState.value)
+ }
+ }
+
+ @Test
+ fun initialState_whenWarningAcknowledged_isPermissions() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val viewModel = createViewModel(
+ smsWarningStore = mockSmsWarningStore(isAcknowledged = true),
+ )
+
+ assertEquals(State.Permissions(), viewModel.uiState.value)
+ }
+ }
+
+ @Test
+ fun onScreenResumed_onSmsWarningStep_whenPermissionsGranted_doesNotRedirect() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val viewModel = createViewModel(
+ checker = mockChecker(hasRequiredPermissions = true),
+ smsWarningStore = mockSmsWarningStore(isAcknowledged = false),
+ )
+
+ viewModel.effects.test {
+ viewModel.onScreenResumed()
+
+ expectNoEvents()
+ cancelAndIgnoreRemainingEvents()
+ }
+
+ assertEquals(State.SmsWarning, viewModel.uiState.value)
+ }
+ }
+
+ @Test
+ fun nextClicked_onSmsWarningStep_whenPermissionsMissing_acknowledgesAndShowsPermissions() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val smsWarningStore = mockSmsWarningStore(isAcknowledged = false)
+ val viewModel = createViewModel(
+ checker = mockChecker(hasRequiredPermissions = false),
+ smsWarningStore = smsWarningStore,
+ )
+
+ viewModel.effects.test {
+ viewModel.onAction(Action.NextClicked)
+
+ expectNoEvents()
+ cancelAndIgnoreRemainingEvents()
+ }
+
+ verify { smsWarningStore.acknowledge() }
+ assertEquals(State.Permissions(), viewModel.uiState.value)
+ }
+ }
+
+ @Test
+ fun nextClicked_onSmsWarningStep_whenPermissionsGranted_acknowledgesAndRedirects() {
+ runTest(context = mainDispatcherRule.testDispatcher) {
+ val smsWarningStore = mockSmsWarningStore(isAcknowledged = false)
+ val viewModel = createViewModel(
+ checker = mockChecker(hasRequiredPermissions = true),
+ smsWarningStore = smsWarningStore,
+ )
+
+ viewModel.effects.test {
+ viewModel.onAction(Action.NextClicked)
+
+ assertEquals(Effect.Redirect, awaitItem())
+ cancelAndIgnoreRemainingEvents()
+ }
+
+ verify { smsWarningStore.acknowledge() }
+ }
+ }
+
@Test
fun onScreenResumed_whenPermissionsGranted_emitsRedirect() {
runTest(context = mainDispatcherRule.testDispatcher) {
@@ -131,7 +216,7 @@ class PermissionCheckViewModelTest {
cancelAndIgnoreRemainingEvents()
}
- assertNull(viewModel.uiState.value.settingsGuidance)
+ assertNull(viewModel.permissionsState().settingsGuidance)
}
}
@@ -158,8 +243,9 @@ class PermissionCheckViewModelTest {
viewModel.onRequestResult()
- assertEquals(SettingsGuidance.DefaultSmsApp, viewModel.uiState.value.settingsGuidance)
- assertEquals(persistentListOf(), viewModel.uiState.value.missingPermissions)
+ val state = viewModel.permissionsState()
+ assertEquals(SettingsGuidance.DefaultSmsApp, state.settingsGuidance)
+ assertEquals(persistentListOf(), state.missingPermissions)
}
}
@@ -190,8 +276,9 @@ class PermissionCheckViewModelTest {
viewModel.onRequestResult()
- assertEquals(SettingsGuidance.Permissions, viewModel.uiState.value.settingsGuidance)
- assertEquals(labels, viewModel.uiState.value.missingPermissions)
+ val state = viewModel.permissionsState()
+ assertEquals(SettingsGuidance.Permissions, state.settingsGuidance)
+ assertEquals(labels, state.missingPermissions)
}
}
@@ -217,7 +304,7 @@ class PermissionCheckViewModelTest {
viewModel.onRequestResult()
- assertNull(viewModel.uiState.value.settingsGuidance)
+ assertNull(viewModel.permissionsState().settingsGuidance)
}
}
@@ -242,7 +329,7 @@ class PermissionCheckViewModelTest {
viewModel.onRequestResult()
- assertNull(viewModel.uiState.value.settingsGuidance)
+ assertNull(viewModel.permissionsState().settingsGuidance)
}
}
@@ -255,15 +342,27 @@ class PermissionCheckViewModelTest {
persistentListOf()
},
elapsedRealtimeProvider: ElapsedRealtimeProvider = ElapsedRealtimeProvider { 0L },
- ): PermissionCheckViewModel {
- return PermissionCheckViewModel(
+ smsWarningStore: SmsWarningStore = mockSmsWarningStore(isAcknowledged = true),
+ ): OnboardingViewModel {
+ return OnboardingViewModel(
checker = checker,
determinePermissionRequest = determinePermissionRequest,
getMissingPermissionLabels = getMissingPermissionLabels,
elapsedRealtimeProvider = elapsedRealtimeProvider,
+ smsWarningStore = smsWarningStore,
)
}
+ private fun OnboardingViewModel.permissionsState(): State.Permissions {
+ return uiState.value as State.Permissions
+ }
+
+ private fun mockSmsWarningStore(isAcknowledged: Boolean): SmsWarningStore {
+ return mockk(relaxed = true).also {
+ every { it.isAcknowledged() } returns isAcknowledged
+ }
+ }
+
private fun mockChecker(
hasRequiredPermissions: Boolean,
isSmsRoleHeld: Boolean = false,
diff --git a/app/src/test/kotlin/com/android/messaging/data/permissioncheck/GetMissingPermissionLabelsImplTest.kt b/app/src/test/kotlin/com/android/messaging/data/onboarding/GetMissingPermissionLabelsImplTest.kt
similarity index 97%
rename from app/src/test/kotlin/com/android/messaging/data/permissioncheck/GetMissingPermissionLabelsImplTest.kt
rename to app/src/test/kotlin/com/android/messaging/data/onboarding/GetMissingPermissionLabelsImplTest.kt
index cd5f55ad3..5833a86fc 100644
--- a/app/src/test/kotlin/com/android/messaging/data/permissioncheck/GetMissingPermissionLabelsImplTest.kt
+++ b/app/src/test/kotlin/com/android/messaging/data/onboarding/GetMissingPermissionLabelsImplTest.kt
@@ -1,4 +1,4 @@
-package com.android.messaging.data.permissioncheck
+package com.android.messaging.data.onboarding
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
diff --git a/app/src/test/kotlin/com/android/messaging/data/onboarding/store/SmsWarningStoreTest.kt b/app/src/test/kotlin/com/android/messaging/data/onboarding/store/SmsWarningStoreTest.kt
new file mode 100644
index 000000000..6b227ffb1
--- /dev/null
+++ b/app/src/test/kotlin/com/android/messaging/data/onboarding/store/SmsWarningStoreTest.kt
@@ -0,0 +1,50 @@
+package com.android.messaging.data.onboarding.store
+
+import com.android.messaging.FactoryTestAccess
+import com.android.messaging.testutil.FakeBuglePrefs
+import com.android.messaging.testutil.installTestFactory
+import io.mockk.mockk
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+
+class SmsWarningStoreTest {
+
+ @Before
+ fun setUp() {
+ installTestFactory(
+ context = mockk(relaxed = true),
+ prefs = FakeBuglePrefs(),
+ )
+ }
+
+ @After
+ fun tearDown() {
+ FactoryTestAccess.reset()
+ }
+
+ @Test
+ fun isAcknowledged_whenNothingStored_returnsFalse() {
+ assertFalse(createStore(versionCode = 42).isAcknowledged())
+ }
+
+ @Test
+ fun isAcknowledged_afterAcknowledgeOnSameBuild_returnsTrue() {
+ createStore(versionCode = 42).acknowledge()
+ assertTrue(createStore(versionCode = 42).isAcknowledged())
+ }
+
+ @Test
+ fun isAcknowledged_afterAcknowledgeOnEarlierBuild_returnsFalse() {
+ createStore(versionCode = 42).acknowledge()
+ assertFalse(createStore(versionCode = 43).isAcknowledged())
+ }
+
+ private fun createStore(versionCode: Int): SmsWarningStore {
+ return SmsWarningStoreImpl(
+ appVersionProvider = { versionCode },
+ )
+ }
+}
diff --git a/app/src/test/kotlin/com/android/messaging/domain/permissioncheck/usecase/DeterminePermissionRequestTest.kt b/app/src/test/kotlin/com/android/messaging/domain/onboarding/usecase/DeterminePermissionRequestTest.kt
similarity index 89%
rename from app/src/test/kotlin/com/android/messaging/domain/permissioncheck/usecase/DeterminePermissionRequestTest.kt
rename to app/src/test/kotlin/com/android/messaging/domain/onboarding/usecase/DeterminePermissionRequestTest.kt
index 60a71fe31..83b818b0f 100644
--- a/app/src/test/kotlin/com/android/messaging/domain/permissioncheck/usecase/DeterminePermissionRequestTest.kt
+++ b/app/src/test/kotlin/com/android/messaging/domain/onboarding/usecase/DeterminePermissionRequestTest.kt
@@ -1,7 +1,7 @@
-package com.android.messaging.domain.permissioncheck.usecase
+package com.android.messaging.domain.onboarding.usecase
-import com.android.messaging.data.permissioncheck.RequiredPermissionsChecker
-import com.android.messaging.domain.permissioncheck.model.PermissionRequest
+import com.android.messaging.data.onboarding.RequiredPermissionsChecker
+import com.android.messaging.domain.onboarding.model.PermissionRequest
import io.mockk.every
import io.mockk.mockk
import kotlinx.collections.immutable.ImmutableList
diff --git a/app/src/test/kotlin/com/android/messaging/domain/onboarding/usecase/ShouldShowOnboardingTest.kt b/app/src/test/kotlin/com/android/messaging/domain/onboarding/usecase/ShouldShowOnboardingTest.kt
new file mode 100644
index 000000000..5ab2bd289
--- /dev/null
+++ b/app/src/test/kotlin/com/android/messaging/domain/onboarding/usecase/ShouldShowOnboardingTest.kt
@@ -0,0 +1,69 @@
+package com.android.messaging.domain.onboarding.usecase
+
+import com.android.messaging.data.onboarding.RequiredPermissionsChecker
+import com.android.messaging.data.onboarding.store.SmsWarningStore
+import io.mockk.every
+import io.mockk.mockk
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class ShouldShowOnboardingTest {
+
+ @Test
+ fun invoke_whenWarningPendingAndPermissionsGranted_returnsTrue() {
+ val useCase = createUseCase(
+ isAcknowledged = false,
+ hasRequiredPermissions = true,
+ )
+
+ assertTrue(useCase())
+ }
+
+ @Test
+ fun invoke_whenWarningAcknowledgedAndPermissionsMissing_returnsTrue() {
+ val useCase = createUseCase(
+ isAcknowledged = true,
+ hasRequiredPermissions = false,
+ )
+
+ assertTrue(useCase())
+ }
+
+ @Test
+ fun invoke_whenWarningPendingAndPermissionsMissing_returnsTrue() {
+ val useCase = createUseCase(
+ isAcknowledged = false,
+ hasRequiredPermissions = false,
+ )
+
+ assertTrue(useCase())
+ }
+
+ @Test
+ fun invoke_whenWarningAcknowledgedAndPermissionsGranted_returnsFalse() {
+ val useCase = createUseCase(
+ isAcknowledged = true,
+ hasRequiredPermissions = true,
+ )
+
+ assertFalse(useCase())
+ }
+
+ private fun createUseCase(
+ isAcknowledged: Boolean,
+ hasRequiredPermissions: Boolean,
+ ): ShouldShowOnboarding {
+ val smsWarningStore = mockk().also {
+ every { it.isAcknowledged() } returns isAcknowledged
+ }
+ val checker = mockk().also {
+ every { it.hasRequiredPermissions() } returns hasRequiredPermissions
+ }
+
+ return ShouldShowOnboardingImpl(
+ checker = checker,
+ smsWarningStore = smsWarningStore,
+ )
+ }
+}
diff --git a/app/src/test/kotlin/com/android/messaging/ui/ActivityPermissionGateTest.kt b/app/src/test/kotlin/com/android/messaging/ui/ActivityPermissionGateTest.kt
index de4f29761..9e7cf5e4d 100644
--- a/app/src/test/kotlin/com/android/messaging/ui/ActivityPermissionGateTest.kt
+++ b/app/src/test/kotlin/com/android/messaging/ui/ActivityPermissionGateTest.kt
@@ -4,7 +4,7 @@ import com.android.messaging.ui.classzero.ClassZeroActivity
import com.android.messaging.ui.contact.AddContactActivity
import com.android.messaging.ui.conversation.LaunchConversationActivity
import com.android.messaging.ui.license.LicenseActivity
-import com.android.messaging.ui.permissioncheck.PermissionCheckActivity
+import com.android.messaging.ui.onboarding.OnboardingActivity
import com.android.messaging.ui.photoviewer.PhotoViewerActivity
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
@@ -24,7 +24,7 @@ internal class ActivityPermissionGateTest {
)
private val intentionallyUngated = setOf>(
- PermissionCheckActivity::class.java,
+ OnboardingActivity::class.java,
LaunchConversationActivity::class.java,
PhotoViewerActivity::class.java,
LicenseActivity::class.java,
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c9c5c2445..2fb29ed9c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -35,6 +35,9 @@
Next
Exit
Settings
+
+ SMS isn\'t private
+ Text messages aren\'t encrypted. Use an app with end-to-end encryption for anything sensitive.
Messaging needs permission to SMS, Phone and Contacts.
diff --git a/src/com/android/messaging/data/permissioncheck/GetMissingPermissionLabels.kt b/src/com/android/messaging/data/onboarding/GetMissingPermissionLabels.kt
similarity index 95%
rename from src/com/android/messaging/data/permissioncheck/GetMissingPermissionLabels.kt
rename to src/com/android/messaging/data/onboarding/GetMissingPermissionLabels.kt
index bd317be8b..4ff22b1ea 100644
--- a/src/com/android/messaging/data/permissioncheck/GetMissingPermissionLabels.kt
+++ b/src/com/android/messaging/data/onboarding/GetMissingPermissionLabels.kt
@@ -1,4 +1,4 @@
-package com.android.messaging.data.permissioncheck
+package com.android.messaging.data.onboarding
import android.content.pm.PackageManager
import javax.inject.Inject
diff --git a/src/com/android/messaging/data/permissioncheck/RequiredPermissionsChecker.kt b/src/com/android/messaging/data/onboarding/RequiredPermissionsChecker.kt
similarity index 94%
rename from src/com/android/messaging/data/permissioncheck/RequiredPermissionsChecker.kt
rename to src/com/android/messaging/data/onboarding/RequiredPermissionsChecker.kt
index 700a08b99..9de843fef 100644
--- a/src/com/android/messaging/data/permissioncheck/RequiredPermissionsChecker.kt
+++ b/src/com/android/messaging/data/onboarding/RequiredPermissionsChecker.kt
@@ -1,4 +1,4 @@
-package com.android.messaging.data.permissioncheck
+package com.android.messaging.data.onboarding
import android.app.role.RoleManager
import com.android.messaging.util.OsUtil
diff --git a/src/com/android/messaging/data/onboarding/store/SmsWarningStore.kt b/src/com/android/messaging/data/onboarding/store/SmsWarningStore.kt
new file mode 100644
index 000000000..28e665a70
--- /dev/null
+++ b/src/com/android/messaging/data/onboarding/store/SmsWarningStore.kt
@@ -0,0 +1,31 @@
+package com.android.messaging.data.onboarding.store
+
+import com.android.messaging.util.BuglePrefs
+import com.android.messaging.util.BuglePrefsKeys
+import com.android.messaging.util.core.AppVersionProvider
+import javax.inject.Inject
+
+internal interface SmsWarningStore {
+ fun isAcknowledged(): Boolean
+ fun acknowledge()
+}
+
+internal class SmsWarningStoreImpl @Inject constructor(
+ private val appVersionProvider: AppVersionProvider,
+) : SmsWarningStore {
+
+ override fun isAcknowledged(): Boolean {
+ val acknowledgedVersion = BuglePrefs.getApplicationPrefs().getInt(
+ BuglePrefsKeys.SMS_WARNING_ACKNOWLEDGED_VERSION,
+ BuglePrefsKeys.SMS_WARNING_ACKNOWLEDGED_VERSION_DEFAULT,
+ )
+ return acknowledgedVersion == appVersionProvider.versionCode()
+ }
+
+ override fun acknowledge() {
+ BuglePrefs.getApplicationPrefs().putInt(
+ BuglePrefsKeys.SMS_WARNING_ACKNOWLEDGED_VERSION,
+ appVersionProvider.versionCode(),
+ )
+ }
+}
diff --git a/src/com/android/messaging/di/core/CoreProvidesModule.kt b/src/com/android/messaging/di/core/CoreProvidesModule.kt
index 4c49744d4..5397a5b92 100644
--- a/src/com/android/messaging/di/core/CoreProvidesModule.kt
+++ b/src/com/android/messaging/di/core/CoreProvidesModule.kt
@@ -8,6 +8,8 @@ import android.content.pm.PackageManager
import android.os.SystemClock
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
+import com.android.messaging.BuildConfig
+import com.android.messaging.util.core.AppVersionProvider
import com.android.messaging.util.core.ElapsedRealtimeProvider
import dagger.Module
import dagger.Provides
@@ -129,4 +131,10 @@ internal class CoreProvidesModule {
fun provideElapsedRealtimeProvider(): ElapsedRealtimeProvider {
return ElapsedRealtimeProvider { SystemClock.elapsedRealtime() }
}
+
+ @Provides
+ @Reusable
+ fun provideAppVersionProvider(): AppVersionProvider {
+ return AppVersionProvider { BuildConfig.VERSION_CODE }
+ }
}
diff --git a/src/com/android/messaging/di/onboarding/OnboardingBindsModule.kt b/src/com/android/messaging/di/onboarding/OnboardingBindsModule.kt
new file mode 100644
index 000000000..ca7d4b50e
--- /dev/null
+++ b/src/com/android/messaging/di/onboarding/OnboardingBindsModule.kt
@@ -0,0 +1,52 @@
+package com.android.messaging.di.onboarding
+
+import com.android.messaging.data.onboarding.GetMissingPermissionLabels
+import com.android.messaging.data.onboarding.GetMissingPermissionLabelsImpl
+import com.android.messaging.data.onboarding.RequiredPermissionsChecker
+import com.android.messaging.data.onboarding.RequiredPermissionsCheckerImpl
+import com.android.messaging.data.onboarding.store.SmsWarningStore
+import com.android.messaging.data.onboarding.store.SmsWarningStoreImpl
+import com.android.messaging.domain.onboarding.usecase.DeterminePermissionRequest
+import com.android.messaging.domain.onboarding.usecase.DeterminePermissionRequestImpl
+import com.android.messaging.domain.onboarding.usecase.ShouldShowOnboarding
+import com.android.messaging.domain.onboarding.usecase.ShouldShowOnboardingImpl
+import dagger.Binds
+import dagger.Module
+import dagger.Reusable
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal abstract class OnboardingBindsModule {
+
+ @Binds
+ @Reusable
+ abstract fun bindRequiredPermissionsChecker(
+ impl: RequiredPermissionsCheckerImpl,
+ ): RequiredPermissionsChecker
+
+ @Binds
+ @Reusable
+ abstract fun bindDeterminePermissionRequest(
+ impl: DeterminePermissionRequestImpl,
+ ): DeterminePermissionRequest
+
+ @Binds
+ @Reusable
+ abstract fun bindGetMissingPermissionLabels(
+ impl: GetMissingPermissionLabelsImpl,
+ ): GetMissingPermissionLabels
+
+ @Binds
+ @Reusable
+ abstract fun bindSmsWarningStore(
+ impl: SmsWarningStoreImpl,
+ ): SmsWarningStore
+
+ @Binds
+ @Reusable
+ abstract fun bindShouldShowOnboarding(
+ impl: ShouldShowOnboardingImpl,
+ ): ShouldShowOnboarding
+}
diff --git a/src/com/android/messaging/di/permissioncheck/PermissionCheckBindsModule.kt b/src/com/android/messaging/di/permissioncheck/PermissionCheckBindsModule.kt
deleted file mode 100644
index 12d6431b0..000000000
--- a/src/com/android/messaging/di/permissioncheck/PermissionCheckBindsModule.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.messaging.di.permissioncheck
-
-import com.android.messaging.data.permissioncheck.GetMissingPermissionLabels
-import com.android.messaging.data.permissioncheck.GetMissingPermissionLabelsImpl
-import com.android.messaging.data.permissioncheck.RequiredPermissionsChecker
-import com.android.messaging.data.permissioncheck.RequiredPermissionsCheckerImpl
-import com.android.messaging.domain.permissioncheck.usecase.DeterminePermissionRequest
-import com.android.messaging.domain.permissioncheck.usecase.DeterminePermissionRequestImpl
-import dagger.Binds
-import dagger.Module
-import dagger.Reusable
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
-
-@Module
-@InstallIn(SingletonComponent::class)
-internal abstract class PermissionCheckBindsModule {
-
- @Binds
- @Reusable
- abstract fun bindRequiredPermissionsChecker(
- impl: RequiredPermissionsCheckerImpl,
- ): RequiredPermissionsChecker
-
- @Binds
- @Reusable
- abstract fun bindDeterminePermissionRequest(
- impl: DeterminePermissionRequestImpl,
- ): DeterminePermissionRequest
-
- @Binds
- @Reusable
- abstract fun bindGetMissingPermissionLabels(
- impl: GetMissingPermissionLabelsImpl,
- ): GetMissingPermissionLabels
-}
diff --git a/src/com/android/messaging/domain/permissioncheck/model/PermissionRequest.kt b/src/com/android/messaging/domain/onboarding/model/PermissionRequest.kt
similarity index 83%
rename from src/com/android/messaging/domain/permissioncheck/model/PermissionRequest.kt
rename to src/com/android/messaging/domain/onboarding/model/PermissionRequest.kt
index 2f7ebae58..b373a43a2 100644
--- a/src/com/android/messaging/domain/permissioncheck/model/PermissionRequest.kt
+++ b/src/com/android/messaging/domain/onboarding/model/PermissionRequest.kt
@@ -1,4 +1,4 @@
-package com.android.messaging.domain.permissioncheck.model
+package com.android.messaging.domain.onboarding.model
import kotlinx.collections.immutable.ImmutableList
diff --git a/src/com/android/messaging/domain/permissioncheck/usecase/DeterminePermissionRequest.kt b/src/com/android/messaging/domain/onboarding/usecase/DeterminePermissionRequest.kt
similarity index 74%
rename from src/com/android/messaging/domain/permissioncheck/usecase/DeterminePermissionRequest.kt
rename to src/com/android/messaging/domain/onboarding/usecase/DeterminePermissionRequest.kt
index 120d9cdb5..2584428a3 100644
--- a/src/com/android/messaging/domain/permissioncheck/usecase/DeterminePermissionRequest.kt
+++ b/src/com/android/messaging/domain/onboarding/usecase/DeterminePermissionRequest.kt
@@ -1,7 +1,7 @@
-package com.android.messaging.domain.permissioncheck.usecase
+package com.android.messaging.domain.onboarding.usecase
-import com.android.messaging.data.permissioncheck.RequiredPermissionsChecker
-import com.android.messaging.domain.permissioncheck.model.PermissionRequest
+import com.android.messaging.data.onboarding.RequiredPermissionsChecker
+import com.android.messaging.domain.onboarding.model.PermissionRequest
import javax.inject.Inject
internal fun interface DeterminePermissionRequest {
diff --git a/src/com/android/messaging/domain/onboarding/usecase/ShouldShowOnboarding.kt b/src/com/android/messaging/domain/onboarding/usecase/ShouldShowOnboarding.kt
new file mode 100644
index 000000000..76a1c12a7
--- /dev/null
+++ b/src/com/android/messaging/domain/onboarding/usecase/ShouldShowOnboarding.kt
@@ -0,0 +1,31 @@
+package com.android.messaging.domain.onboarding.usecase
+
+import com.android.messaging.data.onboarding.RequiredPermissionsChecker
+import com.android.messaging.data.onboarding.store.SmsWarningStore
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Inject
+
+internal interface ShouldShowOnboarding {
+ operator fun invoke(): Boolean
+
+ @EntryPoint
+ @InstallIn(SingletonComponent::class)
+ interface Provider {
+ fun shouldShowOnboarding(): ShouldShowOnboarding
+ }
+}
+
+internal class ShouldShowOnboardingImpl @Inject constructor(
+ private val checker: RequiredPermissionsChecker,
+ private val smsWarningStore: SmsWarningStore,
+) : ShouldShowOnboarding {
+
+ override fun invoke(): Boolean {
+ val needsSmsWarning = !smsWarningStore.isAcknowledged()
+ val needsPermissions = !checker.hasRequiredPermissions()
+
+ return needsSmsWarning || needsPermissions
+ }
+}
diff --git a/src/com/android/messaging/ui/BaseBugleActivity.java b/src/com/android/messaging/ui/BaseBugleActivity.java
index 43ba1d1de..02a316888 100644
--- a/src/com/android/messaging/ui/BaseBugleActivity.java
+++ b/src/com/android/messaging/ui/BaseBugleActivity.java
@@ -32,7 +32,7 @@ public class BaseBugleActivity extends AppCompatActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
+ if (UiUtils.redirectToOnboardingIfNeeded(this)) {
return;
}
}
diff --git a/src/com/android/messaging/ui/BugleActionBarActivity.java b/src/com/android/messaging/ui/BugleActionBarActivity.java
index b7e72cd72..ea04b9481 100644
--- a/src/com/android/messaging/ui/BugleActionBarActivity.java
+++ b/src/com/android/messaging/ui/BugleActionBarActivity.java
@@ -61,7 +61,7 @@ public class BugleActionBarActivity extends AppCompatActivity implements ImeUtil
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
+ if (UiUtils.redirectToOnboardingIfNeeded(this)) {
return;
}
diff --git a/src/com/android/messaging/ui/BugleComponentActivity.kt b/src/com/android/messaging/ui/BugleComponentActivity.kt
index ff9116b05..f6074d1fc 100644
--- a/src/com/android/messaging/ui/BugleComponentActivity.kt
+++ b/src/com/android/messaging/ui/BugleComponentActivity.kt
@@ -9,7 +9,7 @@ open class BugleComponentActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- UiUtils.redirectToPermissionCheckIfNeeded(this)
+ UiUtils.redirectToOnboardingIfNeeded(this)
}
override fun onResume() {
diff --git a/src/com/android/messaging/ui/UIIntents.java b/src/com/android/messaging/ui/UIIntents.java
index c8da82758..36ab58e73 100644
--- a/src/com/android/messaging/ui/UIIntents.java
+++ b/src/com/android/messaging/ui/UIIntents.java
@@ -103,9 +103,9 @@ public static UIIntents get() {
public static final String ACTION_MESSAGE_READ = "com.android.messaging.action.MESSAGE_READ";
/**
- * Launch the permission check activity
+ * Launch the onboarding activity (SMS warning, followed by the permission check)
*/
- public abstract void launchPermissionCheckActivity(final Context context);
+ public abstract void launchOnboardingActivity(final Context context);
public abstract void launchConversationListActivity(final Context context);
diff --git a/src/com/android/messaging/ui/UIIntentsImpl.java b/src/com/android/messaging/ui/UIIntentsImpl.java
index 438e844c9..719de3d4d 100644
--- a/src/com/android/messaging/ui/UIIntentsImpl.java
+++ b/src/com/android/messaging/ui/UIIntentsImpl.java
@@ -55,7 +55,7 @@
import com.android.messaging.ui.conversationpicker.host.widget.WidgetPickConversationActivity;
import com.android.messaging.ui.conversationsettings.ConversationSettingsActivity;
import com.android.messaging.ui.debug.DebugMmsConfigActivity;
-import com.android.messaging.ui.permissioncheck.PermissionCheckActivity;
+import com.android.messaging.ui.onboarding.OnboardingActivity;
import com.android.messaging.ui.photoviewer.PhotoViewerActivity;
import com.android.messaging.ui.vcarddetail.VCardDetailActivity;
import com.android.messaging.util.Assert;
@@ -133,8 +133,8 @@ private Intent getConversationActivityIntent(final Context context,
}
@Override
- public void launchPermissionCheckActivity(final Context context) {
- final Intent intent = new Intent(context, PermissionCheckActivity.class);
+ public void launchOnboardingActivity(final Context context) {
+ final Intent intent = new Intent(context, OnboardingActivity.class);
context.startActivity(intent);
}
diff --git a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java b/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
index c869839e0..5c9e10357 100644
--- a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
+++ b/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
@@ -57,7 +57,7 @@ public class LaunchConversationActivity extends Activity implements
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
+ if (UiUtils.redirectToOnboardingIfNeeded(this)) {
return;
}
diff --git a/src/com/android/messaging/ui/permissioncheck/PermissionCheckActivity.kt b/src/com/android/messaging/ui/onboarding/OnboardingActivity.kt
similarity index 68%
rename from src/com/android/messaging/ui/permissioncheck/PermissionCheckActivity.kt
rename to src/com/android/messaging/ui/onboarding/OnboardingActivity.kt
index ada390456..5b5f47ab1 100644
--- a/src/com/android/messaging/ui/permissioncheck/PermissionCheckActivity.kt
+++ b/src/com/android/messaging/ui/onboarding/OnboardingActivity.kt
@@ -1,4 +1,4 @@
-package com.android.messaging.ui.permissioncheck
+package com.android.messaging.ui.onboarding
import android.app.role.RoleManager
import android.os.Bundle
@@ -6,13 +6,13 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.android.messaging.ui.core.AppTheme
-import com.android.messaging.ui.permissioncheck.screen.PermissionCheckEffectHandlerImpl
-import com.android.messaging.ui.permissioncheck.screen.PermissionCheckScreen
+import com.android.messaging.ui.onboarding.screen.OnboardingEffectHandlerImpl
+import com.android.messaging.ui.onboarding.screen.OnboardingScreen
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
-class PermissionCheckActivity : ComponentActivity() {
+class OnboardingActivity : ComponentActivity() {
@Inject
lateinit var roleManager: RoleManager
@@ -22,14 +22,14 @@ class PermissionCheckActivity : ComponentActivity() {
enableEdgeToEdge()
- val effectHandler = PermissionCheckEffectHandlerImpl(
+ val effectHandler = OnboardingEffectHandlerImpl(
activity = this,
roleManager = roleManager,
)
setContent {
AppTheme {
- PermissionCheckScreen(
+ OnboardingScreen(
effectHandler = effectHandler,
onNavigateBack = ::finish,
)
diff --git a/src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckEffectHandler.kt b/src/com/android/messaging/ui/onboarding/screen/OnboardingEffectHandler.kt
similarity index 81%
rename from src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckEffectHandler.kt
rename to src/com/android/messaging/ui/onboarding/screen/OnboardingEffectHandler.kt
index 8c845c25a..732510a6f 100644
--- a/src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckEffectHandler.kt
+++ b/src/com/android/messaging/ui/onboarding/screen/OnboardingEffectHandler.kt
@@ -1,4 +1,4 @@
-package com.android.messaging.ui.permissioncheck.screen
+package com.android.messaging.ui.onboarding.screen
import android.app.Activity
import android.app.role.RoleManager
@@ -7,18 +7,18 @@ import android.net.Uri
import android.provider.Settings
import com.android.messaging.Factory
import com.android.messaging.ui.UIIntents
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckScreenEffect as Effect
+import com.android.messaging.ui.onboarding.screen.model.OnboardingScreenEffect as Effect
-internal interface PermissionCheckEffectHandler {
+internal interface OnboardingEffectHandler {
fun handle(effect: Effect)
fun createSmsRoleIntent(): Intent
}
-internal class PermissionCheckEffectHandlerImpl(
+internal class OnboardingEffectHandlerImpl(
private val activity: Activity,
private val roleManager: RoleManager,
-) : PermissionCheckEffectHandler {
+) : OnboardingEffectHandler {
override fun handle(effect: Effect) {
when (effect) {
diff --git a/src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckScreen.kt b/src/com/android/messaging/ui/onboarding/screen/OnboardingScreen.kt
similarity index 71%
rename from src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckScreen.kt
rename to src/com/android/messaging/ui/onboarding/screen/OnboardingScreen.kt
index 585f971da..81ad0943a 100644
--- a/src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckScreen.kt
+++ b/src/com/android/messaging/ui/onboarding/screen/OnboardingScreen.kt
@@ -1,4 +1,4 @@
-package com.android.messaging.ui.permissioncheck.screen
+package com.android.messaging.ui.onboarding.screen
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@@ -15,7 +15,10 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Warning
import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
@@ -42,22 +45,23 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.messaging.R
import com.android.messaging.ui.core.MessagingPreviewTheme
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckAction as Action
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckScreenEffect as Effect
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckUiState as State
-import com.android.messaging.ui.permissioncheck.screen.model.SettingsGuidance
+import com.android.messaging.ui.onboarding.screen.model.OnboardingAction as Action
+import com.android.messaging.ui.onboarding.screen.model.OnboardingScreenEffect as Effect
+import com.android.messaging.ui.onboarding.screen.model.OnboardingUiState as State
+import com.android.messaging.ui.onboarding.screen.model.SettingsGuidance
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
private val HeroIconSize = 96.dp
+private val HeroIconPadding = 24.dp
private val ScreenPadding = 24.dp
@Composable
-internal fun PermissionCheckScreen(
- effectHandler: PermissionCheckEffectHandler,
+internal fun OnboardingScreen(
+ effectHandler: OnboardingEffectHandler,
onNavigateBack: () -> Unit,
modifier: Modifier = Modifier,
- screenModel: PermissionCheckScreenModel = viewModel(),
+ screenModel: OnboardingScreenModel = viewModel(),
) {
val uiState by screenModel.uiState.collectAsStateWithLifecycle()
@@ -95,7 +99,7 @@ internal fun PermissionCheckScreen(
screenModel.onScreenResumed()
}
- PermissionCheckContent(
+ OnboardingContent(
uiState = uiState,
onAction = screenModel::onAction,
onNavigateBack = onNavigateBack,
@@ -104,7 +108,7 @@ internal fun PermissionCheckScreen(
}
@Composable
-private fun PermissionCheckContent(
+private fun OnboardingContent(
uiState: State,
onAction: (Action) -> Unit,
onNavigateBack: () -> Unit,
@@ -129,31 +133,16 @@ private fun PermissionCheckContent(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
- PermissionHero()
-
- Spacer(modifier = Modifier.height(24.dp))
-
- Text(
- text = stringResource(R.string.required_permissions_promo),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onSurface,
- textAlign = TextAlign.Center,
- )
-
- uiState.settingsGuidance?.let { guidance ->
- Spacer(modifier = Modifier.height(24.dp))
-
- PermissionSettingsGuidance(
- guidance = guidance,
- missingPermissions = uiState.missingPermissions,
- )
+ when (uiState) {
+ is State.SmsWarning -> SmsWarningStep()
+ is State.Permissions -> PermissionsStep(uiState = uiState)
}
}
Spacer(modifier = Modifier.height(16.dp))
- PermissionCheckActions(
- showSettingsGuidance = uiState.settingsGuidance != null,
+ OnboardingActions(
+ uiState = uiState,
onAction = onAction,
onNavigateBack = onNavigateBack,
)
@@ -162,7 +151,34 @@ private fun PermissionCheckContent(
}
@Composable
-private fun PermissionHero() {
+private fun SmsWarningStep() {
+ Icon(
+ imageVector = Icons.Rounded.Warning,
+ contentDescription = null,
+ modifier = Modifier
+ .size(HeroIconSize)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.errorContainer)
+ .padding(HeroIconPadding),
+ tint = MaterialTheme.colorScheme.onErrorContainer,
+ )
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ OnboardingStepTitle(text = stringResource(R.string.sms_warning_title))
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = stringResource(R.string.sms_warning_description),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ textAlign = TextAlign.Center,
+ )
+}
+
+@Composable
+private fun PermissionsStep(uiState: State.Permissions) {
Image(
painter = painterResource(R.drawable.ic_launcher_foreground),
contentDescription = null,
@@ -171,6 +187,29 @@ private fun PermissionHero() {
.clip(CircleShape)
.background(colorResource(R.color.ic_launcher_background)),
)
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ OnboardingStepTitle(text = stringResource(R.string.required_permissions_promo))
+
+ uiState.settingsGuidance?.let { guidance ->
+ Spacer(modifier = Modifier.height(24.dp))
+
+ PermissionSettingsGuidance(
+ guidance = guidance,
+ missingPermissions = uiState.missingPermissions,
+ )
+ }
+}
+
+@Composable
+private fun OnboardingStepTitle(text: String) {
+ Text(
+ text = text,
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface,
+ textAlign = TextAlign.Center,
+ )
}
@Composable
@@ -226,16 +265,18 @@ private fun PermissionSettingsGuidance(
}
@Composable
-private fun PermissionCheckActions(
- showSettingsGuidance: Boolean,
+private fun OnboardingActions(
+ uiState: State,
onAction: (Action) -> Unit,
onNavigateBack: () -> Unit,
) {
+ val showSettingsGuidance = uiState is State.Permissions && uiState.settingsGuidance != null
+
val primaryActionLabel = when {
showSettingsGuidance -> R.string.settings
else -> R.string.next
}
- val onPrimaryActionClick = when {
+ val primaryAction = when {
showSettingsGuidance -> Action.SettingsClicked
else -> Action.NextClicked
}
@@ -245,7 +286,7 @@ private fun PermissionCheckActions(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Button(
- onClick = { onAction(onPrimaryActionClick) },
+ onClick = { onAction(primaryAction) },
modifier = Modifier.fillMaxWidth(),
) {
Text(text = stringResource(primaryActionLabel))
@@ -264,10 +305,22 @@ private fun PermissionCheckActions(
@PreviewLightDark
@Composable
-private fun PermissionCheckContentPreview() {
+private fun OnboardingContentSmsWarningPreview() {
+ MessagingPreviewTheme {
+ OnboardingContent(
+ uiState = State.SmsWarning,
+ onAction = {},
+ onNavigateBack = {},
+ )
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun OnboardingContentPreview() {
MessagingPreviewTheme {
- PermissionCheckContent(
- uiState = State(settingsGuidance = null),
+ OnboardingContent(
+ uiState = State.Permissions(),
onAction = {},
onNavigateBack = {},
)
@@ -276,10 +329,10 @@ private fun PermissionCheckContentPreview() {
@PreviewLightDark
@Composable
-private fun PermissionCheckContentDefaultSmsAppGuidancePreview() {
+private fun OnboardingContentDefaultSmsAppGuidancePreview() {
MessagingPreviewTheme {
- PermissionCheckContent(
- uiState = State(settingsGuidance = SettingsGuidance.DefaultSmsApp),
+ OnboardingContent(
+ uiState = State.Permissions(settingsGuidance = SettingsGuidance.DefaultSmsApp),
onAction = {},
onNavigateBack = {},
)
@@ -288,10 +341,10 @@ private fun PermissionCheckContentDefaultSmsAppGuidancePreview() {
@PreviewLightDark
@Composable
-private fun PermissionCheckContentPermissionsGuidancePreview() {
+private fun OnboardingContentPermissionsGuidancePreview() {
MessagingPreviewTheme {
- PermissionCheckContent(
- uiState = State(
+ OnboardingContent(
+ uiState = State.Permissions(
settingsGuidance = SettingsGuidance.Permissions,
missingPermissions = persistentListOf(
"read your text messages",
diff --git a/src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckViewModel.kt b/src/com/android/messaging/ui/onboarding/screen/OnboardingViewModel.kt
similarity index 63%
rename from src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckViewModel.kt
rename to src/com/android/messaging/ui/onboarding/screen/OnboardingViewModel.kt
index 7e9df3450..3b79267ab 100644
--- a/src/com/android/messaging/ui/permissioncheck/screen/PermissionCheckViewModel.kt
+++ b/src/com/android/messaging/ui/onboarding/screen/OnboardingViewModel.kt
@@ -1,14 +1,15 @@
-package com.android.messaging.ui.permissioncheck.screen
+package com.android.messaging.ui.onboarding.screen
import androidx.lifecycle.ViewModel
-import com.android.messaging.data.permissioncheck.GetMissingPermissionLabels
-import com.android.messaging.data.permissioncheck.RequiredPermissionsChecker
-import com.android.messaging.domain.permissioncheck.model.PermissionRequest
-import com.android.messaging.domain.permissioncheck.usecase.DeterminePermissionRequest
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckAction as Action
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckScreenEffect as Effect
-import com.android.messaging.ui.permissioncheck.screen.model.PermissionCheckUiState as State
-import com.android.messaging.ui.permissioncheck.screen.model.SettingsGuidance
+import com.android.messaging.data.onboarding.GetMissingPermissionLabels
+import com.android.messaging.data.onboarding.RequiredPermissionsChecker
+import com.android.messaging.data.onboarding.store.SmsWarningStore
+import com.android.messaging.domain.onboarding.model.PermissionRequest
+import com.android.messaging.domain.onboarding.usecase.DeterminePermissionRequest
+import com.android.messaging.ui.onboarding.screen.model.OnboardingAction as Action
+import com.android.messaging.ui.onboarding.screen.model.OnboardingScreenEffect as Effect
+import com.android.messaging.ui.onboarding.screen.model.OnboardingUiState as State
+import com.android.messaging.ui.onboarding.screen.model.SettingsGuidance
import com.android.messaging.util.core.ElapsedRealtimeProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@@ -19,9 +20,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.update
-internal interface PermissionCheckScreenModel {
+internal interface OnboardingScreenModel {
val effects: Flow
val uiState: StateFlow
@@ -31,18 +31,24 @@ internal interface PermissionCheckScreenModel {
}
@HiltViewModel
-internal class PermissionCheckViewModel @Inject constructor(
+internal class OnboardingViewModel @Inject constructor(
private val checker: RequiredPermissionsChecker,
private val determinePermissionRequest: DeterminePermissionRequest,
private val getMissingPermissionLabels: GetMissingPermissionLabels,
private val elapsedRealtimeProvider: ElapsedRealtimeProvider,
+ private val smsWarningStore: SmsWarningStore,
) : ViewModel(),
- PermissionCheckScreenModel {
+ OnboardingScreenModel {
private val _effects = MutableSharedFlow(extraBufferCapacity = 1)
override val effects: Flow = _effects.asSharedFlow()
- private val _uiState = MutableStateFlow(State())
+ private val _uiState = MutableStateFlow(
+ when {
+ smsWarningStore.isAcknowledged() -> State.Permissions()
+ else -> State.SmsWarning
+ },
+ )
override val uiState: StateFlow = _uiState.asStateFlow()
private var requestStartedAtMillis = 0L
@@ -60,6 +66,10 @@ internal class PermissionCheckViewModel @Inject constructor(
}
override fun onScreenResumed() {
+ if (_uiState.value !is State.Permissions) {
+ return
+ }
+
if (checker.hasRequiredPermissions()) {
emitEffect(Effect.Redirect)
}
@@ -82,16 +92,32 @@ internal class PermissionCheckViewModel @Inject constructor(
SettingsGuidance.Permissions -> getMissingPermissionLabels()
}
- _uiState.update {
- it.copy(
- settingsGuidance = guidance,
- missingPermissions = missingPermissions,
- )
- }
+ _uiState.value = State.Permissions(
+ settingsGuidance = guidance,
+ missingPermissions = missingPermissions,
+ )
}
}
private fun handleNextClicked() {
+ when (_uiState.value) {
+ is State.SmsWarning -> handleSmsWarningAcknowledged()
+ is State.Permissions -> handlePermissionRequest()
+ }
+ }
+
+ private fun handleSmsWarningAcknowledged() {
+ smsWarningStore.acknowledge()
+
+ if (checker.hasRequiredPermissions()) {
+ emitEffect(Effect.Redirect)
+ return
+ }
+
+ _uiState.value = State.Permissions()
+ }
+
+ private fun handlePermissionRequest() {
when (val request = determinePermissionRequest()) {
PermissionRequest.SmsRole -> {
requestStartedAtMillis = elapsedRealtimeProvider.elapsedRealtimeMillis()
diff --git a/src/com/android/messaging/ui/onboarding/screen/model/OnboardingAction.kt b/src/com/android/messaging/ui/onboarding/screen/model/OnboardingAction.kt
new file mode 100644
index 000000000..ced6cd72e
--- /dev/null
+++ b/src/com/android/messaging/ui/onboarding/screen/model/OnboardingAction.kt
@@ -0,0 +1,8 @@
+package com.android.messaging.ui.onboarding.screen.model
+
+internal sealed interface OnboardingAction {
+
+ data object NextClicked : OnboardingAction
+
+ data object SettingsClicked : OnboardingAction
+}
diff --git a/src/com/android/messaging/ui/onboarding/screen/model/OnboardingScreenEffect.kt b/src/com/android/messaging/ui/onboarding/screen/model/OnboardingScreenEffect.kt
new file mode 100644
index 000000000..01ce5a811
--- /dev/null
+++ b/src/com/android/messaging/ui/onboarding/screen/model/OnboardingScreenEffect.kt
@@ -0,0 +1,16 @@
+package com.android.messaging.ui.onboarding.screen.model
+
+import kotlinx.collections.immutable.ImmutableList
+
+internal sealed interface OnboardingScreenEffect {
+
+ data class RequestRuntimePermissions(
+ val permissions: ImmutableList,
+ ) : OnboardingScreenEffect
+
+ data object RequestSmsRole : OnboardingScreenEffect
+
+ data object OpenAppSettings : OnboardingScreenEffect
+
+ data object Redirect : OnboardingScreenEffect
+}
diff --git a/src/com/android/messaging/ui/onboarding/screen/model/OnboardingUiState.kt b/src/com/android/messaging/ui/onboarding/screen/model/OnboardingUiState.kt
new file mode 100644
index 000000000..58c01fe2c
--- /dev/null
+++ b/src/com/android/messaging/ui/onboarding/screen/model/OnboardingUiState.kt
@@ -0,0 +1,18 @@
+package com.android.messaging.ui.onboarding.screen.model
+
+import androidx.compose.runtime.Immutable
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+
+@Immutable
+internal sealed interface OnboardingUiState {
+
+ @Immutable
+ data object SmsWarning : OnboardingUiState
+
+ @Immutable
+ data class Permissions(
+ val settingsGuidance: SettingsGuidance? = null,
+ val missingPermissions: ImmutableList = persistentListOf(),
+ ) : OnboardingUiState
+}
diff --git a/src/com/android/messaging/ui/onboarding/screen/model/SettingsGuidance.kt b/src/com/android/messaging/ui/onboarding/screen/model/SettingsGuidance.kt
new file mode 100644
index 000000000..f604cb51b
--- /dev/null
+++ b/src/com/android/messaging/ui/onboarding/screen/model/SettingsGuidance.kt
@@ -0,0 +1,6 @@
+package com.android.messaging.ui.onboarding.screen.model
+
+internal enum class SettingsGuidance {
+ DefaultSmsApp,
+ Permissions,
+}
diff --git a/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckAction.kt b/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckAction.kt
deleted file mode 100644
index cfd7740f1..000000000
--- a/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckAction.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.android.messaging.ui.permissioncheck.screen.model
-
-internal sealed interface PermissionCheckAction {
-
- data object NextClicked : PermissionCheckAction
-
- data object SettingsClicked : PermissionCheckAction
-}
diff --git a/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckScreenEffect.kt b/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckScreenEffect.kt
deleted file mode 100644
index dc18e4b6d..000000000
--- a/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckScreenEffect.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.android.messaging.ui.permissioncheck.screen.model
-
-import kotlinx.collections.immutable.ImmutableList
-
-internal sealed interface PermissionCheckScreenEffect {
-
- data class RequestRuntimePermissions(
- val permissions: ImmutableList,
- ) : PermissionCheckScreenEffect
-
- data object RequestSmsRole : PermissionCheckScreenEffect
-
- data object OpenAppSettings : PermissionCheckScreenEffect
-
- data object Redirect : PermissionCheckScreenEffect
-}
diff --git a/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckUiState.kt b/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckUiState.kt
deleted file mode 100644
index 5c1fa9d56..000000000
--- a/src/com/android/messaging/ui/permissioncheck/screen/model/PermissionCheckUiState.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.android.messaging.ui.permissioncheck.screen.model
-
-import androidx.compose.runtime.Immutable
-import kotlinx.collections.immutable.ImmutableList
-import kotlinx.collections.immutable.persistentListOf
-
-@Immutable
-internal data class PermissionCheckUiState(
- val settingsGuidance: SettingsGuidance? = null,
- val missingPermissions: ImmutableList = persistentListOf(),
-)
-
-internal enum class SettingsGuidance {
- DefaultSmsApp,
- Permissions,
-}
diff --git a/src/com/android/messaging/util/BuglePrefsKeys.java b/src/com/android/messaging/util/BuglePrefsKeys.java
index a0ebb9645..0c1601ac0 100644
--- a/src/com/android/messaging/util/BuglePrefsKeys.java
+++ b/src/com/android/messaging/util/BuglePrefsKeys.java
@@ -61,6 +61,14 @@ private BuglePrefsKeys() {} // do not instantiate
public static final String CONVERSATION_NOTIFICATION_CHANNELS_MIGRATED
= "conversation_notification_channels_migrated_v1";
+ /**
+ * The app version code for which the SMS insecurity warning was last acknowledged.
+ */
+ public static final String SMS_WARNING_ACKNOWLEDGED_VERSION
+ = "sms_warning_acknowledged_version";
+ public static final int SMS_WARNING_ACKNOWLEDGED_VERSION_DEFAULT
+ = BuglePrefs.NO_SHARED_PREFERENCES_VERSION;
+
/**
* The last selected chooser index in the media picker.
*/
diff --git a/src/com/android/messaging/util/UiUtils.java b/src/com/android/messaging/util/UiUtils.java
index 086b00eb2..187250d2f 100644
--- a/src/com/android/messaging/util/UiUtils.java
+++ b/src/com/android/messaging/util/UiUtils.java
@@ -42,8 +42,11 @@
import com.android.messaging.Factory;
import com.android.messaging.R;
+import com.android.messaging.domain.onboarding.usecase.ShouldShowOnboarding;
import com.android.messaging.ui.UIIntents;
+import dagger.hilt.android.EntryPointAccessors;
+
import java.lang.reflect.Field;
import androidx.annotation.Nullable;
@@ -259,22 +262,29 @@ public static boolean isRtlMode() {
}
/**
- * Check if the activity needs to be redirected to permission check
+ * Check if the activity needs to be redirected to onboarding, which covers both the
+ * once-per-version SMS warning and the permission check.
* @return true if {@link Activity#finish()} was called because redirection was performed
*/
- public static boolean redirectToPermissionCheckIfNeeded(final Activity activity) {
- if (!OsUtil.hasRequiredPermissions()) {
- UIIntents.get().launchPermissionCheckActivity(activity);
- } else {
+ public static boolean redirectToOnboardingIfNeeded(final Activity activity) {
+ if (!shouldShowOnboarding()) {
// No redirect performed
return false;
}
- // Redirect performed
+ UIIntents.get().launchOnboardingActivity(activity);
activity.finish();
return true;
}
+ private static boolean shouldShowOnboarding() {
+ final Context context = Factory.get().getApplicationContext();
+ return EntryPointAccessors
+ .fromApplication(context, ShouldShowOnboarding.Provider.class)
+ .shouldShowOnboarding()
+ .invoke();
+ }
+
/**
* Called to check if all conditions are nominal and a "go" for some action, such as deleting
* a message, that requires this app to be the default app. This is also a precondition
diff --git a/src/com/android/messaging/util/core/AppVersionProvider.kt b/src/com/android/messaging/util/core/AppVersionProvider.kt
new file mode 100644
index 000000000..9b7800790
--- /dev/null
+++ b/src/com/android/messaging/util/core/AppVersionProvider.kt
@@ -0,0 +1,5 @@
+package com.android.messaging.util.core
+
+internal fun interface AppVersionProvider {
+ fun versionCode(): Int
+}