diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 897fc034879..c324a1a7119 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -92,6 +92,18 @@
android:name=".app.notice.testing.GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity"
android:label="@string/test_activity_label"
android:theme="@style/OppiaThemeWithoutActionBar" />
+
+
+
+ deprecationNoticeActionListener.onActionButtonClicked(
+ DeprecationNoticeActionType.UPDATE
+ )
+ }
+ .setNegativeButton(R.string.forced_app_update_dialog_close_button_text) { _, _ ->
+ deprecationNoticeActionListener.onActionButtonClicked(
+ DeprecationNoticeActionType.CLOSE
+ )
+ }
+ .setCancelable(false)
+ .create()
+ dialog.setCanceledOnTouchOutside(false)
+ return dialog
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragment.kt b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragment.kt
new file mode 100644
index 00000000000..29eb21fd005
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragment.kt
@@ -0,0 +1,30 @@
+package org.oppia.android.app.notice
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import org.oppia.android.app.fragment.FragmentComponentImpl
+import org.oppia.android.app.fragment.InjectableDialogFragment
+import javax.inject.Inject
+
+/** Dialog fragment that informs the user that a new app version is available for download. */
+class OptionalAppDeprecationNoticeDialogFragment : InjectableDialogFragment() {
+ companion object {
+ /** Returns a new instance of [OptionalAppDeprecationNoticeDialogFragment]. */
+ fun newInstance(): OptionalAppDeprecationNoticeDialogFragment {
+ return OptionalAppDeprecationNoticeDialogFragment()
+ }
+ }
+
+ @Inject lateinit var optionalAppDeprecationNoticeDialogFragmentPresenter:
+ OptionalAppDeprecationNoticeDialogFragmentPresenter
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ (fragmentComponent as FragmentComponentImpl).inject(this)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return optionalAppDeprecationNoticeDialogFragmentPresenter.handleOnCreateDialog()
+ }
+}
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
new file mode 100644
index 00000000000..be4b938c522
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt
@@ -0,0 +1,47 @@
+package org.oppia.android.app.notice
+
+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
+
+/** Presenter class responsible for showing an optional update dialog to the user. */
+class OptionalAppDeprecationNoticeDialogFragmentPresenter @Inject constructor(
+ private val activity: AppCompatActivity,
+ private val resourceHandler: AppLanguageResourceHandler
+) {
+ private val deprecationNoticeActionListener by lazy {
+ activity as DeprecationNoticeActionListener
+ }
+
+ /** Handles dialog creation for the optional app deprecation notice. */
+ fun handleOnCreateDialog(): Dialog {
+ val appName = resourceHandler.getStringInLocale(R.string.app_name)
+
+ val dialog = AlertDialog.Builder(activity, R.style.DeprecationAlertDialogTheme)
+ .setTitle(R.string.optional_app_update_dialog_title)
+ .setMessage(
+ resourceHandler.getStringInLocaleWithWrapping(
+ R.string.optional_app_update_dialog_message,
+ appName
+ )
+ )
+ .setPositiveButton(R.string.optional_app_update_dialog_update_button_text) { _, _ ->
+ deprecationNoticeActionListener.onActionButtonClicked(
+ DeprecationNoticeActionType.UPDATE
+ )
+ }
+ .setNegativeButton(R.string.optional_app_update_dialog_dismiss_button_text) { _, _ ->
+ deprecationNoticeActionListener.onActionButtonClicked(
+ DeprecationNoticeActionType.DISMISS
+ )
+ }
+ .setCancelable(false)
+ .create()
+ dialog.setCanceledOnTouchOutside(false)
+ return dialog
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragment.kt b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragment.kt
new file mode 100644
index 00000000000..48e5fb59181
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragment.kt
@@ -0,0 +1,33 @@
+package org.oppia.android.app.notice
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import org.oppia.android.app.fragment.FragmentComponentImpl
+import org.oppia.android.app.fragment.InjectableDialogFragment
+import javax.inject.Inject
+
+/**
+ * Dialog fragment that informs the user that their phone OS is no longer supported by Oppia and
+ * they will no longer be able to update their app to the latest version.
+ */
+class OsDeprecationNoticeDialogFragment : InjectableDialogFragment() {
+ companion object {
+ /** Returns a new instance of [OsDeprecationNoticeDialogFragment]. */
+ fun newInstance(): OsDeprecationNoticeDialogFragment {
+ return OsDeprecationNoticeDialogFragment()
+ }
+ }
+
+ @Inject lateinit var osDeprecationNoticeDialogFragmentPresenter:
+ OsDeprecationNoticeDialogFragmentPresenter
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ (fragmentComponent as FragmentComponentImpl).inject(this)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return osDeprecationNoticeDialogFragmentPresenter.handleOnCreateDialog()
+ }
+}
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
new file mode 100644
index 00000000000..efcfc84ed20
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt
@@ -0,0 +1,42 @@
+package org.oppia.android.app.notice
+
+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
+
+/** Presenter class responsible for showing an OS deprecation dialog to the user. */
+class OsDeprecationNoticeDialogFragmentPresenter @Inject constructor(
+ private val activity: AppCompatActivity,
+ private val resourceHandler: AppLanguageResourceHandler
+) {
+ private val deprecationNoticeActionListener by lazy {
+ activity as DeprecationNoticeActionListener
+ }
+
+ /** Handles dialog creation for the OS deprecation notice. */
+ fun handleOnCreateDialog(): Dialog {
+ val appName = resourceHandler.getStringInLocale(R.string.app_name)
+
+ val dialog = AlertDialog.Builder(activity, R.style.DeprecationAlertDialogTheme)
+ .setTitle(R.string.os_deprecation_dialog_title)
+ .setMessage(
+ resourceHandler.getStringInLocaleWithWrapping(
+ R.string.os_deprecation_dialog_message,
+ appName
+ )
+ )
+ .setNegativeButton(R.string.os_deprecation_dialog_dismiss_button_text) { _, _ ->
+ deprecationNoticeActionListener.onActionButtonClicked(
+ DeprecationNoticeActionType.DISMISS
+ )
+ }
+ .setCancelable(false)
+ .create()
+ dialog.setCanceledOnTouchOutside(false)
+ return dialog
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel b/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel
index 92f1a0ae62b..00ba0065c2b 100644
--- a/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel
+++ b/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel
@@ -18,6 +18,19 @@ kt_android_library(
],
)
+kt_android_library(
+ name = "forced_app_deprecation_notice_dialog_fragment_test_activity",
+ testonly = True,
+ srcs = [
+ "ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt",
+ ],
+ visibility = ["//app:app_testing_visibility"],
+ deps = [
+ "//app",
+ "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity",
+ ],
+)
+
kt_android_library(
name = "general_availability_upgrade_notice_dialog_fragment_test_activity",
testonly = True,
@@ -31,4 +44,30 @@ kt_android_library(
],
)
+kt_android_library(
+ name = "optional_app_deprecation_notice_dialog_fragment_test_activity",
+ testonly = True,
+ srcs = [
+ "OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt",
+ ],
+ visibility = ["//app:app_testing_visibility"],
+ deps = [
+ "//app",
+ "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity",
+ ],
+)
+
+kt_android_library(
+ name = "os_deprecation_notice_dialog_fragment_test_activity",
+ testonly = True,
+ srcs = [
+ "OsDeprecationNoticeDialogFragmentTestActivity.kt",
+ ],
+ visibility = ["//app:app_testing_visibility"],
+ deps = [
+ "//app",
+ "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity",
+ ],
+)
+
dagger_rules()
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
new file mode 100644
index 00000000000..e6b223af14d
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt
@@ -0,0 +1,30 @@
+package org.oppia.android.app.notice.testing
+
+import android.os.Bundle
+import org.oppia.android.app.notice.DeprecationNoticeActionListener
+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. */
+class ForcedAppDeprecationNoticeDialogFragmentTestActivity :
+ TestActivity(),
+ DeprecationNoticeActionListener {
+ /**
+ * [DeprecationNoticeActionListener] that must be initialized by the test, and is presumed to be a
+ * Mockito mock (though this is not, strictly speaking, required).
+ *
+ * This listener will be used as the callback for the dialog in response to UI operations.
+ */
+ lateinit var mockCallbackListener: DeprecationNoticeActionListener
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ForcedAppDeprecationNoticeDialogFragment.newInstance()
+ .showNow(supportFragmentManager, "forced_app_deprecation_dialog")
+ }
+
+ override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) {
+ mockCallbackListener.onActionButtonClicked(noticeType)
+ }
+}
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
new file mode 100644
index 00000000000..d3ffd8b519e
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt
@@ -0,0 +1,30 @@
+package org.oppia.android.app.notice.testing
+
+import android.os.Bundle
+import org.oppia.android.app.notice.DeprecationNoticeActionListener
+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. */
+class OptionalAppDeprecationNoticeDialogFragmentTestActivity :
+ TestActivity(),
+ DeprecationNoticeActionListener {
+ /**
+ * [DeprecationNoticeActionListener] that must be initialized by the test, and is presumed to be a
+ * Mockito mock (though this is not, strictly speaking, required).
+ *
+ * This listener will be used as the callback for the dialog in response to UI operations.
+ */
+ lateinit var mockCallbackListener: DeprecationNoticeActionListener
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ OptionalAppDeprecationNoticeDialogFragment.newInstance()
+ .showNow(supportFragmentManager, "optional_app_deprecation_dialog")
+ }
+
+ override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) {
+ mockCallbackListener.onActionButtonClicked(noticeType)
+ }
+}
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
new file mode 100644
index 00000000000..13923f43fd8
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt
@@ -0,0 +1,30 @@
+package org.oppia.android.app.notice.testing
+
+import android.os.Bundle
+import org.oppia.android.app.notice.DeprecationNoticeActionListener
+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. */
+class OsDeprecationNoticeDialogFragmentTestActivity :
+ TestActivity(),
+ DeprecationNoticeActionListener {
+ /**
+ * [DeprecationNoticeActionListener] that must be initialized by the test, and is presumed to be a
+ * Mockito mock (though this is not, strictly speaking, required).
+ *
+ * This listener will be used as the callback for the dialog in response to UI operations.
+ */
+ lateinit var mockCallbackListener: DeprecationNoticeActionListener
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ OsDeprecationNoticeDialogFragment.newInstance()
+ .showNow(supportFragmentManager, "os_deprecation_dialog")
+ }
+
+ override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) {
+ mockCallbackListener.onActionButtonClicked(noticeType)
+ }
+}
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 1952e72ae3b..f9310821ddb 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
@@ -16,6 +16,16 @@ import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeClosedListen
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.
@@ -47,10 +57,12 @@ class SplashActivity :
override fun createFragmentComponent(fragment: Fragment): FragmentComponent {
val builderInjector = activityComponent as FragmentComponentBuilderInjector
- return builderInjector.getFragmentComponentBuilderProvider().get().setFragment(fragment).build()
+ return builderInjector.getFragmentComponentBuilderProvider().get()
+ .setFragment(fragment).build()
}
- override fun onCloseAppButtonClicked() = splashActivityPresenter.handleOnCloseAppButtonClicked()
+ override fun onCloseAppButtonClicked() = splashActivityPresenter
+ .handleOnDeprecationNoticeCloseAppButtonClicked()
override fun onBetaNoticeOkayButtonClicked(permanentlyDismiss: Boolean) =
splashActivityPresenter.handleOnBetaNoticeOkayButtonClicked(permanentlyDismiss)
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 cfdf37874bf..dd926f56612 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
@@ -1,5 +1,8 @@
package org.oppia.android.app.splash
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.DialogFragment
@@ -64,12 +67,46 @@ class SplashActivityPresenter @Inject constructor(
subscribeToOnboardingFlow()
}
- fun handleOnCloseAppButtonClicked() {
+ /** 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
// the app.
activity.finish()
}
+ /** Handles cases where the user clicks the update option on a deprecation notice dialog. */
+ 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
+
+ try {
+ activity.startActivity(
+ Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName"))
+ )
+ } catch (e: ActivityNotFoundException) {
+ activity.startActivity(
+ Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse(
+ "https://play.google.com/store/apps/details?id=$packageName"
+ )
+ )
+ )
+ }
+
+ // Finish splash activity to close the app in anticipation of an update.
+ activity.finish()
+ }
+
+ /** Handles cases where the user dismisses the deprecation notice dialog. */
+ 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))
+ activity.finish()
+ }
+
/** Handles cases when the user dismisses the beta notice dialog. */
fun handleOnBetaNoticeOkayButtonClicked(permanentlyDismiss: Boolean) {
if (permanentlyDismiss) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 79ec1534ff1..97547bc1638 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -472,9 +472,48 @@
completed_story_list_recyclerview_tag
Please select all correct choices.
- Unsupported app version
- This version of the app is no longer supported. Please update it through the Play Store.
- Close app
+
+ Unsupported app version
+
+
+ This version of the app is no longer supported. Please update it through the Play Store.
+
+
+ Close app
+
+
+ App update required
+
+
+ A new version of %s is now available. The new version is more secure, and improves your learning experience.\n\nThis version is no longer supported. To continue using the app, please update to the latest version.
+
+
+ Update
+
+
+ Close app
+
+
+ New update available
+
+
+ A new version of %s is now available. We recommend that you update the app for bug fixes and a better learning experience.
+
+
+ Dismiss
+
+
+ Update
+
+
+ Update your Android OS
+
+
+ We recommend updating your Android OS to take advantage of %s\'s new features and lessons.\n\nVisit your phone\'s Settings app to update your OS.
+
+
+ Dismiss
+
Developer Build
Alpha
Beta
diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel
index 20d4828910e..d0733552b3b 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel
+++ b/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel
@@ -80,4 +80,88 @@ app_test(
],
)
+app_test(
+ name = "ForcedAppDeprecationNoticeDialogFragmentTest",
+ processed_src = test_with_resources("ForcedAppDeprecationNoticeDialogFragmentTest.kt"),
+ test_class = "org.oppia.android.app.notice.ForcedAppDeprecationNoticeDialogFragmentTest",
+ deps = [
+ ":dagger",
+ "//app",
+ "//app/src/main/java/org/oppia/android/app/application:application_component",
+ "//app/src/main/java/org/oppia/android/app/application:application_injector",
+ "//app/src/main/java/org/oppia/android/app/application:application_injector_provider",
+ "//app/src/main/java/org/oppia/android/app/application:common_application_modules",
+ "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module",
+ "//app/src/main/java/org/oppia/android/app/notice/testing:forced_app_deprecation_notice_dialog_fragment_test_activity",
+ "//app/src/main/java/org/oppia/android/app/translation/testing:test_module",
+ "//testing",
+ "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule",
+ "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module",
+ "//testing/src/main/java/org/oppia/android/testing/threading:test_module",
+ "//testing/src/main/java/org/oppia/android/testing/time:test_module",
+ "//third_party:androidx_test_espresso_espresso-core",
+ "//third_party:robolectric_android-all",
+ "//utility/src/main/java/org/oppia/android/util/accessibility:test_module",
+ "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module",
+ "//utility/src/main/java/org/oppia/android/util/logging:standard_event_logging_configuration_module",
+ "//utility/src/main/java/org/oppia/android/util/networking:debug_module",
+ ],
+)
+
+app_test(
+ name = "OptionalAppDeprecationNoticeDialogFragmentTest",
+ processed_src = test_with_resources("OptionalAppDeprecationNoticeDialogFragmentTest.kt"),
+ test_class = "org.oppia.android.app.notice.OptionalAppDeprecationNoticeDialogFragmentTest",
+ deps = [
+ ":dagger",
+ "//app",
+ "//app/src/main/java/org/oppia/android/app/application:application_component",
+ "//app/src/main/java/org/oppia/android/app/application:application_injector",
+ "//app/src/main/java/org/oppia/android/app/application:application_injector_provider",
+ "//app/src/main/java/org/oppia/android/app/application:common_application_modules",
+ "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module",
+ "//app/src/main/java/org/oppia/android/app/notice/testing:optional_app_deprecation_notice_dialog_fragment_test_activity",
+ "//app/src/main/java/org/oppia/android/app/translation/testing:test_module",
+ "//testing",
+ "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule",
+ "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module",
+ "//testing/src/main/java/org/oppia/android/testing/threading:test_module",
+ "//testing/src/main/java/org/oppia/android/testing/time:test_module",
+ "//third_party:androidx_test_espresso_espresso-core",
+ "//third_party:robolectric_android-all",
+ "//utility/src/main/java/org/oppia/android/util/accessibility:test_module",
+ "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module",
+ "//utility/src/main/java/org/oppia/android/util/logging:standard_event_logging_configuration_module",
+ "//utility/src/main/java/org/oppia/android/util/networking:debug_module",
+ ],
+)
+
+app_test(
+ name = "OsDeprecationNoticeDialogFragmentTest",
+ processed_src = test_with_resources("OsDeprecationNoticeDialogFragmentTest.kt"),
+ test_class = "org.oppia.android.app.notice.OsDeprecationNoticeDialogFragmentTest",
+ deps = [
+ ":dagger",
+ "//app",
+ "//app/src/main/java/org/oppia/android/app/application:application_component",
+ "//app/src/main/java/org/oppia/android/app/application:application_injector",
+ "//app/src/main/java/org/oppia/android/app/application:application_injector_provider",
+ "//app/src/main/java/org/oppia/android/app/application:common_application_modules",
+ "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module",
+ "//app/src/main/java/org/oppia/android/app/notice/testing:os_deprecation_notice_dialog_fragment_test_activity",
+ "//app/src/main/java/org/oppia/android/app/translation/testing:test_module",
+ "//testing",
+ "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule",
+ "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module",
+ "//testing/src/main/java/org/oppia/android/testing/threading:test_module",
+ "//testing/src/main/java/org/oppia/android/testing/time:test_module",
+ "//third_party:androidx_test_espresso_espresso-core",
+ "//third_party:robolectric_android-all",
+ "//utility/src/main/java/org/oppia/android/util/accessibility:test_module",
+ "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module",
+ "//utility/src/main/java/org/oppia/android/util/logging:standard_event_logging_configuration_module",
+ "//utility/src/main/java/org/oppia/android/util/networking:debug_module",
+ ],
+)
+
dagger_rules()
diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt
new file mode 100644
index 00000000000..d0b1aa4fc7e
--- /dev/null
+++ b/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt
@@ -0,0 +1,274 @@
+package org.oppia.android.app.notice
+
+import android.app.Application
+import android.content.Context
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.RootMatchers.isDialog
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.BindsInstance
+import dagger.Component
+import org.hamcrest.Matcher
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponent
+import org.oppia.android.app.activity.ActivityComponentFactory
+import org.oppia.android.app.activity.route.ActivityRouterModule
+import org.oppia.android.app.application.ApplicationComponent
+import org.oppia.android.app.application.ApplicationInjector
+import org.oppia.android.app.application.ApplicationInjectorProvider
+import org.oppia.android.app.application.ApplicationModule
+import org.oppia.android.app.application.ApplicationStartupListenerModule
+import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.devoptions.DeveloperOptionsModule
+import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.notice.testing.ForcedAppDeprecationNoticeDialogFragmentTestActivity
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
+import org.oppia.android.app.shim.ViewBindingShimModule
+import org.oppia.android.app.splash.DeprecationNoticeActionType
+import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule
+import org.oppia.android.data.backends.gae.NetworkConfigProdModule
+import org.oppia.android.data.backends.gae.NetworkModule
+import org.oppia.android.domain.classify.InteractionsModule
+import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
+import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule
+import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule
+import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule
+import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule
+import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule
+import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule
+import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule
+import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule
+import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule
+import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule
+import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule
+import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule
+import org.oppia.android.domain.exploration.ExplorationProgressModule
+import org.oppia.android.domain.exploration.ExplorationStorageModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigFastShowTestModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule
+import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule
+import org.oppia.android.domain.oppialogger.LogStorageModule
+import org.oppia.android.domain.oppialogger.LoggingIdentifierModule
+import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
+import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
+import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
+import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
+import org.oppia.android.domain.platformparameter.PlatformParameterModule
+import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
+import org.oppia.android.domain.question.QuestionModule
+import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule
+import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
+import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.TestImageLoaderModule
+import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.robolectric.RobolectricModule
+import org.oppia.android.testing.threading.TestCoroutineDispatchers
+import org.oppia.android.testing.threading.TestDispatcherModule
+import org.oppia.android.testing.time.FakeOppiaClockModule
+import org.oppia.android.util.accessibility.AccessibilityTestModule
+import org.oppia.android.util.caching.AssetModule
+import org.oppia.android.util.caching.testing.CachingTestModule
+import org.oppia.android.util.gcsresource.GcsResourceModule
+import org.oppia.android.util.locale.LocaleProdModule
+import org.oppia.android.util.logging.EventLoggingConfigurationModule
+import org.oppia.android.util.logging.LoggerModule
+import org.oppia.android.util.logging.SyncStatusModule
+import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule
+import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule
+import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
+import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
+import org.oppia.android.util.parser.image.ImageParsingModule
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Tests for [ForcedAppDeprecationNoticeDialogFragment]. */
+// FunctionName: test names are conventionally named with underscores.
+@Suppress("FunctionName")
+@RunWith(AndroidJUnit4::class)
+@Config(
+ application = ForcedAppDeprecationNoticeDialogFragmentTest.TestApplication::class,
+ qualifiers = "port-xxhdpi"
+)
+@LooperMode(LooperMode.Mode.PAUSED)
+class ForcedAppDeprecationNoticeDialogFragmentTest {
+ @get:Rule
+ val initializeDefaultLocaleRule = InitializeDefaultLocaleRule()
+
+ @get:Rule
+ val oppiaTestRule = OppiaTestRule()
+
+ @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Inject
+ lateinit var testCoroutineDispatchers: TestCoroutineDispatchers
+
+ @Mock
+ lateinit var mockDeprecationNoticeActionListener: DeprecationNoticeActionListener
+
+ @Inject
+ lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ setUpTestApplicationComponent()
+ }
+
+ @Test
+ fun testFragment_hasExpectedTitle() {
+ launchForcedAppDeprecationNoticeDialogFragmentTestActivity {
+ onDialogView(withText(R.string.forced_app_update_dialog_title)).check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_hasExpectedContentMessageTextUnderTitle() {
+ launchForcedAppDeprecationNoticeDialogFragmentTestActivity {
+ val appName = context.resources.getString(R.string.app_name)
+ val expectedString = context.resources.getString(
+ R.string.forced_app_update_dialog_message,
+ appName
+ )
+ onDialogView(withText(expectedString)).check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_hasUpdateButton() {
+ launchForcedAppDeprecationNoticeDialogFragmentTestActivity {
+ onDialogView(withText(R.string.forced_app_update_dialog_update_button_text))
+ .check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_clickOnUpdateButton_callsCallbackListener_withUpdateDeprecationActionType() {
+ launchForcedAppDeprecationNoticeDialogFragmentTestActivity {
+ clickOnDialogView(withText(R.string.forced_app_update_dialog_update_button_text))
+
+ verify(mockDeprecationNoticeActionListener)
+ .onActionButtonClicked(DeprecationNoticeActionType.UPDATE)
+ }
+ }
+
+ @Test
+ fun testFragment_hasCloseAppButton() {
+ launchForcedAppDeprecationNoticeDialogFragmentTestActivity {
+ onDialogView(withText(R.string.forced_app_update_dialog_close_button_text))
+ .check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_clockOnCloseAppButton_callsCallbackListener_withCloseDeprecationActionType() {
+ launchForcedAppDeprecationNoticeDialogFragmentTestActivity {
+ clickOnDialogView(withText(R.string.forced_app_update_dialog_close_button_text))
+
+ verify(mockDeprecationNoticeActionListener)
+ .onActionButtonClicked(DeprecationNoticeActionType.CLOSE)
+ }
+ }
+
+ private fun launchForcedAppDeprecationNoticeDialogFragmentTestActivity(
+ testBlock: () -> Unit
+ ) {
+ // Launch the test activity, but make sure that it's properly set up & time is given for it to
+ // initialize.
+ ActivityScenario.launch(
+ ForcedAppDeprecationNoticeDialogFragmentTestActivity::class.java
+ ).use { scenario ->
+ scenario.onActivity { it.mockCallbackListener = mockDeprecationNoticeActionListener }
+ testCoroutineDispatchers.runCurrent()
+ testBlock()
+ }
+ }
+
+ private fun clickOnDialogView(matcher: Matcher) {
+ onDialogView(matcher).perform(ViewActions.click())
+ testCoroutineDispatchers.runCurrent()
+ }
+
+ private fun setUpTestApplicationComponent() {
+ ApplicationProvider.getApplicationContext().inject(this)
+ }
+
+ private companion object {
+ private fun onDialogView(matcher: Matcher) = onView(matcher).inRoot(isDialog())
+ }
+
+ @Singleton
+ @Component(
+ modules = [
+ RobolectricModule::class, PlatformParameterModule::class,
+ TestDispatcherModule::class, ApplicationModule::class, LoggerModule::class,
+ ContinueModule::class, FractionInputModule::class, ItemSelectionInputModule::class,
+ MultipleChoiceInputModule::class, NumberWithUnitsRuleModule::class,
+ NumericInputRuleModule::class, TextInputRuleModule::class, DragDropSortInputModule::class,
+ ImageClickInputModule::class, InteractionsModule::class, GcsResourceModule::class,
+ TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class,
+ QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class,
+ LogStorageModule::class, PrimeTopicAssetsControllerModule::class,
+ ExpirationMetaDataRetrieverModule::class, ViewBindingShimModule::class,
+ RatioInputModule::class, ApplicationStartupListenerModule::class,
+ HintsAndSolutionConfigFastShowTestModule::class, HintsAndSolutionProdModule::class,
+ WorkManagerConfigurationModule::class, LogReportWorkerModule::class,
+ FirebaseLogUploaderModule::class, FakeOppiaClockModule::class,
+ DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class,
+ ExplorationStorageModule::class, NetworkConnectionUtilDebugModule::class,
+ NetworkConnectionDebugUtilModule::class, NetworkModule::class, NetworkConfigProdModule::class,
+ AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class,
+ PlatformParameterSingletonModule::class, NumericExpressionInputModule::class,
+ AlgebraicExpressionInputModule::class, MathEquationInputModule::class,
+ SplitScreenInteractionModule::class, LoggingIdentifierModule::class,
+ ApplicationLifecycleModule::class, SyncStatusModule::class, TestingBuildFlavorModule::class,
+ CachingTestModule::class, MetricLogSchedulerModule::class,
+ EventLoggingConfigurationModule::class, ActivityRouterModule::class,
+ CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class
+ ]
+ )
+
+ interface TestApplicationComponent : ApplicationComponent {
+ @Component.Builder
+ interface Builder {
+ @BindsInstance
+ fun setApplication(application: Application): Builder
+
+ fun build(): TestApplicationComponent
+ }
+
+ fun inject(test: ForcedAppDeprecationNoticeDialogFragmentTest)
+ }
+
+ class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider {
+ private val component: TestApplicationComponent by lazy {
+ DaggerForcedAppDeprecationNoticeDialogFragmentTest_TestApplicationComponent.builder()
+ .setApplication(this)
+ .build()
+ }
+
+ fun inject(test: ForcedAppDeprecationNoticeDialogFragmentTest) = component.inject(test)
+
+ override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent {
+ return component.getActivityComponentBuilderProvider().get().setActivity(activity).build()
+ }
+
+ override fun getApplicationInjector(): ApplicationInjector = component
+ }
+}
diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt
new file mode 100644
index 00000000000..c9545e4e324
--- /dev/null
+++ b/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt
@@ -0,0 +1,276 @@
+package org.oppia.android.app.notice
+
+import android.app.Application
+import android.content.Context
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.RootMatchers.isDialog
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.BindsInstance
+import dagger.Component
+import org.hamcrest.Matcher
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponent
+import org.oppia.android.app.activity.ActivityComponentFactory
+import org.oppia.android.app.activity.route.ActivityRouterModule
+import org.oppia.android.app.application.ApplicationComponent
+import org.oppia.android.app.application.ApplicationInjector
+import org.oppia.android.app.application.ApplicationInjectorProvider
+import org.oppia.android.app.application.ApplicationModule
+import org.oppia.android.app.application.ApplicationStartupListenerModule
+import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.devoptions.DeveloperOptionsModule
+import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.notice.testing.OptionalAppDeprecationNoticeDialogFragmentTestActivity
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
+import org.oppia.android.app.shim.ViewBindingShimModule
+import org.oppia.android.app.splash.DeprecationNoticeActionType
+import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule
+import org.oppia.android.data.backends.gae.NetworkConfigProdModule
+import org.oppia.android.data.backends.gae.NetworkModule
+import org.oppia.android.domain.classify.InteractionsModule
+import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
+import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule
+import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule
+import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule
+import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule
+import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule
+import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule
+import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule
+import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule
+import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule
+import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule
+import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule
+import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule
+import org.oppia.android.domain.exploration.ExplorationProgressModule
+import org.oppia.android.domain.exploration.ExplorationStorageModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigFastShowTestModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule
+import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule
+import org.oppia.android.domain.oppialogger.LogStorageModule
+import org.oppia.android.domain.oppialogger.LoggingIdentifierModule
+import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
+import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
+import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
+import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
+import org.oppia.android.domain.platformparameter.PlatformParameterModule
+import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
+import org.oppia.android.domain.question.QuestionModule
+import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule
+import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
+import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.TestImageLoaderModule
+import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.robolectric.RobolectricModule
+import org.oppia.android.testing.threading.TestCoroutineDispatchers
+import org.oppia.android.testing.threading.TestDispatcherModule
+import org.oppia.android.testing.time.FakeOppiaClockModule
+import org.oppia.android.util.accessibility.AccessibilityTestModule
+import org.oppia.android.util.caching.AssetModule
+import org.oppia.android.util.caching.testing.CachingTestModule
+import org.oppia.android.util.gcsresource.GcsResourceModule
+import org.oppia.android.util.locale.LocaleProdModule
+import org.oppia.android.util.logging.EventLoggingConfigurationModule
+import org.oppia.android.util.logging.LoggerModule
+import org.oppia.android.util.logging.SyncStatusModule
+import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule
+import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule
+import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
+import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
+import org.oppia.android.util.parser.image.ImageParsingModule
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Tests for [ForcedAppDeprecationNoticeDialogFragment]. */
+// FunctionName: test names are conventionally named with underscores.
+@Suppress("FunctionName")
+@RunWith(AndroidJUnit4::class)
+@Config(
+ application = OptionalAppDeprecationNoticeDialogFragmentTest.TestApplication::class,
+ qualifiers = "port-xxhdpi"
+)
+@LooperMode(LooperMode.Mode.PAUSED)
+class OptionalAppDeprecationNoticeDialogFragmentTest {
+ @get:Rule
+ val initializeDefaultLocaleRule = InitializeDefaultLocaleRule()
+
+ @get:Rule
+ val oppiaTestRule = OppiaTestRule()
+
+ @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Inject
+ lateinit var testCoroutineDispatchers: TestCoroutineDispatchers
+
+ @Mock
+ lateinit var mockDeprecationNoticeActionListener: DeprecationNoticeActionListener
+
+ @Inject
+ lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ setUpTestApplicationComponent()
+ }
+
+ @Test
+ fun testFragment_hasExpectedTitle() {
+ launchOptionalAppDeprecationNoticeDialogFragmentTestActivity {
+ onDialogView(withText(R.string.optional_app_update_dialog_title))
+ .check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_hasExpectedContentMessageTextUnderTitle() {
+ launchOptionalAppDeprecationNoticeDialogFragmentTestActivity {
+ val appName = context.resources.getString(R.string.app_name)
+ val expectedString = context.resources.getString(
+ R.string.optional_app_update_dialog_message,
+ appName
+ )
+ onDialogView(withText(expectedString)).check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_hasUpdateButton() {
+ launchOptionalAppDeprecationNoticeDialogFragmentTestActivity {
+ onDialogView(withText(R.string.optional_app_update_dialog_update_button_text))
+ .check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_clickOnUpdateButton_callsCallbackListener_withUpdateDeprecationActionType() {
+ launchOptionalAppDeprecationNoticeDialogFragmentTestActivity {
+ clickOnDialogView(withText(R.string.optional_app_update_dialog_update_button_text))
+
+ verify(mockDeprecationNoticeActionListener)
+ .onActionButtonClicked(DeprecationNoticeActionType.UPDATE)
+ }
+ }
+
+ @Test
+ fun testFragment_hasDismissButton() {
+ launchOptionalAppDeprecationNoticeDialogFragmentTestActivity {
+ onDialogView(withText(R.string.optional_app_update_dialog_dismiss_button_text))
+ .check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_clickOnDismissButton_callsCallbackListener_withDismissDeprecationActionType() {
+ launchOptionalAppDeprecationNoticeDialogFragmentTestActivity {
+ clickOnDialogView(withText(R.string.optional_app_update_dialog_dismiss_button_text))
+
+ verify(mockDeprecationNoticeActionListener)
+ .onActionButtonClicked(DeprecationNoticeActionType.DISMISS)
+ }
+ }
+
+ private fun launchOptionalAppDeprecationNoticeDialogFragmentTestActivity(
+ testBlock: () -> Unit
+ ) {
+ // Launch the test activity, but make sure that it's properly set up & time is given for it to
+ // initialize.
+ ActivityScenario.launch(
+ OptionalAppDeprecationNoticeDialogFragmentTestActivity::class.java
+ ).use { scenario ->
+ scenario.onActivity { it.mockCallbackListener = mockDeprecationNoticeActionListener }
+ testCoroutineDispatchers.runCurrent()
+ testBlock()
+ }
+ }
+
+ private fun clickOnDialogView(matcher: Matcher) {
+ onDialogView(matcher).perform(ViewActions.click())
+ testCoroutineDispatchers.runCurrent()
+ }
+
+ private fun setUpTestApplicationComponent() {
+ ApplicationProvider.getApplicationContext().inject(this)
+ }
+
+ private companion object {
+ private fun onDialogView(matcher: Matcher) = Espresso.onView(matcher)
+ .inRoot(isDialog())
+ }
+
+ @Singleton
+ @Component(
+ modules = [
+ RobolectricModule::class, PlatformParameterModule::class,
+ TestDispatcherModule::class, ApplicationModule::class, LoggerModule::class,
+ ContinueModule::class, FractionInputModule::class, ItemSelectionInputModule::class,
+ MultipleChoiceInputModule::class, NumberWithUnitsRuleModule::class,
+ NumericInputRuleModule::class, TextInputRuleModule::class, DragDropSortInputModule::class,
+ ImageClickInputModule::class, InteractionsModule::class, GcsResourceModule::class,
+ TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class,
+ QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class,
+ LogStorageModule::class, PrimeTopicAssetsControllerModule::class,
+ ExpirationMetaDataRetrieverModule::class, ViewBindingShimModule::class,
+ RatioInputModule::class, ApplicationStartupListenerModule::class,
+ HintsAndSolutionConfigFastShowTestModule::class, HintsAndSolutionProdModule::class,
+ WorkManagerConfigurationModule::class, LogReportWorkerModule::class,
+ FirebaseLogUploaderModule::class, FakeOppiaClockModule::class,
+ DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class,
+ ExplorationStorageModule::class, NetworkConnectionUtilDebugModule::class,
+ NetworkConnectionDebugUtilModule::class, NetworkModule::class, NetworkConfigProdModule::class,
+ AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class,
+ PlatformParameterSingletonModule::class, NumericExpressionInputModule::class,
+ AlgebraicExpressionInputModule::class, MathEquationInputModule::class,
+ SplitScreenInteractionModule::class, LoggingIdentifierModule::class,
+ ApplicationLifecycleModule::class, SyncStatusModule::class, TestingBuildFlavorModule::class,
+ CachingTestModule::class, MetricLogSchedulerModule::class,
+ EventLoggingConfigurationModule::class, ActivityRouterModule::class,
+ CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class
+ ]
+ )
+
+ interface TestApplicationComponent : ApplicationComponent {
+ @Component.Builder
+ interface Builder {
+ @BindsInstance
+ fun setApplication(application: Application): Builder
+
+ fun build(): TestApplicationComponent
+ }
+
+ fun inject(test: OptionalAppDeprecationNoticeDialogFragmentTest)
+ }
+
+ class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider {
+ private val component: TestApplicationComponent by lazy {
+ DaggerOptionalAppDeprecationNoticeDialogFragmentTest_TestApplicationComponent.builder()
+ .setApplication(this)
+ .build()
+ }
+
+ fun inject(test: OptionalAppDeprecationNoticeDialogFragmentTest) = component.inject(test)
+
+ override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent {
+ return component.getActivityComponentBuilderProvider().get().setActivity(activity).build()
+ }
+
+ override fun getApplicationInjector(): ApplicationInjector = component
+ }
+}
diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt
new file mode 100644
index 00000000000..becd031ecdb
--- /dev/null
+++ b/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt
@@ -0,0 +1,258 @@
+package org.oppia.android.app.notice
+
+import android.app.Application
+import android.content.Context
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.RootMatchers.isDialog
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.BindsInstance
+import dagger.Component
+import org.hamcrest.Matcher
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponent
+import org.oppia.android.app.activity.ActivityComponentFactory
+import org.oppia.android.app.activity.route.ActivityRouterModule
+import org.oppia.android.app.application.ApplicationComponent
+import org.oppia.android.app.application.ApplicationInjector
+import org.oppia.android.app.application.ApplicationInjectorProvider
+import org.oppia.android.app.application.ApplicationModule
+import org.oppia.android.app.application.ApplicationStartupListenerModule
+import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.devoptions.DeveloperOptionsModule
+import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.notice.testing.OsDeprecationNoticeDialogFragmentTestActivity
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
+import org.oppia.android.app.shim.ViewBindingShimModule
+import org.oppia.android.app.splash.DeprecationNoticeActionType
+import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule
+import org.oppia.android.data.backends.gae.NetworkConfigProdModule
+import org.oppia.android.data.backends.gae.NetworkModule
+import org.oppia.android.domain.classify.InteractionsModule
+import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
+import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule
+import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule
+import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule
+import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule
+import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule
+import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule
+import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule
+import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule
+import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule
+import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule
+import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule
+import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule
+import org.oppia.android.domain.exploration.ExplorationProgressModule
+import org.oppia.android.domain.exploration.ExplorationStorageModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigFastShowTestModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule
+import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule
+import org.oppia.android.domain.oppialogger.LogStorageModule
+import org.oppia.android.domain.oppialogger.LoggingIdentifierModule
+import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
+import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
+import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
+import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
+import org.oppia.android.domain.platformparameter.PlatformParameterModule
+import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
+import org.oppia.android.domain.question.QuestionModule
+import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule
+import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
+import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.TestImageLoaderModule
+import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.robolectric.RobolectricModule
+import org.oppia.android.testing.threading.TestCoroutineDispatchers
+import org.oppia.android.testing.threading.TestDispatcherModule
+import org.oppia.android.testing.time.FakeOppiaClockModule
+import org.oppia.android.util.accessibility.AccessibilityTestModule
+import org.oppia.android.util.caching.AssetModule
+import org.oppia.android.util.caching.testing.CachingTestModule
+import org.oppia.android.util.gcsresource.GcsResourceModule
+import org.oppia.android.util.locale.LocaleProdModule
+import org.oppia.android.util.logging.EventLoggingConfigurationModule
+import org.oppia.android.util.logging.LoggerModule
+import org.oppia.android.util.logging.SyncStatusModule
+import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule
+import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule
+import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
+import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
+import org.oppia.android.util.parser.image.ImageParsingModule
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Tests for [ForcedAppDeprecationNoticeDialogFragment]. */
+// FunctionName: test names are conventionally named with underscores.
+@Suppress("FunctionName")
+@RunWith(AndroidJUnit4::class)
+@Config(
+ application = OsDeprecationNoticeDialogFragmentTest.TestApplication::class,
+ qualifiers = "port-xxhdpi"
+)
+@LooperMode(LooperMode.Mode.PAUSED)
+class OsDeprecationNoticeDialogFragmentTest {
+ @get:Rule
+ val initializeDefaultLocaleRule = InitializeDefaultLocaleRule()
+
+ @get:Rule
+ val oppiaTestRule = OppiaTestRule()
+
+ @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Inject
+ lateinit var testCoroutineDispatchers: TestCoroutineDispatchers
+
+ @Mock
+ lateinit var mockDeprecationNoticeActionListener: DeprecationNoticeActionListener
+
+ @Inject
+ lateinit var context: Context
+
+ @Before
+ fun setUp() {
+ setUpTestApplicationComponent()
+ }
+
+ @Test
+ fun testFragment_hasExpectedTitle() {
+ launchOsDeprecationNoticeDialogFragmentTestActivity {
+ onDialogView(withText(R.string.os_deprecation_dialog_title))
+ .check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_hasExpectedContentMessageTextUnderTitle() {
+ launchOsDeprecationNoticeDialogFragmentTestActivity {
+ val appName = context.resources.getString(R.string.app_name)
+ val expectedString = context.resources.getString(
+ R.string.os_deprecation_dialog_message,
+ appName
+ )
+ onDialogView(withText(expectedString)).check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_hasDismissButton() {
+ launchOsDeprecationNoticeDialogFragmentTestActivity {
+ onDialogView(withText(R.string.os_deprecation_dialog_dismiss_button_text))
+ .check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun testFragment_clickOnDismissButton_callsCallbackListener_withDismissDeprecationActionType() {
+ launchOsDeprecationNoticeDialogFragmentTestActivity {
+ clickOnDialogView(withText(R.string.os_deprecation_dialog_dismiss_button_text))
+
+ verify(mockDeprecationNoticeActionListener)
+ .onActionButtonClicked(DeprecationNoticeActionType.DISMISS)
+ }
+ }
+
+ private fun launchOsDeprecationNoticeDialogFragmentTestActivity(
+ testBlock: () -> Unit
+ ) {
+ // Launch the test activity, but make sure that it's properly set up & time is given for it to
+ // initialize.
+ ActivityScenario.launch(
+ OsDeprecationNoticeDialogFragmentTestActivity::class.java
+ ).use { scenario ->
+ scenario.onActivity { it.mockCallbackListener = mockDeprecationNoticeActionListener }
+ testCoroutineDispatchers.runCurrent()
+ testBlock()
+ }
+ }
+
+ private fun clickOnDialogView(matcher: Matcher) {
+ onDialogView(matcher).perform(ViewActions.click())
+ testCoroutineDispatchers.runCurrent()
+ }
+
+ private fun setUpTestApplicationComponent() {
+ ApplicationProvider.getApplicationContext().inject(this)
+ }
+
+ private companion object {
+ private fun onDialogView(matcher: Matcher) = Espresso.onView(matcher)
+ .inRoot(isDialog())
+ }
+
+ @Singleton
+ @Component(
+ modules = [
+ RobolectricModule::class, PlatformParameterModule::class,
+ TestDispatcherModule::class, ApplicationModule::class, LoggerModule::class,
+ ContinueModule::class, FractionInputModule::class, ItemSelectionInputModule::class,
+ MultipleChoiceInputModule::class, NumberWithUnitsRuleModule::class,
+ NumericInputRuleModule::class, TextInputRuleModule::class, DragDropSortInputModule::class,
+ ImageClickInputModule::class, InteractionsModule::class, GcsResourceModule::class,
+ TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class,
+ QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class,
+ LogStorageModule::class, PrimeTopicAssetsControllerModule::class,
+ ExpirationMetaDataRetrieverModule::class, ViewBindingShimModule::class,
+ RatioInputModule::class, ApplicationStartupListenerModule::class,
+ HintsAndSolutionConfigFastShowTestModule::class, HintsAndSolutionProdModule::class,
+ WorkManagerConfigurationModule::class, LogReportWorkerModule::class,
+ FirebaseLogUploaderModule::class, FakeOppiaClockModule::class,
+ DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class,
+ ExplorationStorageModule::class, NetworkConnectionUtilDebugModule::class,
+ NetworkConnectionDebugUtilModule::class, NetworkModule::class, NetworkConfigProdModule::class,
+ AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class,
+ PlatformParameterSingletonModule::class, NumericExpressionInputModule::class,
+ AlgebraicExpressionInputModule::class, MathEquationInputModule::class,
+ SplitScreenInteractionModule::class, LoggingIdentifierModule::class,
+ ApplicationLifecycleModule::class, SyncStatusModule::class, TestingBuildFlavorModule::class,
+ CachingTestModule::class, MetricLogSchedulerModule::class,
+ EventLoggingConfigurationModule::class, ActivityRouterModule::class,
+ CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class
+ ]
+ )
+
+ interface TestApplicationComponent : ApplicationComponent {
+ @Component.Builder
+ interface Builder {
+ @BindsInstance
+ fun setApplication(application: Application): Builder
+
+ fun build(): TestApplicationComponent
+ }
+
+ fun inject(test: OsDeprecationNoticeDialogFragmentTest)
+ }
+
+ class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider {
+ private val component: TestApplicationComponent by lazy {
+ DaggerOsDeprecationNoticeDialogFragmentTest_TestApplicationComponent.builder()
+ .setApplication(this)
+ .build()
+ }
+
+ fun inject(test: OsDeprecationNoticeDialogFragmentTest) = component.inject(test)
+
+ override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent {
+ return component.getActivityComponentBuilderProvider().get().setActivity(activity).build()
+ }
+
+ override fun getApplicationInjector(): ApplicationInjector = component
+ }
+}
diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto
index 699f8b9a9cd..382bd7c1efa 100644
--- a/scripts/assets/test_file_exemptions.textproto
+++ b/scripts/assets/test_file_exemptions.textproto
@@ -235,8 +235,15 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/MyDownl
exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/MyDownloadsViewPagerAdapter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/UpdatesTabFragment.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/UpdatesTabFragmentPresenter.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeActionListener.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentPresenter.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/BetaNoticeDialogFragmentTestActivity.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboadingSlideViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingActivityPresenter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt"