diff --git a/app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeActionListener.kt b/app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeActionListener.kt index b9edef60fcc..eb92d19568d 100644 --- a/app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeActionListener.kt +++ b/app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeActionListener.kt @@ -1,9 +1,17 @@ package org.oppia.android.app.notice -import org.oppia.android.app.splash.DeprecationNoticeActionType - /** Listener for when an option on any deprecation dialog is clicked. */ interface DeprecationNoticeActionListener { /** Called when a dialog button is clicked. */ fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) } + +/** Enum class for the various deprecation notice actions available to the user. */ +enum class DeprecationNoticeActionType { + /** Action for when the user presses the 'Close' option on a deprecation dialog. */ + CLOSE, + /** Action for when the user presses the 'Dismiss' option on a deprecation dialog. */ + DISMISS, + /** Action for when the user presses the 'Update' option on a deprecation dialog. */ + UPDATE +} diff --git a/app/src/main/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentPresenter.kt index 265670fb3be..bdb80a53535 100644 --- a/app/src/main/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentPresenter.kt @@ -4,7 +4,6 @@ import android.app.Dialog import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import org.oppia.android.R -import org.oppia.android.app.splash.DeprecationNoticeActionType import org.oppia.android.app.translation.AppLanguageResourceHandler import javax.inject.Inject diff --git a/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt index be4b938c522..3b110026325 100644 --- a/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt @@ -4,7 +4,6 @@ import android.app.Dialog import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import org.oppia.android.R -import org.oppia.android.app.splash.DeprecationNoticeActionType import org.oppia.android.app.translation.AppLanguageResourceHandler import javax.inject.Inject diff --git a/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt index efcfc84ed20..f626011829e 100644 --- a/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt @@ -4,7 +4,6 @@ import android.app.Dialog import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import org.oppia.android.R -import org.oppia.android.app.splash.DeprecationNoticeActionType import org.oppia.android.app.translation.AppLanguageResourceHandler import javax.inject.Inject diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt index e6b223af14d..c17533f84c6 100644 --- a/app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt @@ -2,8 +2,8 @@ package org.oppia.android.app.notice.testing import android.os.Bundle import org.oppia.android.app.notice.DeprecationNoticeActionListener +import org.oppia.android.app.notice.DeprecationNoticeActionType import org.oppia.android.app.notice.ForcedAppDeprecationNoticeDialogFragment -import org.oppia.android.app.splash.DeprecationNoticeActionType import org.oppia.android.app.testing.activity.TestActivity /** [TestActivity] for setting up a test environment for testing the beta notice dialog. */ diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt index d3ffd8b519e..d0a57d19833 100644 --- a/app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt @@ -2,8 +2,8 @@ package org.oppia.android.app.notice.testing import android.os.Bundle import org.oppia.android.app.notice.DeprecationNoticeActionListener +import org.oppia.android.app.notice.DeprecationNoticeActionType import org.oppia.android.app.notice.OptionalAppDeprecationNoticeDialogFragment -import org.oppia.android.app.splash.DeprecationNoticeActionType import org.oppia.android.app.testing.activity.TestActivity /** [TestActivity] for setting up a test environment for testing the beta notice dialog. */ diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt index 13923f43fd8..2cfbf8cf565 100644 --- a/app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt @@ -2,8 +2,8 @@ package org.oppia.android.app.notice.testing import android.os.Bundle import org.oppia.android.app.notice.DeprecationNoticeActionListener +import org.oppia.android.app.notice.DeprecationNoticeActionType import org.oppia.android.app.notice.OsDeprecationNoticeDialogFragment -import org.oppia.android.app.splash.DeprecationNoticeActionType import org.oppia.android.app.testing.activity.TestActivity /** [TestActivity] for setting up a test environment for testing the beta notice dialog. */ diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt index f9310821ddb..f52284431e2 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt @@ -11,21 +11,13 @@ import org.oppia.android.app.fragment.FragmentComponentBuilderInjector import org.oppia.android.app.fragment.FragmentComponentFactory import org.oppia.android.app.model.ScreenName.SPLASH_ACTIVITY import org.oppia.android.app.notice.BetaNoticeClosedListener +import org.oppia.android.app.notice.DeprecationNoticeActionListener +import org.oppia.android.app.notice.DeprecationNoticeActionType import org.oppia.android.app.notice.DeprecationNoticeExitAppListener import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeClosedListener import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.decorateWithScreenName import javax.inject.Inject -/** Enum class for the various deprecation notice actions available to the user. */ -enum class DeprecationNoticeActionType { - /** Action for when the user presses the 'Close' option on a deprecation dialog. */ - CLOSE, - /** Action for when the user presses the 'Dismiss' option on a deprecation dialog. */ - DISMISS, - /** Action for when the user presses the 'Update' option on a deprecation dialog. */ - UPDATE -} - /** * An activity that shows a temporary loading page until the app is fully loaded then navigates to * the profile selection screen. @@ -38,6 +30,7 @@ class SplashActivity : AppCompatActivity(), FragmentComponentFactory, DeprecationNoticeExitAppListener, + DeprecationNoticeActionListener, BetaNoticeClosedListener, GeneralAvailabilityUpgradeNoticeClosedListener { @@ -69,4 +62,7 @@ class SplashActivity : override fun onGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss: Boolean) = splashActivityPresenter.handleOnGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss) + + override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) = + splashActivityPresenter.handleOnDeprecationNoticeActionClicked(noticeType) } diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt index dd926f56612..5c85f4e8ebf 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt @@ -15,7 +15,11 @@ import org.oppia.android.app.model.AppStartupState.StartupMode import org.oppia.android.app.model.BuildFlavor import org.oppia.android.app.notice.AutomaticAppDeprecationNoticeDialogFragment import org.oppia.android.app.notice.BetaNoticeDialogFragment +import org.oppia.android.app.notice.DeprecationNoticeActionType +import org.oppia.android.app.notice.ForcedAppDeprecationNoticeDialogFragment import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeDialogFragment +import org.oppia.android.app.notice.OptionalAppDeprecationNoticeDialogFragment +import org.oppia.android.app.notice.OsDeprecationNoticeDialogFragment import org.oppia.android.app.onboarding.OnboardingActivity import org.oppia.android.app.profile.ProfileChooserActivity import org.oppia.android.app.translation.AppLanguageLocaleHandler @@ -31,11 +35,16 @@ import org.oppia.android.util.data.DataProvider import org.oppia.android.util.data.DataProviders.Companion.combineWith import org.oppia.android.util.data.DataProviders.Companion.toLiveData import org.oppia.android.util.locale.OppiaLocale +import org.oppia.android.util.platformparameter.EnableAppAndOsDeprecation +import org.oppia.android.util.platformparameter.PlatformParameterValue import javax.inject.Inject private const val AUTO_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG = "auto_deprecation_notice_dialog" +private const val FORCED_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG = "forced_deprecation_notice_dialog" private const val BETA_NOTICE_DIALOG_FRAGMENT_TAG = "beta_notice_dialog" private const val GA_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG = "general_availability_update_notice_dialog" +private const val OPTIONAL_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG = "optional_update_notice_dialog" +private const val OS_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG = "os_update_notice_dialog" private const val SPLASH_INIT_STATE_DATA_PROVIDER_ID = "splash_init_state_data_provider" /** The presenter for [SplashActivity]. */ @@ -49,7 +58,9 @@ class SplashActivityPresenter @Inject constructor( private val localeController: LocaleController, private val appLanguageLocaleHandler: AppLanguageLocaleHandler, private val lifecycleSafeTimerFactory: LifecycleSafeTimerFactory, - private val currentBuildFlavor: BuildFlavor + private val currentBuildFlavor: BuildFlavor, + @EnableAppAndOsDeprecation + private val enableAppAndOsDeprecation: PlatformParameterValue, ) { lateinit var startupMode: StartupMode @@ -67,6 +78,14 @@ class SplashActivityPresenter @Inject constructor( subscribeToOnboardingFlow() } + fun handleOnDeprecationNoticeActionClicked(noticeType: DeprecationNoticeActionType) { + when (noticeType) { + DeprecationNoticeActionType.CLOSE -> handleOnDeprecationNoticeCloseAppButtonClicked() + DeprecationNoticeActionType.DISMISS -> handleOnDeprecationNoticeUpdateButtonClicked() + DeprecationNoticeActionType.UPDATE -> handleOnDeprecationNoticeDialogDismissed() + } + } + /** Handles cases where the user clicks the close app option on a deprecation notice dialog. */ fun handleOnDeprecationNoticeCloseAppButtonClicked() { // If the app close button is clicked for the deprecation notice, finish the activity to close @@ -75,7 +94,7 @@ class SplashActivityPresenter @Inject constructor( } /** Handles cases where the user clicks the update option on a deprecation notice dialog. */ - fun handleOnDeprecationNoticeUpdateButtonClicked() { + private fun handleOnDeprecationNoticeUpdateButtonClicked() { // If the Update button is clicked for the deprecation notice, launch the Play Store and open // the Oppia app's page. val packageName = activity.packageName @@ -100,7 +119,7 @@ class SplashActivityPresenter @Inject constructor( } /** Handles cases where the user dismisses the deprecation notice dialog. */ - fun handleOnDeprecationNoticeDialogDismissed() { + private fun handleOnDeprecationNoticeDialogDismissed() { // If the Dismiss button is clicked for the deprecation notice, the dialog is automatically // dismissed. Navigate to profile chooser activity. activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) @@ -200,6 +219,43 @@ class SplashActivityPresenter @Inject constructor( } private fun processStartupMode() { + if (enableAppAndOsDeprecation.value) { + when (startupMode) { + StartupMode.USER_IS_ONBOARDED -> { + activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) + activity.finish() + } + StartupMode.APP_IS_DEPRECATED -> { + showDialog( + FORCED_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG, + ForcedAppDeprecationNoticeDialogFragment::newInstance + ) + } + StartupMode.OPTIONAL_UPDATE_AVAILABLE -> { + showDialog( + OPTIONAL_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG, + OptionalAppDeprecationNoticeDialogFragment::newInstance + ) + } + StartupMode.OS_IS_DEPRECATED -> { + showDialog( + OS_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG, + OsDeprecationNoticeDialogFragment::newInstance + ) + } + else -> { + // In all other cases (including errors when the startup state fails to load or is + // defaulted), assume the user needs to be onboarded. + activity.startActivity(OnboardingActivity.createOnboardingActivity(activity)) + activity.finish() + } + } + } else { + processLegacyStartupMode() + } + } + + private fun processLegacyStartupMode() { when (startupMode) { StartupMode.USER_IS_ONBOARDED -> { activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) diff --git a/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt b/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt index 31b6b203ba7..776a72f10db 100644 --- a/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt +++ b/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt @@ -1,16 +1,25 @@ package org.oppia.android.domain.onboarding +import android.os.Build +import kotlinx.coroutines.runBlocking import org.oppia.android.app.model.AppStartupState import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode import org.oppia.android.app.model.AppStartupState.StartupMode import org.oppia.android.app.model.BuildFlavor +import org.oppia.android.app.model.DeprecationResponseDatabase import org.oppia.android.app.model.OnboardingState import org.oppia.android.data.persistence.PersistentCacheStore +import org.oppia.android.domain.BuildConfig import org.oppia.android.domain.oppialogger.OppiaLogger import org.oppia.android.util.data.DataProvider import org.oppia.android.util.data.DataProviders.Companion.transform import org.oppia.android.util.extensions.getStringFromBundle import org.oppia.android.util.locale.OppiaLocale +import org.oppia.android.util.platformparameter.EnableAppAndOsDeprecation +import org.oppia.android.util.platformparameter.ForcedAppUpdateVersionCode +import org.oppia.android.util.platformparameter.LowestSupportedApiLevel +import org.oppia.android.util.platformparameter.OptionalAppUpdateVersionCode +import org.oppia.android.util.platformparameter.PlatformParameterValue import javax.inject.Inject import javax.inject.Singleton @@ -23,7 +32,16 @@ class AppStartupStateController @Inject constructor( private val oppiaLogger: OppiaLogger, private val expirationMetaDataRetriever: ExpirationMetaDataRetriever, private val machineLocale: OppiaLocale.MachineLocale, - private val currentBuildFlavor: BuildFlavor + private val currentBuildFlavor: BuildFlavor, + private val deprecationController: DeprecationController, + @EnableAppAndOsDeprecation + private val enableAppAndOsDeprecation: PlatformParameterValue, + @OptionalAppUpdateVersionCode + private val optionalAppUpdateVersionCode: PlatformParameterValue, + @ForcedAppUpdateVersionCode + private val forcedAppUpdateVersionCode: PlatformParameterValue, + @LowestSupportedApiLevel + private val lowestSupportedApiLevel: PlatformParameterValue ) { private val onboardingFlowStore by lazy { cacheStoreFactory.create("on_boarding_flow", OnboardingState.getDefaultInstance()) @@ -116,11 +134,51 @@ class AppStartupStateController @Inject constructor( } private fun computeAppStartupMode(onboardingState: OnboardingState): StartupMode { - return when { - hasAppExpired() -> StartupMode.APP_IS_DEPRECATED - onboardingState.alreadyOnboardedApp -> StartupMode.USER_IS_ONBOARDED - else -> StartupMode.USER_NOT_YET_ONBOARDED + // Return old logic if app and os feature flag is not enabled + if (!enableAppAndOsDeprecation.value) { + return when { + hasAppExpired() -> StartupMode.APP_IS_DEPRECATED + onboardingState.alreadyOnboardedApp -> StartupMode.USER_IS_ONBOARDED + else -> StartupMode.USER_NOT_YET_ONBOARDED + } } + + val deprecationDataProvider = deprecationController.getDeprecationDatabase() + + var deprecationDatabase = DeprecationResponseDatabase.newBuilder().build() + + runBlocking { + deprecationDataProvider.retrieveData().transform { + deprecationDatabase = it + } + } + + val appVersionCode = BuildConfig.VERSION_CODE + + val osIsDeprecated = lowestSupportedApiLevel.value > Build.VERSION.SDK_INT && + deprecationDatabase.osDeprecationResponse.deprecatedVersion != Build.VERSION.SDK_INT + val appUpdateIsAvailable = optionalAppUpdateVersionCode.value > appVersionCode || + forcedAppUpdateVersionCode.value > appVersionCode + + if (onboardingState.alreadyOnboardedApp) { + if (osIsDeprecated) { + return StartupMode.OS_IS_DEPRECATED + } + + if (appUpdateIsAvailable) { + if (forcedAppUpdateVersionCode.value > appVersionCode) { + return StartupMode.APP_IS_DEPRECATED + } + + if (deprecationDatabase.appDeprecationResponse.deprecatedVersion != + optionalAppUpdateVersionCode.value + ) { + return StartupMode.OPTIONAL_UPDATE_AVAILABLE + } + } + + return StartupMode.USER_IS_ONBOARDED + } else return StartupMode.USER_NOT_YET_ONBOARDED } private fun computeBuildNoticeMode(