From 96923060bc675d26f86a3e8a32a82d28f1f16600 Mon Sep 17 00:00:00 2001 From: Kenneth Murerwa Date: Wed, 12 Jul 2023 18:57:51 +0300 Subject: [PATCH] Fix part of #5025: App and OS Deprecation Milestone 2 - Add protos and the DeprecationController (#4999) ## Explanation Fix part of #5025: When this PR is merged, it will; - Add a new `deprecation.proto` file that will allow the storage of deprecation responses as well as provide the various deprecation types. - Add the `OPTIONAL_UPDATE_AVAILABLE` and the `OS_IS_DEPRECATED` startup modes on the `onboarding.proto` file for the two new startup modes being introduced. - Create a `DeprecationController` class and add tests for the class in the `DeprecationControllerTest`. - Modify BUILD.bazel files to provide the new proto files and the deprecation controller. - Add `DeprecationControllerTest` to the `OppiaParameterizedTestRunner` file exemptions on the `file_content_validation_checks.textproto`. ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). --------- Co-authored-by: Kenneth Murerwa Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> --- domain/BUILD.bazel | 1 + .../android/domain/onboarding/BUILD.bazel | 23 ++ .../onboarding/DeprecationController.kt | 117 ++++++++ .../android/domain/onboarding/BUILD.bazel | 31 ++ .../onboarding/DeprecationControllerTest.kt | 279 ++++++++++++++++++ model/src/main/proto/BUILD.bazel | 12 + model/src/main/proto/deprecation.proto | 47 +++ model/src/main/proto/onboarding.proto | 9 + .../file_content_validation_checks.textproto | 1 + 9 files changed, 520 insertions(+) create mode 100644 domain/src/main/java/org/oppia/android/domain/onboarding/DeprecationController.kt create mode 100644 domain/src/test/java/org/oppia/android/domain/onboarding/DeprecationControllerTest.kt create mode 100644 model/src/main/proto/deprecation.proto diff --git a/domain/BUILD.bazel b/domain/BUILD.bazel index 1590004cf89..79bcd289f08 100755 --- a/domain/BUILD.bazel +++ b/domain/BUILD.bazel @@ -199,6 +199,7 @@ TEST_DEPS = [ "//domain/src/main/java/org/oppia/android/domain/classify/rules/textinput:text_input_rule_module", "//domain/src/main/java/org/oppia/android/domain/feedbackreporting:prod_module", "//domain/src/main/java/org/oppia/android/domain/feedbackreporting:report_schema_version", + "//domain/src/main/java/org/oppia/android/domain/onboarding:deprecation_controller", "//domain/src/main/java/org/oppia/android/domain/onboarding:retriever_prod_module", "//domain/src/main/java/org/oppia/android/domain/onboarding:state_controller", "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", diff --git a/domain/src/main/java/org/oppia/android/domain/onboarding/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/onboarding/BUILD.bazel index f3ba2025c05..ae8e6e2d0ef 100644 --- a/domain/src/main/java/org/oppia/android/domain/onboarding/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/onboarding/BUILD.bazel @@ -9,14 +9,17 @@ kt_android_library( name = "state_controller", srcs = [ "AppStartupStateController.kt", + "DeprecationController.kt", ], visibility = ["//:oppia_api_visibility"], deps = [ ":exploration_meta_data_retriever", "//data/src/main/java/org/oppia/android/data/persistence:cache_store", "//domain/src/main/java/org/oppia/android/domain/oppialogger:oppia_logger", + "//model/src/main/proto:deprecation_java_proto_lite", "//model/src/main/proto:onboarding_java_proto_lite", "//third_party:javax_inject_javax_inject", + "//utility", "//utility/src/main/java/org/oppia/android/util/data:data_provider", "//utility/src/main/java/org/oppia/android/util/data:data_providers", "//utility/src/main/java/org/oppia/android/util/extensions:bundle_extensions", @@ -55,4 +58,24 @@ kt_android_library( ], ) +kt_android_library( + name = "deprecation_controller", + srcs = [ + "DeprecationController.kt", + ], + visibility = ["//:oppia_api_visibility"], + deps = [ + ":exploration_meta_data_retriever", + "//data/src/main/java/org/oppia/android/data/persistence:cache_store", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:oppia_logger", + "//model/src/main/proto:deprecation_java_proto_lite", + "//model/src/main/proto:onboarding_java_proto_lite", + "//third_party:javax_inject_javax_inject", + "//utility", + "//utility/src/main/java/org/oppia/android/util/data:data_provider", + "//utility/src/main/java/org/oppia/android/util/data:data_providers", + "//utility/src/main/java/org/oppia/android/util/extensions:bundle_extensions", + ], +) + dagger_rules() diff --git a/domain/src/main/java/org/oppia/android/domain/onboarding/DeprecationController.kt b/domain/src/main/java/org/oppia/android/domain/onboarding/DeprecationController.kt new file mode 100644 index 00000000000..d798fe83b62 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/onboarding/DeprecationController.kt @@ -0,0 +1,117 @@ +package org.oppia.android.domain.onboarding + +import kotlinx.coroutines.Deferred +import org.oppia.android.app.model.DeprecationNoticeType +import org.oppia.android.app.model.DeprecationResponse +import org.oppia.android.app.model.DeprecationResponseDatabase +import org.oppia.android.data.persistence.PersistentCacheStore +import org.oppia.android.domain.oppialogger.OppiaLogger +import org.oppia.android.util.data.AsyncResult +import org.oppia.android.util.data.DataProvider +import org.oppia.android.util.data.DataProviders +import org.oppia.android.util.data.DataProviders.Companion.transform +import javax.inject.Inject +import javax.inject.Singleton + +private const val GET_DEPRECATION_RESPONSE_PROVIDER_ID = "get_deprecation_response_provider_id" +private const val ADD_DEPRECATION_RESPONSE_PROVIDER_ID = "add_deprecation_response_provider_id" + +/** + * Controller for persisting and retrieving the user's deprecation responses. This will be used to + * handle deprecations once the user opens the app. + */ +@Singleton +class DeprecationController @Inject constructor( + cacheStoreFactory: PersistentCacheStore.Factory, + private val oppiaLogger: OppiaLogger, + private val dataProviders: DataProviders +) { + /** Create an instance of [PersistentCacheStore] that contains a [DeprecationResponseDatabase]. */ + private val deprecationStore by lazy { + cacheStoreFactory.create( + "deprecation_store", + DeprecationResponseDatabase.getDefaultInstance() + ) + } + + /** Enum states for the possible outcomes of a deprecation action. */ + private enum class DeprecationResponseActionStatus { + /** Indicates that the deprecation response read/write operation succeeded. */ + SUCCESS + } + + init { + // Prime the cache ahead of time so that the deprecation response can be retrieved + // synchronously. + deprecationStore.primeInMemoryAndDiskCacheAsync( + updateMode = PersistentCacheStore.UpdateMode.UPDATE_ALWAYS, + publishMode = PersistentCacheStore.PublishMode.PUBLISH_TO_IN_MEMORY_CACHE + ).invokeOnCompletion { primeFailure -> + primeFailure?.let { + oppiaLogger.e( + "DeprecationController", + "Failed to prime cache ahead of data retrieval for DeprecationController.", + primeFailure + ) + } + } + } + + private val deprecationDataProvider by lazy { fetchDeprecationProvider() } + + private fun fetchDeprecationProvider(): DataProvider { + return deprecationStore.transform( + GET_DEPRECATION_RESPONSE_PROVIDER_ID + ) { deprecationResponsesDatabase -> + DeprecationResponseDatabase.newBuilder().apply { + appDeprecationResponse = deprecationResponsesDatabase.appDeprecationResponse + osDeprecationResponse = deprecationResponsesDatabase.osDeprecationResponse + }.build() + } + } + + /** + * Returns a [DataProvider] containing the the [DeprecationResponseDatabase], which in turn + * affects what initial app flow the user is directed to. + */ + fun getDeprecationDatabase(): DataProvider = deprecationDataProvider + + /** + * Stores a new [DeprecationResponse] to the cache. + * + * @param deprecationResponse the deprecation response to be stored + * @return [AsyncResult] of the deprecation action + */ + fun saveDeprecationResponse(deprecationResponse: DeprecationResponse): DataProvider { + val deferred = deprecationStore.storeDataWithCustomChannelAsync( + updateInMemoryCache = true + ) { deprecationResponseDb -> + val deprecationBuilder = deprecationResponseDb.toBuilder().apply { + if (deprecationResponse.deprecationNoticeType == DeprecationNoticeType.APP_DEPRECATION) + appDeprecationResponse = deprecationResponse + else + osDeprecationResponse = deprecationResponse + } + .build() + Pair(deprecationBuilder, DeprecationResponseActionStatus.SUCCESS) + } + + return dataProviders.createInMemoryDataProviderAsync(ADD_DEPRECATION_RESPONSE_PROVIDER_ID) { + return@createInMemoryDataProviderAsync getDeferredResult(deferred) + } + } + + /** + * Retrieves the [DeprecationResponse] from the cache. + * + * @param deferred a deferred instance of the [DeprecationResponseActionStatus] + * @return [AsyncResult] + */ + private suspend fun getDeferredResult( + deferred: Deferred + ): AsyncResult { + return when (deferred.await()) { + DeprecationResponseActionStatus.SUCCESS -> AsyncResult.Success(null) + } + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/onboarding/BUILD.bazel b/domain/src/test/java/org/oppia/android/domain/onboarding/BUILD.bazel index 85124495fd0..7014081371a 100644 --- a/domain/src/test/java/org/oppia/android/domain/onboarding/BUILD.bazel +++ b/domain/src/test/java/org/oppia/android/domain/onboarding/BUILD.bazel @@ -36,4 +36,35 @@ oppia_android_test( ], ) +oppia_android_test( + name = "DeprecationControllerTest", + srcs = ["DeprecationControllerTest.kt"], + custom_package = "org.oppia.android.domain.onboarding", + test_class = "org.oppia.android.domain.onboarding.DeprecationControllerTest", + test_manifest = "//domain:test_manifest", + deps = [ + ":dagger", + "//domain", + "//domain/src/main/java/org/oppia/android/domain/onboarding:deprecation_controller", + "//domain/src/main/java/org/oppia/android/domain/onboarding:retriever_prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/data:data_provider_test_monitor", + "//testing/src/main/java/org/oppia/android/testing/junit:oppia_parameterized_test_runner", + "//testing/src/main/java/org/oppia/android/testing/junit:parameterized_robolectric_test_runner", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_mockito_mockito-core", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + "//utility/src/main/java/org/oppia/android/util/system:prod_module", + ], +) + dagger_rules() diff --git a/domain/src/test/java/org/oppia/android/domain/onboarding/DeprecationControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/onboarding/DeprecationControllerTest.kt new file mode 100644 index 00000000000..3ef5714961d --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/onboarding/DeprecationControllerTest.kt @@ -0,0 +1,279 @@ +package org.oppia.android.domain.onboarding + +import android.app.Application +import android.content.Context +import android.os.Bundle +import androidx.test.core.app.ApplicationProvider +import androidx.test.core.content.pm.ApplicationInfoBuilder +import androidx.test.core.content.pm.PackageInfoBuilder +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.app.model.BuildFlavor +import org.oppia.android.app.model.DeprecationNoticeType +import org.oppia.android.app.model.DeprecationResponse +import org.oppia.android.app.model.DeprecationResponseDatabase +import org.oppia.android.data.persistence.PersistentCacheStore +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.platformparameter.PlatformParameterModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.data.DataProviderTestMonitor +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.SelectRunnerPlatform +import org.oppia.android.testing.junit.ParameterizedRobolectricTestRunner +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.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.EnableConsoleLog +import org.oppia.android.util.logging.EnableFileLog +import org.oppia.android.util.logging.GlobalLogLevel +import org.oppia.android.util.logging.LogLevel +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.system.OppiaClockModule +import org.robolectric.Shadows +import org.robolectric.annotation.Config +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [DeprecationController]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(OppiaParameterizedTestRunner::class) +@SelectRunnerPlatform(ParameterizedRobolectricTestRunner::class) +@Config(application = DeprecationControllerTest.TestApplication::class) +class DeprecationControllerTest { + @Inject lateinit var context: Context + @Inject lateinit var deprecationController: DeprecationController + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory + + @Test + fun testController_providesInitialState_indicatesNoUpdatesReceivedFromGatingConsole() { + val defaultDeprecationResponseDatabase = DeprecationResponseDatabase + .getDefaultInstance() + + setUpDefaultTestApplicationComponent() + + val deprecationDataProvider = deprecationController + .getDeprecationDatabase() + + val deprecationResponseDatabase = monitorFactory + .waitForNextSuccessfulResult(deprecationDataProvider) + + assertThat(deprecationResponseDatabase.osDeprecationResponse) + .isEqualTo(defaultDeprecationResponseDatabase.osDeprecationResponse) + + assertThat(deprecationResponseDatabase.appDeprecationResponse) + .isEqualTo(defaultDeprecationResponseDatabase.appDeprecationResponse) + } + + @Test + fun testController_observedAfterSettingAppDeprecation_providesUpdatedDeprecationResponse() { + executeInPreviousAppInstance { testComponent -> + val appDeprecationResponse = DeprecationResponse.newBuilder().apply { + deprecatedVersion = 5 + deprecationNoticeType = DeprecationNoticeType.APP_DEPRECATION + }.build() + + testComponent.getDeprecationController().saveDeprecationResponse(appDeprecationResponse) + testComponent.getTestCoroutineDispatchers().runCurrent() + } + + // Create the application after previous arrangement to simulate a re-creation. + setUpDefaultTestApplicationComponent() + + val deprecationDataProvider = deprecationController + .getDeprecationDatabase() + + val deprecationResponseDatabase = monitorFactory + .waitForNextSuccessfulResult(deprecationDataProvider) + + assertThat(deprecationResponseDatabase.appDeprecationResponse) + .isEqualTo( + DeprecationResponse.newBuilder().apply { + deprecatedVersion = 5 + deprecationNoticeType = DeprecationNoticeType.APP_DEPRECATION + }.build() + ) + } + + @Test + fun testController_observedAfterSettingOsDeprecation_providesUpdatedDeprecationResponse() { + executeInPreviousAppInstance { testComponent -> + val osDeprecationResponse = DeprecationResponse.newBuilder().apply { + deprecatedVersion = 5 + deprecationNoticeType = DeprecationNoticeType.OS_DEPRECATION + }.build() + + testComponent.getDeprecationController().saveDeprecationResponse(osDeprecationResponse) + testComponent.getTestCoroutineDispatchers().runCurrent() + } + + // Create the application after previous arrangement to simulate a re-creation. + setUpDefaultTestApplicationComponent() + + val deprecationDataProvider = deprecationController + .getDeprecationDatabase() + + val deprecationResponseDatabase = monitorFactory + .waitForNextSuccessfulResult(deprecationDataProvider) + + assertThat(deprecationResponseDatabase.osDeprecationResponse) + .isEqualTo( + DeprecationResponse.newBuilder().apply { + deprecatedVersion = 5 + deprecationNoticeType = DeprecationNoticeType.OS_DEPRECATION + }.build() + ) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + private fun setUpOppiaApplication(expirationEnabled: Boolean, expDate: String) { + setUpOppiaApplicationForContext(context, expirationEnabled, expDate) + } + + /** + * Creates a separate test application component and executes the specified block. This should be + * called before [setUpTestApplicationComponent] to avoid undefined behavior in production code. + * This can be used to simulate arranging state in a "prior" run of the app. + * + * Note that only dependencies fetched from the specified [TestApplicationComponent] should be + * used, not any class-level injected dependencies. + */ + private fun executeInPreviousAppInstance(block: (TestApplicationComponent) -> Unit) { + val testApplication = TestApplication() + // The true application is hooked as a base context. This is to make sure the new application + // can behave like a real Android application class (per Robolectric) without having a shared + // Dagger dependency graph with the application under test. + testApplication.attachBaseContext(ApplicationProvider.getApplicationContext()) + block( + DaggerDeprecationControllerTest_TestApplicationComponent.builder() + .setApplication(testApplication) + .build() + ) + } + + private fun setUpOppiaApplicationForContext( + context: Context, + expirationEnabled: Boolean, + expDate: String + ) { + val packageManager = Shadows.shadowOf(context.packageManager) + val applicationInfo = + ApplicationInfoBuilder.newBuilder() + .setPackageName(context.packageName) + .setName("Oppia") + .build() + applicationInfo.metaData = Bundle() + applicationInfo.metaData.putBoolean("automatic_app_expiration_enabled", expirationEnabled) + applicationInfo.metaData.putString("expiration_date", expDate) + val packageInfo = + PackageInfoBuilder.newBuilder() + .setPackageName(context.packageName) + .setApplicationInfo(applicationInfo) + .build() + packageManager.installPackage(packageInfo) + } + + private fun setUpDefaultTestApplicationComponent() { + setUpTestApplicationComponent() + + // By default, set up the application to never expire. + setUpOppiaApplication(expirationEnabled = false, expDate = "9999-12-31") + } + + @Module + class TestModule { + companion object { + var buildFlavor = BuildFlavor.BUILD_FLAVOR_UNSPECIFIED + } + + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + + // TODO(#59): Either isolate these to their own shared test module, or use the real logging + // module in tests to avoid needing to specify these settings for tests. + @EnableConsoleLog + @Provides + fun provideEnableConsoleLog(): Boolean = true + + @EnableFileLog + @Provides + fun provideEnableFileLog(): Boolean = false + + @GlobalLogLevel + @Provides + fun provideGlobalLogLevel(): LogLevel = LogLevel.VERBOSE + + @Provides + fun provideTestingBuildFlavor(): BuildFlavor = buildFlavor + } + + @Singleton + @Component( + modules = [ + LogStorageModule::class, RobolectricModule::class, + TestModule::class, TestDispatcherModule::class, TestLogReportingModule::class, + NetworkConnectionUtilDebugModule::class, + OppiaClockModule::class, LocaleProdModule::class, + ExpirationMetaDataRetrieverModule::class, // Use real implementation to test closer to prod. + LoggingIdentifierModule::class, ApplicationLifecycleModule::class, + SyncStatusModule::class, PlatformParameterModule::class, + PlatformParameterSingletonModule::class + ] + ) + interface TestApplicationComponent : DataProvidersInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun getDeprecationController(): DeprecationController + + fun getCacheFactory(): PersistentCacheStore.Factory + + fun getTestCoroutineDispatchers(): TestCoroutineDispatchers + + fun getContext(): Context + + fun inject(deprecationControllerTest: DeprecationControllerTest) + } + + class TestApplication : Application(), DataProvidersInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerDeprecationControllerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(deprecationControllerTest: DeprecationControllerTest) { + component.inject(deprecationControllerTest) + } + + public override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + } + + override fun getDataProvidersInjector(): DataProvidersInjector = component + } +} diff --git a/model/src/main/proto/BUILD.bazel b/model/src/main/proto/BUILD.bazel index 3993aeee960..a168e981191 100644 --- a/model/src/main/proto/BUILD.bazel +++ b/model/src/main/proto/BUILD.bazel @@ -154,6 +154,18 @@ java_lite_proto_library( deps = [":onboarding_proto"], ) +oppia_proto_library( + name = "deprecation_proto", + srcs = ["deprecation.proto"], + deps = [":version_proto"], +) + +java_lite_proto_library( + name = "deprecation_java_proto_lite", + visibility = ["//:oppia_api_visibility"], + deps = [":deprecation_proto"], +) + oppia_proto_library( name = "spotlight_proto", srcs = ["spotlight.proto"], diff --git a/model/src/main/proto/deprecation.proto b/model/src/main/proto/deprecation.proto new file mode 100644 index 00000000000..251109f0c23 --- /dev/null +++ b/model/src/main/proto/deprecation.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package model; + +import "version.proto"; + +option java_package = "org.oppia.android.app.model"; +option java_multiple_files = true; + +// Top-level proto used to store deprecation responses, which correspond to a user's interaction +// with a dialog notifying them of either an optional update or of an OS deprecation. +message DeprecationResponseDatabase { + // Stores a user's response to a dialog notifying the user that an optional update is available + // for download. + DeprecationResponse app_deprecation_response = 1; + + // Stores a user's response to a dialog notifying the user that their OS is deprecated. + DeprecationResponse os_deprecation_response = 2; +} + +// Represents a response to a dialog notifying the user that an optional update is available for +// download, or that their OS is deprecated. +message DeprecationResponse { + // The app version of the latest available update at the time the user dismissed a deprecation + // notice dialog. + int32 deprecated_version = 1; + + // The timestamp in milliseconds since epoch corresponding to the date and time that the user + // dismissed the deprecation notice dialog. + uint64 notice_dismissed_timestamp_millis = 2; + + // The [DeprecationNoticeType] of the dialog that the user has dismissed. This can + // either be unspecified, an app deprecation or an OS deprecation notice. + DeprecationNoticeType deprecation_notice_type = 3; +} + +// An enum object that represents the different deprecation notices that can be shown to the user. +enum DeprecationNoticeType { + // Unspecified notice type. + DEPRECATION_NOTICE_TYPE_UNSPECIFIED = 0; + + // App update notice type. + APP_DEPRECATION = 1; + + // OS deprecation notice type. + OS_DEPRECATION = 2; +} diff --git a/model/src/main/proto/onboarding.proto b/model/src/main/proto/onboarding.proto index 3ed97a65062..4cefc9213d7 100644 --- a/model/src/main/proto/onboarding.proto +++ b/model/src/main/proto/onboarding.proto @@ -24,6 +24,15 @@ message AppStartupState { // continue using it. Instead, they should be shown a prompt suggesting that they update the app // via the Play Store. APP_IS_DEPRECATED = 3; + + // Indicates that a new app version is available and the user should be shown a prompt to update + // the app. Since the update is optional, the user can choose to update or not. + OPTIONAL_UPDATE_AVAILABLE = 4; + + // Indicates that a new app version is available but the user can not update the app because + // they are using an OS version that is no longer supported. The user should be shown a prompt + // to update their OS. + OS_IS_DEPRECATED = 5; } // Describes different notices that may be shown to the user on startup depending on whether diff --git a/scripts/assets/file_content_validation_checks.textproto b/scripts/assets/file_content_validation_checks.textproto index 40a355c58f2..677e7ebcb9e 100644 --- a/scripts/assets/file_content_validation_checks.textproto +++ b/scripts/assets/file_content_validation_checks.textproto @@ -331,6 +331,7 @@ file_content_checks { exempted_file_name: "utility/src/test/java/org/oppia/android/util/math/PolynomialExtensionsTest.kt" exempted_file_name: "utility/src/test/java/org/oppia/android/util/math/RealExtensionsTest.kt" exempted_file_name: "utility/src/test/java/org/oppia/android/util/profile/ProfileNameValidatorTest.kt" + exempted_file_name: "domain/src/test/java/org/oppia/android/domain/onboarding/DeprecationControllerTest.kt" exempted_file_patterns: "testing/src/main/java/org/oppia/android/testing/junit/.+?\\.kt" } file_content_checks {