diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt index 9cb185bcac4..5a2970e9652 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt @@ -14,9 +14,11 @@ import org.oppia.android.domain.oppialogger.OppiaLogger import org.oppia.android.domain.oppialogger.analytics.AnalyticsController import org.oppia.android.util.data.AsyncResult import org.oppia.android.util.data.DataProviders.Companion.toLiveData +import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.logging.SyncStatusManager import org.oppia.android.util.logging.SyncStatusManager.SyncStatus import java.io.ByteArrayOutputStream +import java.security.MessageDigest import java.util.Base64 import java.util.zip.GZIPOutputStream import javax.inject.Inject @@ -31,6 +33,7 @@ class ControlButtonsViewModel private constructor( private val activity: AppCompatActivity, private val analyticsController: AnalyticsController, private val syncStatusManager: SyncStatusManager, + private val machineLocale: OppiaLocale.MachineLocale, private val viewModels: List ) : ProfileListItemViewModel(ProfileListViewModel.ProfileListItemViewType.SHARE_IDS) { private var monitoredUploadProgress: LiveData = @@ -73,10 +76,17 @@ class ControlButtonsViewModel private constructor( ) } is SyncStatusItemViewModel -> { + val halfLineCount = BASE64_LINE_WRAP_LIMIT / 2 + val logsStr = logs?.toCompressedBase64() listOf( "Current sync status: ${viewModel.syncStatus.value}.", + "Event log encoding integrity checks:", + "- First $halfLineCount chars of encoded string: ${logsStr?.take(halfLineCount)}", + "- Last $halfLineCount chars of encoded string: ${logsStr?.takeLast(halfLineCount)}", + "- SHA-1 hash (unwrapped event string): ${logsStr?.computeSha1Hash(machineLocale)}", + "- Total event string length (unwrapped): ${logsStr?.length}", "Encoded event logs:" - ) + (logs?.toCompressedBase64()?.chunked(BASE64_LINE_WRAP_LIMIT) ?: listOf("Missing")) + ) + (logsStr?.chunked(BASE64_LINE_WRAP_LIMIT) ?: listOf("Missing")) } else -> null } @@ -198,12 +208,13 @@ class ControlButtonsViewModel private constructor( private val oppiaLogger: OppiaLogger, private val activity: AppCompatActivity, private val analyticsController: AnalyticsController, - private val syncStatusManager: SyncStatusManager + private val syncStatusManager: SyncStatusManager, + private val machineLocale: OppiaLocale.MachineLocale ) { /** Returns a new [ControlButtonsViewModel]. */ fun create(viewModels: List): ControlButtonsViewModel { return ControlButtonsViewModel( - oppiaLogger, activity, analyticsController, syncStatusManager, viewModels + oppiaLogger, activity, analyticsController, syncStatusManager, machineLocale, viewModels ) } } @@ -219,5 +230,13 @@ class ControlButtonsViewModel private constructor( }.toByteArray() return Base64.getEncoder().encodeToString(compressedMessage) } + + private fun String.computeSha1Hash(machineLocale: OppiaLocale.MachineLocale): String { + return machineLocale.run { + MessageDigest.getInstance("SHA-1") + .digest(this@computeSha1Hash.toByteArray()) + .joinToString("") { "%02x".formatForMachines(it) } + } + } } } diff --git a/app/src/main/java/org/oppia/android/app/application/alpha/AlphaApplicationComponent.kt b/app/src/main/java/org/oppia/android/app/application/alpha/AlphaApplicationComponent.kt index a2ccde2b957..195f28be76d 100644 --- a/app/src/main/java/org/oppia/android/app/application/alpha/AlphaApplicationComponent.kt +++ b/app/src/main/java/org/oppia/android/app/application/alpha/AlphaApplicationComponent.kt @@ -37,7 +37,7 @@ import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterM import org.oppia.android.domain.oppialogger.exceptions.UncaughtExceptionLoggerModule 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.PlatformParameterAlphaModule import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorkerModule import org.oppia.android.domain.question.QuestionModule @@ -85,7 +85,7 @@ import javax.inject.Singleton ApplicationStartupListenerModule::class, LogReportWorkerModule::class, WorkManagerConfigurationModule::class, HintsAndSolutionConfigModule::class, FirebaseLogUploaderModule::class, NetworkModule::class, - PlatformParameterModule::class, PlatformParameterSingletonModule::class, + PlatformParameterAlphaModule::class, PlatformParameterSingletonModule::class, ExplorationStorageModule::class, DeveloperOptionsModule::class, PlatformParameterSyncUpWorkerModule::class, NetworkConfigProdModule::class, AssetModule::class, LocaleProdModule::class, ActivityRecreatorProdModule::class, ActivityRouterModule::class, diff --git a/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt index 2f45b29a533..44fb2ec9945 100644 --- a/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt @@ -427,7 +427,9 @@ class NavigationDrawerFragmentPresenter @Inject constructor( override fun onDrawerClosed(drawerView: View) { super.onDrawerClosed(drawerView) - fragment.activity!!.invalidateOptionsMenu() + // It's possible in some rare cases for the activity to be gone while the drawer is + // closing (possibly an out-of-lifecycle call from the AndroidX component). + fragment.activity?.invalidateOptionsMenu() StatusBarColor.statusBarColorUpdate( R.color.component_color_shared_activity_status_bar_color, activity, diff --git a/app/src/main/java/org/oppia/android/app/home/recentlyplayed/PromotedStoryViewModel.kt b/app/src/main/java/org/oppia/android/app/home/recentlyplayed/PromotedStoryViewModel.kt index 3c62c4cde21..f93fba2613c 100755 --- a/app/src/main/java/org/oppia/android/app/home/recentlyplayed/PromotedStoryViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/home/recentlyplayed/PromotedStoryViewModel.kt @@ -41,7 +41,8 @@ class PromotedStoryViewModel( /** * Starts [ResumeLessonActivity] if a saved exploration is selected or [ExplorationActivity] if an - * un-started recommended story is selected. */ + * un-started recommended story is selected. + */ fun clickOnPromotedStoryTile(@Suppress("UNUSED_PARAMETER") v: View) { promotedStoryClickListener.promotedStoryClicked(promotedStory) } diff --git a/app/src/main/java/org/oppia/android/app/options/OptionControlsViewModel.kt b/app/src/main/java/org/oppia/android/app/options/OptionControlsViewModel.kt index 75582bd1de7..50f489ff326 100644 --- a/app/src/main/java/org/oppia/android/app/options/OptionControlsViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/options/OptionControlsViewModel.kt @@ -29,7 +29,7 @@ private const val OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID = /** Options settings view model for the recycler view in [OptionsFragment]. */ @FragmentScope class OptionControlsViewModel @Inject constructor( - val activity: AppCompatActivity, + activity: AppCompatActivity, private val profileManagementController: ProfileManagementController, private val oppiaLogger: OppiaLogger, @EnableLanguageSelectionUi private val enableLanguageSelectionUi: PlatformParameterValue, diff --git a/app/src/main/java/org/oppia/android/app/player/audio/AudioFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/audio/AudioFragmentPresenter.kt index 76c3ab8f2a8..02bcc3deb32 100644 --- a/app/src/main/java/org/oppia/android/app/player/audio/AudioFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/audio/AudioFragmentPresenter.kt @@ -175,6 +175,8 @@ class AudioFragmentPresenter @Inject constructor( AudioLanguage.FRENCH_AUDIO_LANGUAGE -> "fr" AudioLanguage.CHINESE_AUDIO_LANGUAGE -> "zh" AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE -> "pt" + AudioLanguage.ARABIC_LANGUAGE -> "ar" + AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE -> "pcm" AudioLanguage.NO_AUDIO, AudioLanguage.UNRECOGNIZED, AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED, AudioLanguage.ENGLISH_AUDIO_LANGUAGE -> "en" } diff --git a/app/src/main/java/org/oppia/android/app/player/audio/LanguageDialogFragment.kt b/app/src/main/java/org/oppia/android/app/player/audio/LanguageDialogFragment.kt index 311de76cbe5..275705a0ed9 100644 --- a/app/src/main/java/org/oppia/android/app/player/audio/LanguageDialogFragment.kt +++ b/app/src/main/java/org/oppia/android/app/player/audio/LanguageDialogFragment.kt @@ -8,7 +8,10 @@ import androidx.appcompat.view.ContextThemeWrapper import org.oppia.android.R import org.oppia.android.app.fragment.FragmentComponentImpl import org.oppia.android.app.fragment.InjectableDialogFragment -import java.util.Locale +import org.oppia.android.app.model.AudioLanguage +import org.oppia.android.app.translation.AppLanguageResourceHandler +import org.oppia.android.util.locale.OppiaLocale +import javax.inject.Inject import kotlin.collections.ArrayList private const val LANGUAGE_LIST_ARGUMENT_KEY = "LanguageDialogFragment.language_list" @@ -18,6 +21,9 @@ private const val SELECTED_INDEX_ARGUMENT_KEY = "LanguageDialogFragment.selected * DialogFragment that controls language selection in audio and written translations. */ class LanguageDialogFragment : InjectableDialogFragment() { + @Inject lateinit var appLanguageResourceHandler: AppLanguageResourceHandler + @Inject lateinit var machineLocale: OppiaLocale.MachineLocale + companion object { /** * This function is responsible for displaying content in DialogFragment. @@ -55,13 +61,21 @@ class LanguageDialogFragment : InjectableDialogFragment() { val languageNameArrayList = ArrayList() for (languageCode in languageCodeArrayList) { + val audioLanguage = when (machineLocale.run { languageCode.toMachineLowerCase() }) { + "hi" -> AudioLanguage.HINDI_AUDIO_LANGUAGE + "fr" -> AudioLanguage.FRENCH_AUDIO_LANGUAGE + "zh" -> AudioLanguage.CHINESE_AUDIO_LANGUAGE + "pt", "pt-br" -> AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE + "ar" -> AudioLanguage.ARABIC_LANGUAGE + "pcm" -> AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE + else -> AudioLanguage.ENGLISH_AUDIO_LANGUAGE + } if (languageCode == "hi-en") { languageNameArrayList.add("Hinglish") } else { - // TODO(#3791): Remove this dependency. - val locale = Locale(languageCode) - val name = locale.getDisplayLanguage(locale) - languageNameArrayList.add(name) + languageNameArrayList.add( + appLanguageResourceHandler.computeLocalizedDisplayName(audioLanguage) + ) } } diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/StateViewModel.kt index b180c0e61d7..54109859994 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/StateViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateViewModel.kt @@ -26,7 +26,7 @@ import org.oppia.android.domain.translation.TranslationController import org.oppia.android.util.data.AsyncResult import org.oppia.android.util.data.DataProviders.Companion.toLiveData import org.oppia.android.util.locale.OppiaLocale -import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableFastLanguageSwitchingInLesson import org.oppia.android.util.platformparameter.PlatformParameterValue import javax.inject.Inject @@ -39,7 +39,8 @@ class StateViewModel @Inject constructor( private val oppiaLogger: OppiaLogger, private val fragment: Fragment, private val profileManagementController: ProfileManagementController, - @EnableLearnerStudyAnalytics private val enableLearnerStudy: PlatformParameterValue + @EnableFastLanguageSwitchingInLesson + private val enableFastLanguageSwitchingInLesson: PlatformParameterValue ) : ObservableViewModel() { val itemList: ObservableList = ObservableArrayList() val rightItemList: ObservableList = ObservableArrayList() @@ -52,7 +53,7 @@ class StateViewModel @Inject constructor( val isHintBulbVisible = ObservableField(false) val isHintOpenedAndUnRevealed = ObservableField(false) - val hasSupportForSwitchingToSwahili: Boolean = enableLearnerStudy.value + val hasSupportForSwitchingToSwahili: Boolean = enableFastLanguageSwitchingInLesson.value val hasSwahiliTranslations: LiveData by lazy { Transformations.map( explorationProgressController.getCurrentState().toLiveData(), diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditViewModel.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditViewModel.kt index 8aa02c1d428..90f3d710703 100644 --- a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditViewModel.kt @@ -11,6 +11,7 @@ import org.oppia.android.domain.profile.ProfileManagementController import org.oppia.android.util.data.AsyncResult import org.oppia.android.util.data.DataProviders.Companion.toLiveData import org.oppia.android.util.platformparameter.EnableDownloadsSupport +import org.oppia.android.util.platformparameter.EnableFastLanguageSwitchingInLesson import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics import org.oppia.android.util.platformparameter.PlatformParameterValue import javax.inject.Inject @@ -21,7 +22,9 @@ class ProfileEditViewModel @Inject constructor( private val oppiaLogger: OppiaLogger, private val profileManagementController: ProfileManagementController, @EnableDownloadsSupport private val enableDownloadsSupport: PlatformParameterValue, - @EnableLearnerStudyAnalytics private val enableLearnerStudy: PlatformParameterValue + @EnableLearnerStudyAnalytics private val enableLearnerStudy: PlatformParameterValue, + @EnableFastLanguageSwitchingInLesson + private val enableFastLanguageSwitchingInLesson: PlatformParameterValue ) : ObservableViewModel() { private lateinit var profileId: ProfileId @@ -29,7 +32,8 @@ class ProfileEditViewModel @Inject constructor( val isAllowedToMarkFinishedChapters: Boolean = enableLearnerStudy.value /** Whether the admin can allow learners to quickly switch content languages within a lesson. */ - val isAllowedToEnableQuickLessonLanguageSwitching: Boolean = enableLearnerStudy.value + val isAllowedToEnableQuickLessonLanguageSwitching: Boolean = + enableFastLanguageSwitchingInLesson.value /** List of all the current profiles registered in the app [ProfileListFragment]. */ val profile: LiveData by lazy { diff --git a/app/src/main/java/org/oppia/android/app/spotlight/SpotlightFragment.kt b/app/src/main/java/org/oppia/android/app/spotlight/SpotlightFragment.kt index 5e6be96804e..0c2203d4ae2 100644 --- a/app/src/main/java/org/oppia/android/app/spotlight/SpotlightFragment.kt +++ b/app/src/main/java/org/oppia/android/app/spotlight/SpotlightFragment.kt @@ -154,7 +154,7 @@ class SpotlightFragment : InjectableFragment(), SpotlightNavigationListener, Spo if (targetList.isNullOrEmpty()) return spotlight = Spotlight.Builder(activity) .setTargets(targetList) - .setBackgroundColorRes(R.color.component_color_shared_close_spotlight_button_color) + .setBackgroundColorRes(R.color.component_color_shared_spotlight_overlay_background_color) .setDuration(500L) .setAnimation(AccelerateInterpolator(0.5f)) .setOnSpotlightListener(object : OnSpotlightListener { diff --git a/app/src/main/java/org/oppia/android/app/translation/AppLanguageResourceHandler.kt b/app/src/main/java/org/oppia/android/app/translation/AppLanguageResourceHandler.kt index 14ac4c1346a..fed7674f823 100644 --- a/app/src/main/java/org/oppia/android/app/translation/AppLanguageResourceHandler.kt +++ b/app/src/main/java/org/oppia/android/app/translation/AppLanguageResourceHandler.kt @@ -157,6 +157,9 @@ class AppLanguageResourceHandler @Inject constructor( AudioLanguage.FRENCH_AUDIO_LANGUAGE -> getLocalizedDisplayName("fr") AudioLanguage.CHINESE_AUDIO_LANGUAGE -> getLocalizedDisplayName("zh") AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE -> getLocalizedDisplayName("pt", "BR") + AudioLanguage.ARABIC_LANGUAGE -> getLocalizedDisplayName("ar", "EG") + AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE -> + resources.getString(R.string.nigerian_pidgin_localized_language_name) AudioLanguage.NO_AUDIO, AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED, AudioLanguage.UNRECOGNIZED, AudioLanguage.ENGLISH_AUDIO_LANGUAGE -> getLocalizedDisplayName("en") } @@ -180,6 +183,8 @@ class AppLanguageResourceHandler @Inject constructor( OppiaLanguage.ENGLISH -> resources.getString(R.string.english_localized_language_name) OppiaLanguage.ARABIC -> resources.getString(R.string.arabic_localized_language_name) OppiaLanguage.HINGLISH -> resources.getString(R.string.hinglish_localized_language_name) + OppiaLanguage.NIGERIAN_PIDGIN -> + resources.getString(R.string.nigerian_pidgin_localized_language_name) } } diff --git a/app/src/main/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtil.kt b/app/src/main/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtil.kt index 301d7ee17e7..2635a98b662 100644 --- a/app/src/main/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtil.kt +++ b/app/src/main/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtil.kt @@ -28,6 +28,7 @@ import org.oppia.android.app.model.OppiaLanguage.ENGLISH import org.oppia.android.app.model.OppiaLanguage.HINDI import org.oppia.android.app.model.OppiaLanguage.HINGLISH import org.oppia.android.app.model.OppiaLanguage.LANGUAGE_UNSPECIFIED +import org.oppia.android.app.model.OppiaLanguage.NIGERIAN_PIDGIN import org.oppia.android.app.model.OppiaLanguage.PORTUGUESE import org.oppia.android.app.model.OppiaLanguage.SWAHILI import org.oppia.android.app.model.OppiaLanguage.UNRECOGNIZED @@ -72,8 +73,8 @@ class MathExpressionAccessibilityUtil @Inject constructor( ): String? { return when (language) { ENGLISH -> expression.toHumanReadableEnglishString(divAsFraction) - ARABIC, HINDI, HINGLISH, PORTUGUESE, BRAZILIAN_PORTUGUESE, SWAHILI, LANGUAGE_UNSPECIFIED, - UNRECOGNIZED -> null + ARABIC, HINDI, HINGLISH, PORTUGUESE, BRAZILIAN_PORTUGUESE, SWAHILI, NIGERIAN_PIDGIN, + LANGUAGE_UNSPECIFIED, UNRECOGNIZED -> null } } @@ -91,8 +92,8 @@ class MathExpressionAccessibilityUtil @Inject constructor( ): String? { return when (language) { ENGLISH -> equation.toHumanReadableEnglishString(divAsFraction) - ARABIC, HINDI, HINGLISH, PORTUGUESE, BRAZILIAN_PORTUGUESE, SWAHILI, LANGUAGE_UNSPECIFIED, - UNRECOGNIZED -> null + ARABIC, HINDI, HINGLISH, PORTUGUESE, BRAZILIAN_PORTUGUESE, SWAHILI, NIGERIAN_PIDGIN, + LANGUAGE_UNSPECIFIED, UNRECOGNIZED -> null } } diff --git a/app/src/main/res/values-pcm-rNG/strings.xml b/app/src/main/res/values-pcm-rNG/strings.xml new file mode 100644 index 00000000000..41dea5c3849 --- /dev/null +++ b/app/src/main/res/values-pcm-rNG/strings.xml @@ -0,0 +1,511 @@ + + + + Navigation header + Options + My Downloads + Help + Lesson Player + Help + Close + Change Profile + Developer Options + Administrator Controls + Navigation Menu Open + Navigation Menu Close + Play di audio + Pause di audio + %s audio no dey available. + OK + Cancel am + Audio Language + You dey offline + Make sure sey Wi-Fi or mobile data dey on, den try am again. + OK + OK + Cancel am + Na your data you dey use now + Playing di audio go use plenti mobile data. + No show this message again + Concept Card + Revision Card + Comot go the topic page? + Wetin you don do before no go save + Comot + Kansu-am + Comot go the topic page? + Your progress no go save. + Kansu-am + Comot + Use dis button take comot anytime. We go save your progress. + You go like make %s read for you? Click on dis button to try am. + Click here to change voiceover language. + Ei don reach di highest storage capacity + Di saved progress for di lesson %s go comot. + Continue + Comot without saving progress + Back to di lesson + Cram dese skills + Ratios and Proportional Reasoning + Choose skills wey you go like to practice. + Start + Things about di topic. + Revise di concepts you learn from di lessons wey you don complete here. + Practice di concepts wey you learn from lessons wey you don complete. + Click here to start to play di lesson. + Find all your lessons for here. + Revise concepts wey you learn from lessons wey you don complete here. + Click here to start to play a lesson. + Continue + Submit + Go di former card + Go di next card + Submit + Replay + Return To Di Topic + Former reply (%s) + Clicks on %s + Learn Am Again + See More + See Less + FAQs + Featured Questions + Frequently Asked Questions + FAQs (Frequently Asked Questions) + PIN verification + Introduction + Frequently Asked Questions (FAQs) + Info + Lessons + Practice + Revision + Administrator Controls + Topic page + Topic: %s + Topic + Topics wey dey in Progress + Chapter %s: %s + Chapter %s with title %s don complete + Chapter %s with title %s dey in progress + Complete Chapter %s: %s to unlock dis chapter. + Finish the chapter wey dey before to fit open dis chapter + Enter text. + Enter fraction wey dey in di form x/x, or mixed nomba wey dey in di form x x/x. + Enter fraction wey dey in di form x/x. + Enter a nomba. + Write nombas with units for here. + Use only nombas write an expression for here. + Write an expression for here. + Write an equation for here. + Abeg remove di spaces between di nombas for your ansa. + Abeg close or remove di bracket. + Abeg remove di bracket wey dey around di whole ansa: \'%s\'. + Abeg remove di extra bracket wey dey around di \'(%1$s)\', for example: \'%1$s\'. + Abeg remove di extra bracket wey dey around \'(%1$s)\', for example: \'%1$s\'. + Invalid \'%s\' dey for inside di ansa. Abeg remove am. + Abeg arrange di order of %1$s & %2$s. For example: %2$s%1$s. + %1$s and %2$s suppose dey separated by a nomba or a variable. + Abeg remove di extra symbols for your ansa. + Ei get a nomba or a variable wey dey miss before or afta di addition sign \'%1$s\'? If not, abeg remove di extra \'%1$s\'. + Ei get a nomba or a variable wey dey miss before or afta di multiplication sign \'%1$s\'? If not, abeg remove di extra \'%1$s\'. + Ei get a nomba or a variable wey dey miss before or afta di division sign \'%1$s\'? If not, abeg remove di extra \'%1$s\'. + Ei get a nomba or a variable wey dey miss before or afta di exponentiation sign \'%1$s\'? If not, abeg remove di extra \'%1$s\'. + Ei be like sey a nomba or a variable dey miss afta di addition sign \'%s\'. + Ei be like sey a nomba or a variable dey miss afta di subtraction sign \'%s\'. + Ei be like sey a nomba or a variable dey miss afta di multiplication sign \'%s\'. + Ei be like sey a nomba or a variable dey miss afta di division sign \'%s\'. + Ei be like sey a nomba or a variable dey miss afta di exponentiation sign \'%s\'. + Sorry, di app no dey support variables in exponents. Abeg revise your ansa. + Sorry, di app no dey support powers wey dey higher dan 5. Abeg revise your ansa. + Sorry, di app no dey support repeated powers/exponents. Abeg reduce your ansa to one power. + Input dey miss for square root. + Dividing by zero dey invalid. Abeg revise your ansa. + Ei be like sey you done enter some variables. Abeg make sure sey na only nombas dey your ansa and remove any variables from your ansa. + Abeg use di variables wey dey di question and not %s. + Your equation dey miss an \'=\' sign. + Your equation get too many \'=\' signs. Ei suppose get only one. + One of di sides of \'=\' for your equation dey empty. + Function \'%s\' no dey supported. Abeg revise your ansa. + Na sqrt you mean? If not, abeg separate di variables with multiplication signs. + Sorry, we no fit understand your ansa. Abeg check am to make sure any mistake no dey. + Enable audio voiceover for dis lesson. + Stories wey you just play + Stori wey you play last + View All + You play am last week + You play am last month + Chapter List + Picture for %s + Stories Wey You Fit Play + Go up + Stories wey you just play + Topic Wey You Don Download + You don download am + Practice Mode + Question %s of %s + Complete + Finished + You don finish all of di questions! You fit choose to play anoda set of questions, or go back to di topic. + In Progress + Completed + Show di chapter list + Hide di chapter list + Play/Pause Audio + How you like am + Profile Progress Page + Find-am + Abeg only use numerical digits, spaces or forward slashes (/) + Abeg enter a valid fraction (e.g., 5/3 or 1 2/3) + Abeg no put 0 for di denominator + None of di nombas for di fraction go get more dan 7 digits. + Abeg start your ansa with nomba (e.g.,”0” for 0.5). + Abeg put correct nomba. + Di ansa fit get at most 15 digits (0–9) or sign (. or -). + Abeg write a ratio wey get nomba separated by colons (e.g. 1:2 or 1:2:3). + Abeg enter a valid ratio (e.g. 1:2 or 1:2:3). + Your ansa get two colons (:) next to each other. + Nomba of terms no dey equal to di required terms. + Ratios no suppose get 0 as an element. + Size wey dey no know + %s Bytes + %s KB + %s MB + %s GB + You get am! + Topic: %s + + 1 Chapter + %s Chapters + + + 1 Story + %s Stories + + + %s of %s Chapter Completed + %s of %s Chapters Completed + + + 1 Lesson + %s Lessons + + + 1 Story Completed + %s Stories Completed + %s Stories Completed + + + 1 Topic in Progress + %s Topics in Progress + %s Topics in Progress + + Page to select profile + Administrator + Select your profile + Add Profile + Set up Plenty Profiles + Add up to 10 users to your account. Perfect for families and classrooms. + Administrator Controls + Language + Administrator Controls + Authorise to add profiles + Authorise to access Administrator Controls + Administrator Authorization Required + Enter di Administrator PIN in order to create a new account. + Enter di Administrator PIN in order to access Administrator Controls. + Administrator\'s PIN + Administrator PIN no correct. Abeg try again. + Abeg enter Administrator PIN. + Submit + Close + Before we add profiles, we need to protect your account by creating a PIN. Dis wan go give you di ability to authorize manage profiles for di device. + You fit use PIN wey you don set for personal accounts like banking or social security. + New 5-Digit PIN + Confirm 5-Digit PIN + Your PIN suppose be 5 digits long. + Abeg make sure sey di two PINs match. + Save + Authorise to add profiles + Add Profile + Add Profile + Name + 3-Digit PIN + Confirm 3-Digit PIN + Allow Download Access + User go dey able to download and delete content without Administrator PIN. + Create + Close + With a PIN, nobody else go fit access a profile besides dis assigned user. + We fail to save your avatar image. Abeg try again. + Anoda profile don already dey use dis name. + Abeg put valid name for dis profile. + Abeg choose profile name wey no get nombas or symbols. + Your PIN suppose dey 3 digits long. + Abeg make sure sey di two PINs match. + More information on 3-digit PINs. + Current profile picture + Edit profile picture + Welcome to %s! + Learn anything wey you want in an effective and enjoyable way. + Add users to your account. + Share di experience and create up to 10 profiles. + Download for offline. + Continue to learn your lessons without internet connection. + Have fun! + Enjoy your learning adventures with our free, effective lessons. + Skip + Next + Get Started + Slide %s of %s + Hi, %s! + Abeg put your Administrator PIN. + Abeg put your PIN. + Administrator’s 5-Digit PIN. + User’s 3-Digit PIN. + You don forget your PIN? + Di PIN no correct. + show + hide am + Close + PIN change is successful + You don forget your PIN? + To reset your PIN, you go need to clear all di data wey don save for %s.\n\nRememba sey dis action go make all profiles and user progress delete, and you no fit undo am. Also, di app go close wen ei don complete and you go need need to reopen am. + Reset %s Data + Confirm %s Data Reset + You dey sure sey you wan delete all %s profiles for dis device? You no fit undo dis operation. + Yes + No + Show/Hide password icon + Password shown icon + Password hidden icon + Put your PIN + Put PIN + Administrator\'s PIN + Access to Administrator Settings + You need administrator\'s PIN to change user\'s PIN + Cancel + Submit + Administrator PIN no correct. Abeg try again. + %1$s\'s New PIN. + Put a New Pin + Dis PIN go be %s\'s new PIN and ei go need am wen signing in. + My Downloads + Downloads + Updates (2) + You go like to exit your profile? + Cancel + Exit + Home + From now, you go see lessons recommended for you here. + Select a Topic to Start + Profiles + Edit Profile + Created on %s + Last used + Rename + Reset PIN + Mark Chapters Completed + Enable Quick Language Switching + Allow dis user to quickly switch between English and Swahili within a lesson. + Profile Deletion + Permanently delete dis profile? + All progress go delete and you no fit get am back. + Delete + Cancel + Allow Download Access + Di user no fit download and delete content without Administrator password + Profile Picture + Profile Picture + Cancel + View Profile Picture + Choose From Library + Rename Profile + New Name + save + Reset PIN + Put new PIN for di user to put wan dey wan enter deir profile. + 3-Digit PIN + 5-Digit PIN + Confirm 3-Digit PIN + Confirm 5-Digit PIN + Your PIN suppose dey 3 digits long. + Your PIN suppose dey 5 digits long. + Create a 3-Digit PIN + Required + Back Button + Next + General + Edit account + Profile Management + Edit profiles + Download Permissions + Download and update only on Wi-fi + Topics go dey downloaded and updated only on Wi-fi. Any downloads or updates of cellular data go dey queue. + Automatically update topics + Downloaded topics wey get new content available go automatically update. + App Information + App Version + Account Actions + Log Out + Cancel + Ok + You dey sure sey you wan log out of your profile? + App Version %s + Di last update install on %s. Use di version nomba for up to send feedback about bugs. + App Version + App Language + Default Audio Language + Reading Text Size + Reading Text Size + Story text go look like dis. + A + Default Audio + App Language + Reading Text Size + Small + Medium + Large + Extra Large + Slide seekbar to control di text size. + Profile + 2 Stories + Topics in Progress + Topic in Progress + Stories Completed + Story Completed + Options + Stories Completed + App walkthrough + Learn new math skills with stories wey go show you how to use dem for your daily life + \"Welcome %s!\" + Wetin you wan learn? + Great + Make we start. + Yes + No… + Pick a \ndifferent topic. + You dey interested in:\n%s? + New hint dey + No new hint dey + Show hints and solution + Hint %s + Go up + Hints + Show solution + Show Solution + Solution + Show Hint + Hide Hint + Hide solution + Di only solution na: %s + One solution na: %s + Dis go show di solution. You dey sure? + Show + just now + recently + %s ago + yesterday + Go back to topic + Go back to lesson + Explanation: + If two items dey equal, join dem. + Link to item %s + Unlink items at %s + Move item down to %s + Move item up to %s + Up + Down + %s %s + + a minute + %s minutes + + + an hour + %s hours + + + a day + %s days + + topic_revision_recyclerview_tag + ongoing_recycler_view_tag + Abeg select all di correct choices. + Unsupported app version + Dis version of di app no longer dey supported. Abeg update am from di Play Store. + Close app + Developer Build + Alpha + Beta + Beta Notice + Hello! Your app don dey update to di Beta version. If you experience any problems while you dey use di app, or get any questions, abeg contact us at android-feedback@oppia.org. + No show dis message again + OK + General Availability Notice + Hello! Your app don dey update to di General Availability version. If you experience any problems while you dey use di app, or get any questions, abeg contact us at android-feedback@oppia.org. + No show dis message again + OK + to + Enter a ratio in di form x:y. + Tap here to put text. + Smallest text size + Largest text size + Coming Soon + Recommended Stories + Stories For You + Practice Mode + Skill revision page + Audio progress + Change language + Audio, ON + Audio, OFF + Correct submitted ansa + Correct submitted ansa: %s + Incorrect submitted ansa + Incorrect submitted ansa: %s + Third-party Dependencies + version %s + Copyright Licenses + Copyright License Viewer + Go back to %s + third-party dependencies list + copyright licenses list + Resume Lesson + Continue + Start Over + Good morning, + Good afternoon, + Good evening, + Policy Page + Privacy Policy + Please visit <a href=\"https://www.oppia.org/privacy-policy\">dis page</a> for di latest version of dis privacy policy. + Terms of Service + By using %s, you dey agree to our <br> <oppia-noninteractive-policy link=\"tos\">Terms of Service</oppia-noninteractive-policy> and <oppia-noninteractive-policy link=\"privacy\">Privacy Policy</oppia-noninteractive-policy>. + Abeg visit <a href=\"https://www.oppia.org/terms\">dis page</a> for di latest version of dese terms. + How I go fit create a new profile? + How I go delete a profile? + How I go take change my email/phone nomba? + Wetin be %s? + Who be Administrator? + Why di Exploration player no dey load? + Why my audio no dey play? + I no dey find my question here. What now? + <p>If na your first time creating a profile and not have a PIN:<ol><li>From di Profile Chooser, tap on <strong>Set up Multiple Profiles</strong>.</li><li>Create a PIN and <strong>Save</strong>.</li><li>Fill in all boxes for di profile.<ol><li>(Optional) Upload a photo.</li><li>Enter a name.</li><li>(Optional) Assign a 3-digit PIN.</li></ol></li><li>Tap <strong>Create</strong>. Dis profile go add to your Profile Chooser!</li></ol></p><p> If you don create a profile before and you get a PIN:<ol><li>From di Profile Chooser, tap on <strong>Add Profile</strong>. </li><li>Enter your PIN and tap <strong>Submit</strong>. </li><li>Fill in all boxes for di profile.<ol><li> (Optional) Upload a photo. </li><li> Enter a name. </li><li> (Optional) Assign a 3-digit PIN. </li></ol></li><li>Tap <strong>Create</strong>. Dis profile go add to your Profile Chooser!</li></ol></p><br><p>Note: Only di <u>Administrator</u> go dey able to manage profiles.</p> + <p>Once profile don delete:</p><ol><li>Di profile no fit dey recovered. </li><li> Profile information such as name, photos, and progress go permanently delete. </li></ol><p>To delete a profile (excluding the <u>Administrator\'s</u>):</p><ol><li> From di Administrator\'s Home Page, tap on di menu button on di top left. </li><li>Tap on <strong>Administrator Controls</strong>. </li><li>Tap on <strong>Edit Profiles</strong>. </li><li>Tap on di Profile wey you wan delete. </li><li>For di bottom of di screen, tap <strong>Profile Deletion</strong>. </li><li>Tap <strong>Delete</strong> to confirm deletion. </li></ol><p><br></p><p>Note: Only di <u>Administrator</u> go dey able to manage profiles.</p> + <p>To change your email/phone nomba:</p><ol><li>From di Administrator\'s Home Page, tap on di menu button for di top left.</li><li>Tap on <strong>Administrator Controls</strong>.</li><li>Tap on <strong>Edit Account</strong>.</li></ol><p><br></p> <p>If you wan change your email:</p><ol><li>Enter your new email and tap <strong>Save</strong>.</li><li>A confirmation link go send to confirm your new email. Di link go expire after 24 hours and you must click on am to be associated with your account.</li></ol><p><br></p> <p>If you dey change your phone nomba:</p><ol><li> Enter your new phone nomba and tap <strong>Verify</strong>.</li><li> A code go send to confirm your new nomba. Di code go expire after 5 minutes and you must be enter am in for di new screen to be associated with your account.</li></ol> + <p>%1$s <i>\"O-pee-yah\"</i> (Finnish) - \"to learn\"</p><p><br></p><p>%1$s\'s mission na to help anyone learn anything dey want in an effective and enjoyable way.</p><p><br></p><p>By creating a set of free, high-quality, demonstrably effective lessons with di help of educators from around di world, %1$s dey aim to provide students with quality education — regardless of where dem dey or di traditional resources wey dem get access to.</p><p><br></p><p>As a student, you fit start your learning adventure by browsing di topics listed on di Home Page!</p> + <p>An Administrator na di main user wey dey manage profiles and settings for every profile on top their account. They fit be your parent, teacher, or guardian wey don create dis profile for you. </p><p><br></p><p>Administrators get di ability to manage profiles, assign PINs, and change other settings under their account. Depending on your profile, Administrator permissions fit dey required for some features such as changing your PIN, and more. </p><p><br></p><p>To see who your Administrator be, go di Profile Chooser. Di first profile fot di list and get \"Administrator\" written under their name na di Administrator. </p> + <p>If di Exploration Player no dey load</p><p><br></p><p>Check to see if di app dey up to date:</p><p><ul><li> Go to di Play Store and make sure sey di app dey updated to di latest version </li></ul><p><br></p><p>Check your internet connection:</p><ul><li> If your internet connection dey slow, try re-connecting to your Wi-Fi network or connecting to a different network. </li></ul><p>Ask di Administrator to check their device and internet connection:</p><ul><li> Get di Administrator to troubleshoot using di steps above </li></ul><p>Let us know if you still dey get issues with loading:</p><ul><li> Report a problem by contacting us at admin@oppia.org. </li></ul> + <p>If your audio no dey play</p><p><br></p><p>Check to see if di app dey up to date:</p><ul><li> Go to di Play Store and make sure sey di app dey updated to di latest version </li></ul><p><br></p><p>Check your internet connection:</p><ul><li> If your internet connection dey slow, try re-connecting to your Wi-Fi network or connecting to a different network. Slow internet fit cause di audio to load irregularly, and go make am difficult to play. </li></ul><p><br></p><p>Ask di Administrator to check their device and internet connection:</p><ul><li> Get di Administrator to troubleshoot using di steps for up</li></ul><p><br></p><p>Let us know if you still dey get issues with loading:</p><ul><li> Report a problem by contacting us at admin@oppia.org. </li></ul> + <p>If you no fit find your question or you go like to report a bug, contact us for admin@oppia.org.</p> + Profile Edit Fragment Test Activity + Administrator Controls Fragment Test Activity + Continue Studying + Go di bottom of di screen for a hint. + Abeg select all di correct choices. + You fit select more choices. + No more dan %s choices go dey selected. + diff --git a/app/src/main/res/values/color_defs.xml b/app/src/main/res/values/color_defs.xml index 3553669a41c..d99a90237e9 100644 --- a/app/src/main/res/values/color_defs.xml +++ b/app/src/main/res/values/color_defs.xml @@ -25,10 +25,11 @@ #000000 #00000000 #1F000000 + #3D000000 #42000000 #8A000000 + #C8000000 #DE000000 - #3D000000 #F9F9F9 #333333 #555555 diff --git a/app/src/main/res/values/color_palette.xml b/app/src/main/res/values/color_palette.xml index ad4f5e43a4b..8bb2dba3b17 100644 --- a/app/src/main/res/values/color_palette.xml +++ b/app/src/main/res/values/color_palette.xml @@ -199,6 +199,7 @@ @color/color_def_root_beer_blue @color/color_def_japanese_indigo @color/color_def_oppia_light_yellow + @color/color_def_black_78 @color/color_def_black @color/color_def_white @color/color_def_grey diff --git a/app/src/main/res/values/component_colors.xml b/app/src/main/res/values/component_colors.xml index ccdea1ba3df..1daab233d78 100644 --- a/app/src/main/res/values/component_colors.xml +++ b/app/src/main/res/values/component_colors.xml @@ -82,6 +82,7 @@ @color/color_palette_navbar_header_background_color @color/color_palette_icon_white_color @color/color_palette_icon_color + @color/color_palette_shared_spotlight_overlay_background_color @color/color_palette_shared_spotlight_hint_background_color @color/color_palette_shared_close_spotlight_button_color @color/color_palette_shared_spotlight_overlay_arrow_color diff --git a/app/src/main/res/values/untranslated_strings.xml b/app/src/main/res/values/untranslated_strings.xml index 9a52c5059c5..78b94ac1e9c 100644 --- a/app/src/main/res/values/untranslated_strings.xml +++ b/app/src/main/res/values/untranslated_strings.xml @@ -105,4 +105,5 @@ Português Português Kiswahili + Naijá diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt index 940bfaef32a..a9219bfddd5 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt @@ -171,6 +171,7 @@ class ProfileAndDeviceIdFragmentTest { fun setUp() { TestPlatformParameterModule.forceEnableEditAccountsOptionsUi(true) TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + TestPlatformParameterModule.forceEnableLoggingLearnerStudyIds(true) setUpTestApplicationComponent() Intents.init() testCoroutineDispatchers.registerIdlingResource() @@ -785,6 +786,11 @@ class ProfileAndDeviceIdFragmentTest { - Uploading learner events: 1 - Uploaded learner events: 2 Current sync status: Waiting to schedule data uploading worker…. + Event log encoding integrity checks: + - First 40 chars of encoded string: H4sIAAAAAAAAAOPSlGBUUj3FqMTFX5JaXBKfk5pY + - Last 40 chars of encoded string: BzGNlJIepORoISdAydHERJ4m4sMLAFFY60EUAwAA + - SHA-1 hash (unwrapped event string): 76f7a26348b4034787982f9505c6b5697efc6567 + - Total event string length (unwrapped): 140 Encoded event logs: H4sIAAAAAAAAAOPSlGBUUj3FqMTFX5JaXBKfk5pYlJdaFJ+ZIgQRyMwrLknMyQEKcBkSrVSLwYjBisGJ gU5ajEnVwsTBSDdNTELEBzGNlJIepORoISdAydHERJ4m4sMLAFFY60EUAwAA diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt index df0139a9bf8..f945b019e3c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt @@ -63,6 +63,8 @@ import org.oppia.android.app.model.OppiaLanguage.BRAZILIAN_PORTUGUESE import org.oppia.android.app.model.OppiaLanguage.BRAZILIAN_PORTUGUESE_VALUE import org.oppia.android.app.model.OppiaLanguage.ENGLISH import org.oppia.android.app.model.OppiaLanguage.ENGLISH_VALUE +import org.oppia.android.app.model.OppiaLanguage.NIGERIAN_PIDGIN +import org.oppia.android.app.model.OppiaLanguage.NIGERIAN_PIDGIN_VALUE import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.ReadingTextSize import org.oppia.android.app.model.ScreenName @@ -1782,6 +1784,52 @@ class HomeActivityTest { } } + @Test + @DefineAppLanguageLocaleContext( + oppiaLanguageEnumId = NIGERIAN_PIDGIN_VALUE, + appStringIetfTag = "pcm", + appStringAndroidLanguageId = "pcm", + appStringAndroidRegionId = "NG" + ) + @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3840): Make this test work on Espresso & Robolectric. + fun testHomeActivity_initialNigerianPidginContext_isInLtrLayout() { + // Ensure the system locale matches the initial locale context. + forceDefaultLocale(NIGERIA_NAIJA_LOCALE) + fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME) + fakeOppiaClock.setCurrentTimeToSameDateTime(MORNING_TIMESTAMP) + launch(createHomeActivityIntent(internalProfileId)).use { + testCoroutineDispatchers.runCurrent() + + val displayLocale = appLanguageLocaleHandler.getDisplayLocale() + val layoutDirection = displayLocale.getLayoutDirection() + assertThat(layoutDirection).isEqualTo(ViewCompat.LAYOUT_DIRECTION_LTR) + } + } + + // TODO(#3840): Make this test work on Espresso & Robolectric. + @Test + @DefineAppLanguageLocaleContext( + oppiaLanguageEnumId = NIGERIAN_PIDGIN_VALUE, + appStringIetfTag = "pcm", + appStringAndroidLanguageId = "pcm", + appStringAndroidRegionId = "NG" + ) + @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) + fun testHomeActivity_initialNigerianPidginContext_hasNaijaDisplayLocale() { + // Ensure the system locale matches the initial locale context. + forceDefaultLocale(NIGERIA_NAIJA_LOCALE) + fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME) + fakeOppiaClock.setCurrentTimeToSameDateTime(MORNING_TIMESTAMP) + launch(createHomeActivityIntent(internalProfileId)).use { + testCoroutineDispatchers.runCurrent() + + // Verify that the display locale is set up correctly (for string formatting). + val displayLocale = appLanguageLocaleHandler.getDisplayLocale() + val localeContext = displayLocale.localeContext + assertThat(localeContext.languageDefinition.language).isEqualTo(NIGERIAN_PIDGIN) + } + } + private fun markSpotlightSeen(profileId: ProfileId) { spotlightStateController.markSpotlightViewed(profileId, Spotlight.FeatureCase.PROMOTED_STORIES) testCoroutineDispatchers.runCurrent() @@ -1966,5 +2014,6 @@ class HomeActivityTest { private companion object { private val BRAZIL_PORTUGUESE_LOCALE = Locale("pt", "BR") private val EGYPT_ARABIC_LOCALE = Locale("ar", "EG") + private val NIGERIA_NAIJA_LOCALE = Locale("pcm", "NG") } } diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt index 265730f0a8f..0d092f56d1e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt @@ -35,6 +35,7 @@ import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule import org.oppia.android.app.model.AudioLanguage import org.oppia.android.app.model.AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE import org.oppia.android.app.model.AudioLanguage.ENGLISH_AUDIO_LANGUAGE +import org.oppia.android.app.model.AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView import org.oppia.android.app.shim.ViewBindingShimModule @@ -109,6 +110,8 @@ class AudioLanguageFragmentTest { private companion object { private const val ENGLISH_BUTTON_INDEX = 0 private const val PORTUGUESE_BUTTON_INDEX = 4 + private const val ARABIC_BUTTON_INDEX = 5 + private const val NIGERIAN_PIDGIN_BUTTON_INDEX = 6 } @get:Rule val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() @@ -138,6 +141,13 @@ class AudioLanguageFragmentTest { } } + @Test + fun testOpenFragment_withNigerianPidgin_selectedLanguageIsNaija() { + launchActivityWithLanguage(NIGERIAN_PIDGIN_LANGUAGE).use { + verifyNigerianPidginIsSelected() + } + } + @Test fun testAudioLanguage_configChange_selectedLanguageIsEnglish() { launchActivityWithLanguage(ENGLISH_AUDIO_LANGUAGE).use { @@ -243,6 +253,10 @@ class AudioLanguageFragmentTest { verifyLanguageIsSelected(index = PORTUGUESE_BUTTON_INDEX, expectedLanguageName = "Português") } + private fun verifyNigerianPidginIsSelected() { + verifyLanguageIsSelected(index = NIGERIAN_PIDGIN_BUTTON_INDEX, expectedLanguageName = "Naijá") + } + private fun verifyLanguageIsSelected(index: Int, expectedLanguageName: String) { onView( atPositionOnView( diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt index 57c8e47299b..d5e3bfab34c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt @@ -111,7 +111,6 @@ import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode -import java.util.Locale import javax.inject.Inject import javax.inject.Singleton @@ -155,7 +154,7 @@ class AudioFragmentTest { "2mzzFVDLuAj8/assets/audio/content-en-057j51i2es.mp3" private val TEST_URL2 = "https://storage.googleapis.com/oppiaserver-resources/exploration/" + - "2mzzFVDLuAj8/assets/audio/content-es-i0nhu49z0q.mp3" + "2mzzFVDLuAj8/assets/audio/content-hi-2hn6btuei5.mp3" private var internalProfileId = 0 private var profileId = ProfileId.newBuilder().setInternalId(internalProfileId).build() @@ -328,11 +327,10 @@ class AudioFragmentTest { testCoroutineDispatchers.runCurrent() onView(withId(R.id.audio_language_icon)).perform(click()) - // TODO(#3791): Remove this dependency. - val locale = Locale("es") - testCoroutineDispatchers.runCurrent() - onView(withText(locale.getDisplayLanguage(locale))).inRoot(isDialog()).perform(click()) + onView(withText(R.string.hinglish_localized_language_name)) + .inRoot(isDialog()) + .perform(click()) testCoroutineDispatchers.runCurrent() onView(withText("OK")).inRoot(isDialog()).perform(click()) diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt index b3ca06c1c64..1daf0a16ce1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt @@ -250,7 +250,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_explorationLoads() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -261,7 +261,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_explorationLoads_changeConfiguration_buttonIsNotVisible() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -274,7 +274,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_explorationHasContinueButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -286,7 +286,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_changeConfiguration_explorationHasContinueButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -299,7 +299,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_secondState_hasSubmitButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -314,7 +314,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_changeConfiguration_secondState_hasSubmitButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -330,7 +330,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_secondState_submitAnswer_submitButtonIsEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() clickContinueInteractionButton() @@ -344,7 +344,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_secondState_submitAnswer_clickSubmit_continueButtonIsVisible() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() clickContinueInteractionButton() @@ -361,7 +361,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_landscape_secondState_submitAnswer_submitButtonIsEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -376,7 +376,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_land_secondState_submitAnswer_clickSubmit_continueIsVisible() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -394,7 +394,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_secondState_submitInvalidAnswer_disablesSubmitAndShowsError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() clickContinueInteractionButton() @@ -411,7 +411,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_land_secondState_submitInvalidAnswer_disablesSubmitAndShowsError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -429,7 +429,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_secondState_invalidAnswer_submitAnswerIsNotEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() clickContinueInteractionButton() @@ -443,7 +443,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_secondState_invalidAnswer_updated_submitAnswerIsEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() clickContinueInteractionButton() @@ -461,7 +461,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_land_secondState_invalidAnswer_submitAnswerIsNotEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -476,7 +476,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_land_secondState_invalidAnswer_updated_submitAnswerIsEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -495,7 +495,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_secondState_submitWrongAnswer_contentDescriptionIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() clickContinueInteractionButton() @@ -517,7 +517,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_secondState_submitCorrectAnswer_contentDescriptionIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() clickContinueInteractionButton() @@ -539,7 +539,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_thirdState_hasDisabledSubmitButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -555,7 +555,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_changeConfiguration_thirdState_hasDisabledSubmitButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -573,7 +573,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_thirdState_selectAnswer_submitButtonIsEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -588,7 +588,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_thirdState_selectAnswer_clickSubmit_continueButtonIsVisible() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -606,7 +606,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_landscape_thirdState_selectAnswer_submitButtonIsEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -622,7 +622,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_land_thirdState_selectAnswer_clickSubmit_continueIsVisible() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -641,7 +641,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_thirdState_submitInvalidAnswer_disablesSubmitButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -659,7 +659,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_land_thirdState_submitInvalidAnswer_disablesSubmitButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -677,7 +677,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_thirdState_invalidAnswer_updated_submitAnswerIsEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -696,7 +696,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_firstState_previousAndNextButtonIsNotDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -707,7 +707,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadDragDropExp_mergeFirstTwoItems_worksCorrectly() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_4, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -726,7 +726,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadDragDropExp_mergeFirstTwoItems_invalidAnswer_correctItemCount() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_4, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -747,7 +747,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadDragDropExp_wrongAnswer_contentDescriptionIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_4, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -768,7 +768,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO) // TODO(#1612): Enable for Robolectric. fun testStateFragment_loadDragDropExp_correctAnswer_contentDescriptionIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -801,7 +801,7 @@ class StateFragmentTest { // Note to self: current setup allows the user to drag the view without issues (now that // event interception isn't a problem), however the view is going partly offscreen which // is triggering an infinite animation loop in ItemTouchHelper). - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_4, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -821,7 +821,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadDragDropExp_mergeFirstTwoItems_unlinkFirstItem_worksCorrectly() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_4, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -843,7 +843,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_clickRegion6_submitButtonEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -859,7 +859,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_clickRegion6_clickSubmit_receivesCorrectFeedback() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -880,7 +880,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_submitButtonDisabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -895,7 +895,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_defaultRegionClick_defRegionClicked_submitButtonDisabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -910,7 +910,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_clickedRegion6_region6Clicked_submitButtonEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -926,7 +926,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_clickedRegion6_region6Clicked_correctFeedback() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -947,7 +947,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_clickedRegion6_region6Clicked_correctAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -968,7 +968,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_clickedRegion6_region6Clicked_continueButtonIsDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -985,7 +985,7 @@ class StateFragmentTest { @RunOn(TestPlatform.ESPRESSO) // TODO(#1611): Enable for Robolectric. @Ignore("Flaky test") // TODO(#3171): Fix ImageRegion failing test cases. fun testStateFragment_loadImageRegion_clickRegion6_clickedRegion5_clickRegion5_correctFeedback() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_13, shouldSavePartialProgress = false).use { startPlayingExploration() waitForImageViewInteractionToFullyLoad() @@ -1005,7 +1005,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_changeConfiguration_firstState_prevAndNextButtonIsNotDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -1018,7 +1018,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_submitAnswer_clickContinueButton_previousButtonIsDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -1030,7 +1030,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_changeConfig_submitAnswer_clickContinue_prevButtonIsDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -1043,7 +1043,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_submitAnswer_clickContinueThenPrevious_onlyNextButtonIsShown() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() clickContinueInteractionButton() @@ -1059,7 +1059,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_changeConfig_submit_clickContinueThenPrev_onlyNextButtonShown() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -1076,7 +1076,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_submitAnswer_clickContinueThenPrevThenNext_prevAndSubmitShown() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() clickContinueInteractionButton() @@ -1095,7 +1095,7 @@ class StateFragmentTest { @Test fun testStateFragment_loadExp_land_submit_clickContinueThenPrevThenNext_prevAndSubmitShown() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -1116,7 +1116,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO) // TODO(#1612): Enable for Robolectric. fun testStateFragment_loadExp_continueToEndExploration_hasReturnToTopicButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -1133,7 +1133,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO) // TODO(#1612): Enable for Robolectric. fun testStateFragment_loadExp_changeConfiguration_continueToEnd_hasReturnToTopicButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -1151,7 +1151,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO) // TODO(#1612): Enable for Robolectric. fun testStateFragment_loadExp_continueToEndExploration_clickReturnToTopic_destroysActivity() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeExploration() @@ -1166,7 +1166,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO) // TODO(#1612): Enable for Robolectric. fun testStateFragment_loadExp_changeConfig_continueToEnd_clickReturnToTopic_destroysActivity() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() rotateToLandscape() @@ -1181,7 +1181,7 @@ class StateFragmentTest { @Test fun testContentCard_forPrototypeExploration_withCustomOppiaTags_displaysParsedHtml() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -1193,7 +1193,7 @@ class StateFragmentTest { @Test fun testContentCard_forPrototypeExploration_changeConfig_withCustomTags_displaysParsedHtml() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -1205,7 +1205,7 @@ class StateFragmentTest { @Test fun testStateFragment_inputRatio_correctAnswerSubmitted_correctAnswerIsDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1225,7 +1225,7 @@ class StateFragmentTest { @Test fun testStateFragment_forHintsAndSolution_incorrectInputTwice_hintBulbContainerIsVisible() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(FRACTIONS_EXPLORATION_ID_1, shouldSavePartialProgress = false).use { startPlayingExploration() selectMultipleChoiceOption( @@ -1248,7 +1248,7 @@ class StateFragmentTest { @Test fun testStateFragment_forMisconception_showsLinkTextForConceptCard() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(FRACTIONS_EXPLORATION_ID_1, shouldSavePartialProgress = false).use { startPlayingExploration() selectMultipleChoiceOption( @@ -1273,7 +1273,7 @@ class StateFragmentTest { @Test fun testStateFragment_landscape_forMisconception_showsLinkTextForConceptCard() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(FRACTIONS_EXPLORATION_ID_1, shouldSavePartialProgress = false).use { rotateToLandscape() startPlayingExploration() @@ -1299,7 +1299,7 @@ class StateFragmentTest { @Test fun testStateFragment_forMisconception_clickLinkText_opensConceptCard() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(FRACTIONS_EXPLORATION_ID_1, shouldSavePartialProgress = false).use { startPlayingExploration() selectMultipleChoiceOption( @@ -1323,7 +1323,7 @@ class StateFragmentTest { @Test fun testStateFragment_landscape_forMisconception_clickLinkText_opensConceptCard() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(FRACTIONS_EXPLORATION_ID_1, shouldSavePartialProgress = false).use { rotateToLandscape() startPlayingExploration() @@ -1348,7 +1348,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_initialStateIsContinueInteraction() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -1360,7 +1360,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_continueInteraction_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -1375,7 +1375,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_fractionInteraction_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1391,7 +1391,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_multipleChoiceInteraction_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1408,7 +1408,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_radioItemSelection_hasCorrectAccessibilityAttributes() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1425,7 +1425,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_radioItemSelection_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1443,7 +1443,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_checkboxItemSelection_hasCorrectAccessibilityAttributes() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1459,7 +1459,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_checkboxItemSelection_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1478,7 +1478,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_numericInputInteraction_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1498,7 +1498,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_numericInputInteraction_hasCorrectHint() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1516,7 +1516,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_ratioInputInteraction_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1537,7 +1537,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_textInputInteraction_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1560,7 +1560,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO) // TODO(#1612): Enable for Robolectric. fun testStateFragment_interactions_dragAndDropNoGrouping_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1584,7 +1584,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO) // TODO(#1612): Enable for Robolectric. fun testStateFragment_interactions_dragAndDropWithGrouping_canSuccessfullySubmitAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -1607,7 +1607,7 @@ class StateFragmentTest { @Test fun testStateFragment_fractionInput_textViewHasTextInputType() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() @@ -1625,7 +1625,7 @@ class StateFragmentTest { @Test fun testStateFragment_ratioInput_textViewHasTextInputType() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() playThroughPrototypeState1() @@ -1649,7 +1649,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO) // TODO(#1612): Enable for Robolectric. fun testStateFragment_loadExp_saveProg_continueToEndExp_clickReturnToTopic_partialProgDeleted() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeExploration() @@ -1668,7 +1668,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_englishContentLang_content_isInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1681,7 +1681,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabicContentLang_content_isInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1694,7 +1694,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabicContentLang_thenEnglish_content_isInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1708,7 +1708,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_continueInteraction_buttonIsInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1721,7 +1721,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_continueInteraction_buttonIsInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1734,7 +1734,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_fractionInput_placeholderIsInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1749,7 +1749,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_fractionInput_submitAnswer_answerMatchesSubmission() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1767,7 +1767,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_fractionInput_placeholderIsInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1782,7 +1782,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_fractionInput_submitAnswer_answerMatchesSubmission() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1801,7 +1801,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_englishContentLang_feedback_isInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1819,7 +1819,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabicContentLang_feedback_isInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1838,7 +1838,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabicContentLang_thenEnglish_feedback_isInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1857,7 +1857,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_multipleChoice_optionsAreInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1879,7 +1879,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_multipleChoice_submittedAnswer_answerIsInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1897,7 +1897,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_multipleChoice_optionsAreInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1920,7 +1920,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_multipleChoice_submittedAnswer_answerIsInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1939,7 +1939,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_multipleChoice_submittedAnswer_switchToEnglish_answerIsInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1959,7 +1959,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_itemSelection_optionsAreInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -1983,7 +1983,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_itemSelection_submittedAnswer_answerIsInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() @@ -2006,7 +2006,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_itemSelection_optionsAreInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2031,7 +2031,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_itemSelection_submittedAnswer_answerIsInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2054,7 +2054,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_itemSelection_submittedAnswer_switchToEnglish_answerIsInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2078,7 +2078,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_numericInput_submitAnswer_answerMatchesSubmission() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2100,7 +2100,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_numericInput_submitAnswer_answerMatchesSubmission() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2122,7 +2122,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_ratioInput_placeholderIsInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2141,7 +2141,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_ratioInput_submitAnswer_answerMatchesSubmission() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2164,7 +2164,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_ratioInput_placeholderIsInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2184,7 +2184,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_ratioInput_submitAnswer_answerMatchesSubmission() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2207,7 +2207,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_textInput_placeholderIsInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2227,7 +2227,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_textInput_submitAnswer_answerMatchesSubmission() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2251,7 +2251,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_textInput_placeholderIsInArabic() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2272,7 +2272,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_textInput_submitAnswer_answerMatchesSubmission() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2296,7 +2296,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_arabic_textInput_submitAnswer_switchToEnglish_answerDoesNotChange() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2321,7 +2321,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testStateFragment_english_dragAndDrop_optionsAreInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2350,7 +2350,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_english_dragAndDrop_submittedAnswer_answerIsInEnglish() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2381,7 +2381,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_portuguese_dragAndDrop_optionsAreInPortuguese() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2410,7 +2410,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_portuguese_dragAndDrop_submittedAnswer_answerIsInPortuguese() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2441,7 +2441,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_portuguese_dragAndDrop_submittedAnswer_switchToEnglish_answerIsInPt() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = true).use { startPlayingExploration() playThroughPrototypeState1() @@ -2474,7 +2474,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_playWholeLesson_inArabic_hasReturnToTopicButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2491,7 +2491,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_studyOff_inEnglish_doesNotHaveSwitchToSwahiliButton() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2504,7 +2504,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_studyOn_inEnglish_lessonWithoutSwahili_doesNotHaveSwitchToSwahiliButton() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() launchForExploration(FRACTIONS_EXPLORATION_ID_1, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2518,7 +2518,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_studyOn_inEnglish_notEnabledForProfile_doesNotHaveSwitchToSwahiliButton() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2532,7 +2532,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_studyOn_enabledForProfile_inEnglish_hasSwitchToSwahiliButton() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() enableInLessonLanguageSwitching() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2549,7 +2549,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_studyOn_enabledForProfile_inSwahili_hasSwitchToEnglishButton() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() enableInLessonLanguageSwitching() updateContentLanguage(profileId, OppiaLanguage.SWAHILI) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { @@ -2567,7 +2567,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_inEnglish_clickSwitchToSwahili_contentIsInSwahili() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() enableInLessonLanguageSwitching() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2586,7 +2586,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_inSwahili_clickSwitchToEnglish_contentIsInEnglish() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() enableInLessonLanguageSwitching() updateContentLanguage(profileId, OppiaLanguage.SWAHILI) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { @@ -2606,7 +2606,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ESPRESSO, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_inEnglish_clickSwitchToSwahili_thenBackToEnglish_contentIsInEnglish() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() enableInLessonLanguageSwitching() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2627,7 +2627,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_inEnglish_clickSwitchToSwahili_logsSwitchLanguageEvent() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() enableInLessonLanguageSwitching() updateContentLanguage(profileId, OppiaLanguage.ENGLISH) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { @@ -2649,7 +2649,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_inSwahili_clickSwitchToEnglish_logsSwitchLanguageEvent() { - setUpTestWithStudyOn() + setUpTestWithLanguageSwitchingFeatureOn() enableInLessonLanguageSwitching() updateContentLanguage(profileId, OppiaLanguage.SWAHILI) launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { @@ -2670,7 +2670,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_matchesExactly_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2686,7 +2686,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_matchesExactly_diffOrder_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2702,7 +2702,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_matchesExactly_diffElems_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2718,7 +2718,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_matchesExactly_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2734,7 +2734,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_matchesUpTo_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughMathInteractionExplorationState1() @@ -2751,7 +2751,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_matchesUpTo_diffOrder_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughMathInteractionExplorationState1() @@ -2769,7 +2769,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_matchesUpTo_diffElems_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughMathInteractionExplorationState1() @@ -2786,7 +2786,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_matchesUpTo_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughMathInteractionExplorationState1() @@ -2803,7 +2803,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_equivalence_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState2() @@ -2820,7 +2820,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_equivalence_diffOrder_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState2() @@ -2838,7 +2838,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_equivalence_diffElems_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState2() @@ -2856,7 +2856,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_equivalence_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState2() @@ -2873,7 +2873,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_answerWithDivideByZero_displaysError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2886,7 +2886,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_answerWithVariable_displaysError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2903,7 +2903,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_validAns_submissionDisplaysLatex() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() playThroughMathInteractionExplorationState1() @@ -2927,7 +2927,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_numericExp_validAns_divAsFrac_submissionDisplaysLatex() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() @@ -2952,7 +2952,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_numericExp_validAns_english_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughMathInteractionExplorationState1() @@ -2970,7 +2970,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_numericExp_validAns_divAsFrac_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -2987,7 +2987,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_numericExp_validAns_arabic_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -3006,7 +3006,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_matchesExactly_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState3() @@ -3023,7 +3023,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_matchesExactly_diffOrder_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState3() @@ -3040,7 +3040,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_matchesExactly_diffElems_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState3() @@ -3057,7 +3057,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_matchesExactly_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState3() @@ -3074,7 +3074,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_matchesUpTo_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState4() @@ -3091,7 +3091,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_matchesUpTo_diffOrder_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState4() @@ -3109,7 +3109,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_matchesUpTo_diffElems_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState4() @@ -3126,7 +3126,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_matchesUpTo_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState4() @@ -3143,7 +3143,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_equivalence_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState5() @@ -3160,7 +3160,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_equivalence_diffOrder_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState5() @@ -3178,7 +3178,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_equivalence_diffElems_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState5() @@ -3196,7 +3196,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_equivalence_diffElems_andVals_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState5() @@ -3214,7 +3214,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_equivalence_diffOperations_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState5() @@ -3233,7 +3233,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_equivalence_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState5() @@ -3250,7 +3250,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_answerWithVariablePower_displaysError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState3() @@ -3267,7 +3267,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_answerWithUnknownVars_displaysError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState3() @@ -3282,7 +3282,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_validAns_submissionDisplaysLatex() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() playUpThroughMathInteractionExplorationState4() @@ -3306,7 +3306,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_algExp_validAns_divAsFrac_submissionDisplaysLatex() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() playUpThroughMathInteractionExplorationState3() @@ -3332,7 +3332,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_algExp_validAns_english_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState4() @@ -3354,7 +3354,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_algExp_validAns_divAsFrac_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState3() @@ -3377,7 +3377,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_algExp_validAns_arabic_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -3396,7 +3396,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesExactly_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3413,7 +3413,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesExactly_flipped_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3431,7 +3431,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesExactly_diffOrder_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3448,7 +3448,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesExactly_diffElems_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3465,7 +3465,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesExactly_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3482,7 +3482,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesUpTo_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState7() @@ -3499,7 +3499,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesUpTo_flipped_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState7() @@ -3517,7 +3517,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesUpTo_diffOrder_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState7() @@ -3535,7 +3535,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesUpTo_diffElems_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState7() @@ -3552,7 +3552,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_matchesUpTo_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState7() @@ -3569,7 +3569,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_canSubmitCorrectAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3588,7 +3588,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_flipped_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3608,7 +3608,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_diffOrder_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3628,7 +3628,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_diffElems_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3648,7 +3648,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_diffElems_andVals_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3666,7 +3666,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_diffOperations_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3687,7 +3687,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_rearranged_answerIsCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3707,7 +3707,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_multiple_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3725,7 +3725,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_equivalence_diffValue_answerIsWrong() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState8() @@ -3742,7 +3742,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_answerWithDoubleMult_displaysError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3756,7 +3756,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_missingEquals_displaysError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3771,7 +3771,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_answerWithUnknownVars_displaysError() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3788,7 +3788,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_validAns_submissionDisplaysLatex() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() playUpThroughMathInteractionExplorationState7() @@ -3812,7 +3812,7 @@ class StateFragmentTest { @Test fun testStateFragment_mathInteractions_mathEq_validAns_divAsFrac_submissionDisplaysLatex() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { scenario -> startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3838,7 +3838,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_mathEq_validAns_english_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState7() @@ -3861,7 +3861,7 @@ class StateFragmentTest { @Test @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_mathEq_validAns_divAsFrac_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() playUpThroughMathInteractionExplorationState6() @@ -3886,7 +3886,7 @@ class StateFragmentTest { @Test @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testStateFragment_mathInteractions_mathEq_validAns_arabic_submissionHasA11yAnswer() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() updateContentLanguage(profileId, OppiaLanguage.ARABIC) launchForExploration(TEST_EXPLORATION_ID_5, shouldSavePartialProgress = false).use { startPlayingExploration() @@ -3908,7 +3908,7 @@ class StateFragmentTest { @Test fun testStateFragment_clickContinue_returnToState_doesNotHaveFeedbackBox() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -3923,7 +3923,7 @@ class StateFragmentTest { @Test fun testStateFragment_clickContinue_finishNextState_returnToContinue_doesNotHaveFeedbackBox() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -3941,7 +3941,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_noRadioItemSelected_defaultSelectionTextIsDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -3957,7 +3957,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_oneRadioItemSelected_selectionTextIsDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -3975,7 +3975,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_twoRadioItemSelected_selectionTextIsDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -3994,7 +3994,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_maxRadioItemSelected_selectionTextIsDisplayed() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -4014,7 +4014,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_maxRadioItemSelected_nonSelectedCheckboxesAreDisabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -4055,7 +4055,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_maxItemSelected_deselectingReturnsYouMaySelectMoreChoices() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -4076,7 +4076,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_someItemSelected_deselectingReturnsPleaseSelectAllCorrect() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -4097,7 +4097,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_notSelectingMaxRadioItem_return_allOtherCheckBoxesEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -4145,7 +4145,7 @@ class StateFragmentTest { @Test fun testStateFragment_interactions_SelectingMaxItemAndOneBelow_returnNoOtherCheckBoxesEnabled() { - setUpTestWithStudyOff() + setUpTestWithLanguageSwitchingFeatureOff() launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { startPlayingExploration() playThroughPrototypeState1() @@ -4719,13 +4719,13 @@ class StateFragmentTest { return onView(isRoot()).perform(waitForMatch(viewMatcher, 30000L)) } - private fun setUpTestWithStudyOn() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + private fun setUpTestWithLanguageSwitchingFeatureOn() { + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) setUpTest() } - private fun setUpTestWithStudyOff() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(false) + private fun setUpTestWithLanguageSwitchingFeatureOff() { + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(false) setUpTest() } diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt index feac49bcc26..54d977d015d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt @@ -354,8 +354,8 @@ class ProfileEditFragmentTest { } @Test - fun testProfileEdit_studyOff_doesNotHaveEnableQuickSwitchingSwitch() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(false) + fun testProfileEdit_featureOff_doesNotHaveEnableQuickSwitchingSwitch() { + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(false) // Without the study feature enabled, the switch should not be visible. launchFragmentTestActivity(internalProfileId = 0).use { @@ -365,8 +365,8 @@ class ProfileEditFragmentTest { } @Test - fun testProfileEdit_studyOn_hasEnableQuickSwitchingSwitch() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + fun testProfileEdit_featureOn_hasEnableQuickSwitchingSwitch() { + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) launchFragmentTestActivity(internalProfileId = 0).use { onView(withId(R.id.profile_edit_enable_in_lesson_language_switching_container)) @@ -376,8 +376,8 @@ class ProfileEditFragmentTest { @Test @Config(qualifiers = "land") - fun testProfileEdit_studyOn_landscape_hasEnableQuickSwitchingSwitch() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + fun testProfileEdit_featureOn_landscape_hasEnableQuickSwitchingSwitch() { + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) launchFragmentTestActivity(internalProfileId = 0).use { onView(isRoot()).perform(orientationLandscape()) @@ -392,8 +392,8 @@ class ProfileEditFragmentTest { } @Test - fun testProfileEdit_studyOn_doNotHaveSwitchingPermission_enableLanguageSwitchingIsOff() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + fun testProfileEdit_featureOn_doNotHaveSwitchingPermission_enableLanguageSwitchingIsOff() { + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) // Without the permission to switch languages, the setting should be off by default. launchFragmentTestActivity(internalProfileId = 0).use { @@ -403,8 +403,8 @@ class ProfileEditFragmentTest { } @Test - fun testProfileEdit_studyOn_hasSwitchingPermission_enableLanguageSwitchingIsOn() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + fun testProfileEdit_featureOn_hasSwitchingPermission_enableLanguageSwitchingIsOn() { + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) val updateLangProvider = profileManagementController.updateEnableInLessonQuickLanguageSwitching( profileId = ProfileId.newBuilder().apply { internalId = 0 }.build(), @@ -420,8 +420,8 @@ class ProfileEditFragmentTest { } @Test - fun testProfileEdit_studyOn_doNotClickEnableLanguageSwitching_doesNotHaveSwitchingPermission() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + fun testProfileEdit_featureOn_doNotClickEnableLanguageSwitching_doesNotHaveSwitchingPermission() { + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) // Open the UI, but don't interact with it. launchFragmentTestActivity(internalProfileId = 0).use {} @@ -437,7 +437,7 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_studyOn_clickEnableLanguageSwitching_hasSwitchingPermission() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) // Enable language switching in the UI. launchFragmentTestActivity(internalProfileId = 0).use { diff --git a/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt index aec2ab09efb..17fc93aeeb9 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt @@ -48,6 +48,7 @@ import org.oppia.android.app.model.OppiaLanguage.ARABIC import org.oppia.android.app.model.OppiaLanguage.BRAZILIAN_PORTUGUESE import org.oppia.android.app.model.OppiaLanguage.ENGLISH import org.oppia.android.app.model.OppiaLanguage.LANGUAGE_UNSPECIFIED +import org.oppia.android.app.model.OppiaLanguage.NIGERIAN_PIDGIN import org.oppia.android.app.model.OppiaLocaleContext import org.oppia.android.app.model.OppiaRegion import org.oppia.android.app.model.ScreenName @@ -330,6 +331,21 @@ class SplashActivityTest { } } + @Test + @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) + fun testSplashActivity_nigerianPidginLocale_initializesLocaleHandlerNaijaContext() { + initializeTestApplication() + forceDefaultLocale(NIGERIAN_PIDGIN_LOCALE) + + launchSplashActivityFully { + // Verify that the locale is initialized (i.e. getDisplayLocale doesn't throw an exception) & + // that the correct display locale is defined per the system locale. + val displayLocale = appLanguageLocaleHandler.getDisplayLocale() + val context = displayLocale.localeContext + assertThat(context.languageDefinition.language).isEqualTo(NIGERIAN_PIDGIN) + } + } + @Test fun testSplashActivity_unsupportedLocale_initializesLocaleHandlerWithUnspecifiedLanguage() { initializeTestApplication() @@ -1276,6 +1292,7 @@ class SplashActivityTest { private companion object { private val EGYPT_ARABIC_LOCALE = Locale("ar", "EG") private val BRAZIL_PORTUGUESE_LOCALE = Locale("pt", "BR") + private val NIGERIAN_PIDGIN_LOCALE = Locale("pcm", "NG") private val TURKEY_TURKISH_LOCALE = Locale("tr", "TR") private fun onDialogView(matcher: Matcher) = onView(matcher).inRoot(isDialog()) diff --git a/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt index 864ac67c862..95943eb5b24 100644 --- a/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt @@ -1691,6 +1691,7 @@ class StateFragmentLocalTest { appStringIetfTag = "en", appStringAndroidLanguageId = "" ) + @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds. fun testStateFragment_englishLocale_defaultContentLang_hint_titlesAreCorrectInEnglish() { // Ensure the system locale matches the initial locale context. forceDefaultLocale(Locale.ENGLISH) @@ -1717,6 +1718,7 @@ class StateFragmentLocalTest { appStringIetfTag = "en", appStringAndroidLanguageId = "" ) + @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds. fun testStateFragment_englishLocale_defaultContentLang_hint_labelsAreInEnglish() { // Ensure the system locale matches the initial locale context. forceDefaultLocale(Locale.ENGLISH) @@ -1742,6 +1744,7 @@ class StateFragmentLocalTest { appStringIetfTag = "en", appStringAndroidLanguageId = "" ) + @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds. fun testStateFragment_englishLocale_defaultContentLang_hint_explanationIsInEnglish() { // Ensure the system locale matches the initial locale context. forceDefaultLocale(Locale.ENGLISH) diff --git a/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt b/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt index 54d346d8e8f..b83166fd691 100644 --- a/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt +++ b/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt @@ -498,6 +498,8 @@ class AppLanguageResourceHandlerTest { Iteration("fr", "lang=FRENCH_AUDIO_LANGUAGE", "expectedDisplayText=Français"), Iteration("zh", "lang=CHINESE_AUDIO_LANGUAGE", "expectedDisplayText=中文"), Iteration("pr-pt", "lang=BRAZILIAN_PORTUGUESE_LANGUAGE", "expectedDisplayText=Português"), + Iteration("ar", "lang=ARABIC_LANGUAGE", "expectedDisplayText=العربية"), + Iteration("pcm", "lang=NIGERIAN_PIDGIN_LANGUAGE", "expectedDisplayText=Naijá"), Iteration("unsp", "lang=AUDIO_LANGUAGE_UNSPECIFIED", "expectedDisplayText=English"), Iteration("none", "lang=NO_AUDIO", "expectedDisplayText=English"), Iteration("unknown", "lang=UNRECOGNIZED", "expectedDisplayText=English"), @@ -522,7 +524,8 @@ class AppLanguageResourceHandlerTest { Iteration("hi-en", "lang=HINGLISH", "expectedDisplayText=हिन्दी"), Iteration("pt", "lang=PORTUGUESE", "expectedDisplayText=Português"), Iteration("pr-pt", "lang=BRAZILIAN_PORTUGUESE", "expectedDisplayText=Português"), - Iteration("sw", "lang=SWAHILI", "expectedDisplayText=Kiswahili") + Iteration("sw", "lang=SWAHILI", "expectedDisplayText=Kiswahili"), + Iteration("pcm", "lang=NIGERIAN_PIDGIN", "expectedDisplayText=Naijá") ) fun testComputeLocalizedDisplayName_englishLocale_forAllDisplayLanguages_hasTheExpectedOutput() { updateAppLanguageTo(OppiaLanguage.ENGLISH) diff --git a/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt b/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt index 323ab88e166..4ec7e401ffd 100644 --- a/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt +++ b/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt @@ -37,6 +37,7 @@ import org.oppia.android.app.model.OppiaLanguage.ENGLISH import org.oppia.android.app.model.OppiaLanguage.HINDI import org.oppia.android.app.model.OppiaLanguage.HINGLISH import org.oppia.android.app.model.OppiaLanguage.LANGUAGE_UNSPECIFIED +import org.oppia.android.app.model.OppiaLanguage.NIGERIAN_PIDGIN import org.oppia.android.app.model.OppiaLanguage.PORTUGUESE import org.oppia.android.app.model.OppiaLanguage.SWAHILI import org.oppia.android.app.model.OppiaLanguage.UNRECOGNIZED @@ -175,6 +176,7 @@ class MathExpressionAccessibilityUtilTest { Iteration("PORTUGUESE", "language=PORTUGUESE"), Iteration("BRAZILIAN_PORTUGUESE", "language=BRAZILIAN_PORTUGUESE"), Iteration("SWAHILI", "language=SWAHILI"), + Iteration("NIGERIAN_PIDGIN", "language=NIGERIAN_PIDGIN"), Iteration("UNRECOGNIZED", "language=UNRECOGNIZED") ) fun testConvertToString_constExp_unsupportedLanguage_returnsNull() { @@ -193,6 +195,7 @@ class MathExpressionAccessibilityUtilTest { Iteration("PORTUGUESE", "language=PORTUGUESE"), Iteration("BRAZILIAN_PORTUGUESE", "language=BRAZILIAN_PORTUGUESE"), Iteration("SWAHILI", "language=SWAHILI"), + Iteration("NIGERIAN_PIDGIN", "language=NIGERIAN_PIDGIN"), Iteration("UNRECOGNIZED", "language=UNRECOGNIZED") ) fun testConvertToString_constEq_unsupportedLanguage_returnsNull() { @@ -211,7 +214,7 @@ class MathExpressionAccessibilityUtilTest { .asList() .containsExactly( LANGUAGE_UNSPECIFIED, ENGLISH, ARABIC, HINDI, HINGLISH, PORTUGUESE, BRAZILIAN_PORTUGUESE, - SWAHILI, UNRECOGNIZED + SWAHILI, NIGERIAN_PIDGIN, UNRECOGNIZED ) } diff --git a/config/src/java/org/oppia/android/config/alllanguages/supported_languages.textproto b/config/src/java/org/oppia/android/config/alllanguages/supported_languages.textproto index 76863ee2348..f0f45c3891e 100644 --- a/config/src/java/org/oppia/android/config/alllanguages/supported_languages.textproto +++ b/config/src/java/org/oppia/android/config/alllanguages/supported_languages.textproto @@ -138,3 +138,27 @@ language_definitions { } } } +language_definitions { + language: NIGERIAN_PIDGIN + fallback_macro_language: ENGLISH + min_android_sdk_version: 1 + app_string_id { + ietf_bcp47_id { + ietf_language_tag: "pcm" + } + android_resources_language_id { + language_code: "pcm" + region_code: "NG" + } + } + content_string_id { + ietf_bcp47_id { + ietf_language_tag: "pcm" + } + } + audio_translation_id { + ietf_bcp47_id { + ietf_language_tag: "pcm" + } + } +} diff --git a/config/src/java/org/oppia/android/config/alllanguages/supported_regions.textproto b/config/src/java/org/oppia/android/config/alllanguages/supported_regions.textproto index 9cc01d7d53b..8c346ff3256 100644 --- a/config/src/java/org/oppia/android/config/alllanguages/supported_regions.textproto +++ b/config/src/java/org/oppia/android/config/alllanguages/supported_regions.textproto @@ -29,3 +29,11 @@ region_definitions { languages: ENGLISH languages: SWAHILI } +region_definitions { + region: NIGERIA + region_id { + ietf_region_tag: "NG" + } + languages: ENGLISH + languages: NIGERIAN_PIDGIN +} diff --git a/config/src/java/org/oppia/android/config/productionlanguages/supported_languages.textproto b/config/src/java/org/oppia/android/config/productionlanguages/supported_languages.textproto index 91f7833c04c..d4c9c9ee61f 100644 --- a/config/src/java/org/oppia/android/config/productionlanguages/supported_languages.textproto +++ b/config/src/java/org/oppia/android/config/productionlanguages/supported_languages.textproto @@ -1,3 +1,25 @@ +language_definitions { + language: ARABIC + min_android_sdk_version: 1 + app_string_id { + ietf_bcp47_id { + ietf_language_tag: "ar" + } + android_resources_language_id { + language_code: "ar" + } + } + content_string_id { + ietf_bcp47_id { + ietf_language_tag: "ar" + } + } + audio_translation_id { + ietf_bcp47_id { + ietf_language_tag: "ar" + } + } +} language_definitions { language: ENGLISH min_android_sdk_version: 1 @@ -57,3 +79,27 @@ language_definitions { } } } +language_definitions { + language: NIGERIAN_PIDGIN + fallback_macro_language: ENGLISH + min_android_sdk_version: 1 + app_string_id { + ietf_bcp47_id { + ietf_language_tag: "pcm" + } + android_resources_language_id { + language_code: "pcm" + region_code: "NG" + } + } + content_string_id { + ietf_bcp47_id { + ietf_language_tag: "pcm" + } + } + audio_translation_id { + ietf_bcp47_id { + ietf_language_tag: "pcm" + } + } +} diff --git a/config/src/java/org/oppia/android/config/productionlanguages/supported_regions.textproto b/config/src/java/org/oppia/android/config/productionlanguages/supported_regions.textproto index 0400f40454c..563370f5318 100644 --- a/config/src/java/org/oppia/android/config/productionlanguages/supported_regions.textproto +++ b/config/src/java/org/oppia/android/config/productionlanguages/supported_regions.textproto @@ -19,4 +19,13 @@ region_definitions { ietf_region_tag: "KE" } languages: ENGLISH + languages: SWAHILI +} +region_definitions { + region: NIGERIA + region_id { + ietf_region_tag: "NG" + } + languages: ENGLISH + languages: NIGERIAN_PIDGIN } diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt index 42ffafbc2e3..94fb32a522a 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt @@ -22,14 +22,18 @@ import org.oppia.android.util.platformparameter.EnableContinueButtonAnimation import org.oppia.android.util.platformparameter.EnableDownloadsSupport import org.oppia.android.util.platformparameter.EnableEditAccountsOptionsUi import org.oppia.android.util.platformparameter.EnableExtraTopicTabsUi +import org.oppia.android.util.platformparameter.EnableFastLanguageSwitchingInLesson import org.oppia.android.util.platformparameter.EnableInteractionConfigChangeStateRetention import org.oppia.android.util.platformparameter.EnableLanguageSelectionUi import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.EnablePerformanceMetricsCollection import org.oppia.android.util.platformparameter.EnableSpotlightUi +import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSON import org.oppia.android.util.platformparameter.FORCED_APP_UPDATE_VERSION_CODE import org.oppia.android.util.platformparameter.ForcedAppUpdateVersionCode import org.oppia.android.util.platformparameter.LEARNER_STUDY_ANALYTICS +import org.oppia.android.util.platformparameter.LOGGING_LEARNER_STUDY_IDS import org.oppia.android.util.platformparameter.LOWEST_SUPPORTED_API_LEVEL import org.oppia.android.util.platformparameter.LOWEST_SUPPORTED_API_LEVEL_DEFAULT_VALUE import org.oppia.android.util.platformparameter.LowestSupportedApiLevel @@ -112,6 +116,26 @@ class PlatformParameterAlphaKenyaModule { ?: PlatformParameterValue.createDefaultParameter(true) } + @Provides + @EnableFastLanguageSwitchingInLesson + fun provideFastInLessonLanguageSwitching( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + // Turn on fast language switching functionality by default. + return platformParameterSingleton.getBooleanPlatformParameter(FAST_LANGUAGE_SWITCHING_IN_LESSON) + ?: PlatformParameterValue.createDefaultParameter(true) + } + + @Provides + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + // Turn on fast language switching functionality by default. + return platformParameterSingleton.getBooleanPlatformParameter(LOGGING_LEARNER_STUDY_IDS) + ?: PlatformParameterValue.createDefaultParameter(true) + } + @Provides @CacheLatexRendering fun provideCacheLatexRendering( diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt new file mode 100644 index 00000000000..29307b62b1c --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt @@ -0,0 +1,266 @@ +package org.oppia.android.domain.platformparameter + +import android.content.Context +import dagger.Module +import dagger.Provides +import org.oppia.android.app.utility.getVersionCode +import org.oppia.android.util.platformparameter.CACHE_LATEX_RENDERING +import org.oppia.android.util.platformparameter.CACHE_LATEX_RENDERING_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.CacheLatexRendering +import org.oppia.android.util.platformparameter.ENABLE_APP_AND_OS_DEPRECATION_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.ENABLE_CONTINUE_BUTTON_ANIMATION_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION +import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.EnableAppAndOsDeprecation +import org.oppia.android.util.platformparameter.EnableContinueButtonAnimation +import org.oppia.android.util.platformparameter.EnableDownloadsSupport +import org.oppia.android.util.platformparameter.EnableEditAccountsOptionsUi +import org.oppia.android.util.platformparameter.EnableExtraTopicTabsUi +import org.oppia.android.util.platformparameter.EnableFastLanguageSwitchingInLesson +import org.oppia.android.util.platformparameter.EnableInteractionConfigChangeStateRetention +import org.oppia.android.util.platformparameter.EnableLanguageSelectionUi +import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds +import org.oppia.android.util.platformparameter.EnablePerformanceMetricsCollection +import org.oppia.android.util.platformparameter.EnableSpotlightUi +import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSON +import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSON_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.FORCED_APP_UPDATE_VERSION_CODE +import org.oppia.android.util.platformparameter.ForcedAppUpdateVersionCode +import org.oppia.android.util.platformparameter.LEARNER_STUDY_ANALYTICS +import org.oppia.android.util.platformparameter.LOGGING_LEARNER_STUDY_IDS +import org.oppia.android.util.platformparameter.LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.LOWEST_SUPPORTED_API_LEVEL +import org.oppia.android.util.platformparameter.LOWEST_SUPPORTED_API_LEVEL_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.LowestSupportedApiLevel +import org.oppia.android.util.platformparameter.OPTIONAL_APP_UPDATE_VERSION_CODE +import org.oppia.android.util.platformparameter.OptionalAppUpdateVersionCode +import org.oppia.android.util.platformparameter.PERFORMANCE_METRICS_COLLECTION_HIGH_FREQUENCY_TIME_INTERVAL_IN_MINUTES +import org.oppia.android.util.platformparameter.PERFORMANCE_METRICS_COLLECTION_HIGH_FREQUENCY_TIME_INTERVAL_IN_MINUTES_DEFAULT_VAL +import org.oppia.android.util.platformparameter.PERFORMANCE_METRICS_COLLECTION_LOW_FREQUENCY_TIME_INTERVAL_IN_MINUTES +import org.oppia.android.util.platformparameter.PERFORMANCE_METRICS_COLLECTION_LOW_FREQUENCY_TIME_INTERVAL_IN_MINUTES_DEFAULT_VAL +import org.oppia.android.util.platformparameter.PERFORMANCE_METRICS_COLLECTION_UPLOAD_TIME_INTERVAL_IN_MINUTES +import org.oppia.android.util.platformparameter.PERFORMANCE_METRICS_COLLECTION_UPLOAD_TIME_INTERVAL_IN_MINUTES_DEFAULT_VAL +import org.oppia.android.util.platformparameter.PerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes +import org.oppia.android.util.platformparameter.PerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes +import org.oppia.android.util.platformparameter.PerformanceMetricsCollectionUploadTimeIntervalInMinutes +import org.oppia.android.util.platformparameter.PlatformParameterSingleton +import org.oppia.android.util.platformparameter.PlatformParameterValue +import org.oppia.android.util.platformparameter.SPLASH_SCREEN_WELCOME_MSG +import org.oppia.android.util.platformparameter.SPLASH_SCREEN_WELCOME_MSG_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS +import org.oppia.android.util.platformparameter.SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.SplashScreenWelcomeMsg +import org.oppia.android.util.platformparameter.SyncUpWorkerTimePeriodHours + +/** Dagger module that provides bindings for platform parameters for the alpha app builds. */ +@Module +class PlatformParameterAlphaModule { + @Provides + @EnableDownloadsSupport + fun provideEnableDownloadsSupport(): PlatformParameterValue = + PlatformParameterValue.createDefaultParameter(ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE) + + @Provides + @SplashScreenWelcomeMsg + fun provideSplashScreenWelcomeMsgParam( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(SPLASH_SCREEN_WELCOME_MSG) + ?: PlatformParameterValue.createDefaultParameter(SPLASH_SCREEN_WELCOME_MSG_DEFAULT_VALUE) + } + + @Provides + @SyncUpWorkerTimePeriodHours + fun provideSyncUpWorkerTimePeriod( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getIntegerPlatformParameter( + SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS + ) ?: PlatformParameterValue.createDefaultParameter( + SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS_DEFAULT_VALUE + ) + } + + @Provides + @EnableLanguageSelectionUi + fun provideEnableLanguageSelectionUi(): PlatformParameterValue { + return PlatformParameterValue.createDefaultParameter( + ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE + ) + } + + @Provides + @EnableEditAccountsOptionsUi + fun provideEnableEditAccountsOptionsUi(): PlatformParameterValue { + return PlatformParameterValue.createDefaultParameter( + ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE + ) + } + + @Provides + @EnableLearnerStudyAnalytics + fun provideLearnerStudyAnalytics( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(LEARNER_STUDY_ANALYTICS) + ?: PlatformParameterValue.createDefaultParameter(true) + } + + @Provides + @EnableFastLanguageSwitchingInLesson + fun provideFastInLessonLanguageSwitching( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(FAST_LANGUAGE_SWITCHING_IN_LESSON) + ?: PlatformParameterValue.createDefaultParameter( + FAST_LANGUAGE_SWITCHING_IN_LESSON_DEFAULT_VALUE + ) + } + + @Provides + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(LOGGING_LEARNER_STUDY_IDS) + ?: PlatformParameterValue.createDefaultParameter(LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE) + } + + @Provides + @CacheLatexRendering + fun provideCacheLatexRendering( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(CACHE_LATEX_RENDERING) + ?: PlatformParameterValue.createDefaultParameter(CACHE_LATEX_RENDERING_DEFAULT_VALUE) + } + + @Provides + @EnablePerformanceMetricsCollection + fun provideEnablePerformanceMetricCollection( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + ENABLE_PERFORMANCE_METRICS_COLLECTION + ) ?: PlatformParameterValue.createDefaultParameter( + ENABLE_PERFORMANCE_METRICS_COLLECTION_DEFAULT_VALUE + ) + } + + @Provides + @PerformanceMetricsCollectionUploadTimeIntervalInMinutes + fun providePerformanceMetricsCollectionUploadTimeIntervalInMinutes( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getIntegerPlatformParameter( + PERFORMANCE_METRICS_COLLECTION_UPLOAD_TIME_INTERVAL_IN_MINUTES + ) ?: PlatformParameterValue.createDefaultParameter( + PERFORMANCE_METRICS_COLLECTION_UPLOAD_TIME_INTERVAL_IN_MINUTES_DEFAULT_VAL + ) + } + + @Provides + @PerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes + fun providePerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getIntegerPlatformParameter( + PERFORMANCE_METRICS_COLLECTION_HIGH_FREQUENCY_TIME_INTERVAL_IN_MINUTES + ) ?: PlatformParameterValue.createDefaultParameter( + PERFORMANCE_METRICS_COLLECTION_HIGH_FREQUENCY_TIME_INTERVAL_IN_MINUTES_DEFAULT_VAL + ) + } + + @Provides + @PerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes + fun providePerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getIntegerPlatformParameter( + PERFORMANCE_METRICS_COLLECTION_LOW_FREQUENCY_TIME_INTERVAL_IN_MINUTES + ) ?: PlatformParameterValue.createDefaultParameter( + PERFORMANCE_METRICS_COLLECTION_LOW_FREQUENCY_TIME_INTERVAL_IN_MINUTES_DEFAULT_VAL + ) + } + + @Provides + @EnableSpotlightUi + fun provideEnableSpotlightUi(): PlatformParameterValue = + PlatformParameterValue.createDefaultParameter(true) // Enable spotlights for alpha users. + + @Provides + @EnableExtraTopicTabsUi + fun provideEnableExtraTopicTabsUi(): PlatformParameterValue { + return PlatformParameterValue.createDefaultParameter( + ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE + ) + } + + @Provides + @EnableInteractionConfigChangeStateRetention + fun provideEnableInteractionConfigChangeStateRetention(): PlatformParameterValue { + return PlatformParameterValue.createDefaultParameter( + ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE + ) + } + + @Provides + @EnableContinueButtonAnimation + fun provideEnableContinueButtonAnimation(): PlatformParameterValue { + return PlatformParameterValue.createDefaultParameter( + ENABLE_CONTINUE_BUTTON_ANIMATION_DEFAULT_VALUE + ) + } + + @Provides + @EnableAppAndOsDeprecation + fun provideEnableAppAndOsDeprecation(): PlatformParameterValue { + return PlatformParameterValue.createDefaultParameter( + ENABLE_APP_AND_OS_DEPRECATION_DEFAULT_VALUE + ) + } + + @Provides + @OptionalAppUpdateVersionCode + fun provideOptionalAppUpdateVersionCode( + platformParameterSingleton: PlatformParameterSingleton, + context: Context + ): PlatformParameterValue { + return platformParameterSingleton.getIntegerPlatformParameter( + OPTIONAL_APP_UPDATE_VERSION_CODE + ) ?: PlatformParameterValue.createDefaultParameter( + context.getVersionCode() + ) + } + + @Provides + @ForcedAppUpdateVersionCode + fun provideForcedAppUpdateVersionCode( + platformParameterSingleton: PlatformParameterSingleton, + context: Context + ): PlatformParameterValue { + return platformParameterSingleton.getIntegerPlatformParameter( + FORCED_APP_UPDATE_VERSION_CODE + ) ?: PlatformParameterValue.createDefaultParameter( + context.getVersionCode() + ) + } + + @Provides + @LowestSupportedApiLevel + fun provideLowestSupportedApiLevel( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getIntegerPlatformParameter( + LOWEST_SUPPORTED_API_LEVEL + ) ?: PlatformParameterValue.createDefaultParameter( + LOWEST_SUPPORTED_API_LEVEL_DEFAULT_VALUE + ) + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterModule.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterModule.kt index 4ce52320f39..28ff162e540 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterModule.kt @@ -22,15 +22,21 @@ import org.oppia.android.util.platformparameter.EnableContinueButtonAnimation import org.oppia.android.util.platformparameter.EnableDownloadsSupport import org.oppia.android.util.platformparameter.EnableEditAccountsOptionsUi import org.oppia.android.util.platformparameter.EnableExtraTopicTabsUi +import org.oppia.android.util.platformparameter.EnableFastLanguageSwitchingInLesson import org.oppia.android.util.platformparameter.EnableInteractionConfigChangeStateRetention import org.oppia.android.util.platformparameter.EnableLanguageSelectionUi import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.EnablePerformanceMetricsCollection import org.oppia.android.util.platformparameter.EnableSpotlightUi +import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSON +import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSON_DEFAULT_VALUE import org.oppia.android.util.platformparameter.FORCED_APP_UPDATE_VERSION_CODE import org.oppia.android.util.platformparameter.ForcedAppUpdateVersionCode import org.oppia.android.util.platformparameter.LEARNER_STUDY_ANALYTICS import org.oppia.android.util.platformparameter.LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.LOGGING_LEARNER_STUDY_IDS +import org.oppia.android.util.platformparameter.LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE import org.oppia.android.util.platformparameter.LOWEST_SUPPORTED_API_LEVEL import org.oppia.android.util.platformparameter.LOWEST_SUPPORTED_API_LEVEL_DEFAULT_VALUE import org.oppia.android.util.platformparameter.LowestSupportedApiLevel @@ -108,6 +114,26 @@ class PlatformParameterModule { ?: PlatformParameterValue.createDefaultParameter(LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE) } + @Provides + @EnableFastLanguageSwitchingInLesson + fun provideFastInLessonLanguageSwitching( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(FAST_LANGUAGE_SWITCHING_IN_LESSON) + ?: PlatformParameterValue.createDefaultParameter( + FAST_LANGUAGE_SWITCHING_IN_LESSON_DEFAULT_VALUE + ) + } + + @Provides + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(LOGGING_LEARNER_STUDY_IDS) + ?: PlatformParameterValue.createDefaultParameter(LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE) + } + @Provides @CacheLatexRendering fun provideCacheLatexRendering( diff --git a/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt b/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt index 54c82facee8..d9fa43ea0b7 100644 --- a/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt +++ b/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt @@ -29,6 +29,7 @@ import org.oppia.android.util.data.DataProviders.Companion.transform import org.oppia.android.util.data.DataProviders.Companion.transformAsync import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.PlatformParameterValue import org.oppia.android.util.profile.DirectoryManagementUtil import org.oppia.android.util.profile.ProfileNameValidator @@ -82,6 +83,8 @@ class ProfileManagementController @Inject constructor( private val learnerAnalyticsLogger: LearnerAnalyticsLogger, @EnableLearnerStudyAnalytics private val enableLearnerStudyAnalytics: PlatformParameterValue, + @EnableLoggingLearnerStudyIds + private val enableLoggingLearnerStudyIds: PlatformParameterValue, private val profileNameValidator: ProfileNameValidator ) { private var currentProfileId: Int = DEFAULT_LOGGED_OUT_INTERNAL_PROFILE_ID @@ -267,7 +270,7 @@ class ProfileManagementController @Inject constructor( audioLanguage = AudioLanguage.ENGLISH_AUDIO_LANGUAGE numberOfLogins = 0 - if (enableLearnerStudyAnalytics.value) { + if (enableLoggingLearnerStudyIds.value) { // Only set a learner ID if there's an ongoing user study. learnerId = loggingIdentifierController.createLearnerId() } @@ -599,7 +602,7 @@ class ProfileManagementController @Inject constructor( val updatedProfile = profile.toBuilder().apply { learnerId = when { // There should be no learner ID if no ongoing study. - !enableLearnerStudyAnalytics.value -> "" + !enableLoggingLearnerStudyIds.value -> "" learnerId.isEmpty() -> loggingIdentifierController.createLearnerId() // Generate new ID. else -> learnerId // Keep it unchanged. } diff --git a/domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt b/domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt index dd486f19f6a..a916d935ac4 100644 --- a/domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt +++ b/domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt @@ -36,7 +36,6 @@ import kotlin.concurrent.withLock private const val SYSTEM_LANGUAGE_LOCALE_DATA_PROVIDER_ID = "system_language_locale" private const val APP_LANGUAGE_DATA_PROVIDER_ID = "app_language" private const val APP_LANGUAGE_LOCALE_DATA_PROVIDER_ID = "app_language_locale" -private const val APP_LANGUAGE_SELECTION_DATA_PROVIDER_ID = "app_language_selection" private const val UPDATE_APP_LANGUAGE_DATA_PROVIDER_ID = "update_app_language" private const val WRITTEN_TRANSLATION_CONTENT_DATA_PROVIDER_ID = "written_translation_content" private const val WRITTEN_TRANSLATION_CONTENT_LOCALE_DATA_PROVIDER_ID = diff --git a/domain/src/test/java/org/oppia/android/domain/audio/AudioPlayerControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/audio/AudioPlayerControllerTest.kt index 655ba70bd3a..20e5315db8e 100644 --- a/domain/src/test/java/org/oppia/android/domain/audio/AudioPlayerControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/audio/AudioPlayerControllerTest.kt @@ -71,6 +71,7 @@ import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.PlatformParameterValue import org.robolectric.Shadows import org.robolectric.annotation.Config @@ -843,6 +844,17 @@ class AudioPlayerControllerTest { override val value: Boolean = enableFeature } } + + @Provides + @Singleton + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds(): PlatformParameterValue { + // Snapshot the value so that it doesn't change between injection and use. + val enableFeature = enableLearnerStudyAnalytics + return object : PlatformParameterValue { + override val value: Boolean = enableFeature + } + } } // TODO(#89): Move this to a common test application component. diff --git a/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt index 4b4b4d0ae4e..f77ec52419c 100644 --- a/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt @@ -108,6 +108,7 @@ 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.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.PlatformParameterValue import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode @@ -3155,6 +3156,13 @@ class ExplorationProgressControllerTest { // Enable the study by default in tests. return PlatformParameterValue.createDefaultParameter(defaultValue = true) } + + @Provides + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds(): PlatformParameterValue { + // Enable study IDs by default in tests. + return PlatformParameterValue.createDefaultParameter(defaultValue = true) + } } // TODO(#89): Move this to a common test application component. diff --git a/domain/src/test/java/org/oppia/android/domain/locale/LanguageConfigRetrieverProductionTest.kt b/domain/src/test/java/org/oppia/android/domain/locale/LanguageConfigRetrieverProductionTest.kt index fd2d49a9e45..5f2e49fab4b 100644 --- a/domain/src/test/java/org/oppia/android/domain/locale/LanguageConfigRetrieverProductionTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/locale/LanguageConfigRetrieverProductionTest.kt @@ -55,21 +55,29 @@ class LanguageConfigRetrieverProductionTest { } @Test - fun testLoadSupportedLanguages_hasThreeSupportedLanguages() { + fun testLoadSupportedLanguages_hasFiveSupportedLanguages() { val supportedLanguages = languageConfigRetriever.loadSupportedLanguages() // Change detector test to ensure changes to the configuration are reflected in tests since // changes to the configuration can have a major impact on the app (and may require additional // work to be done to support the changes). - assertThat(supportedLanguages.languageDefinitionsCount).isEqualTo(3) + assertThat(supportedLanguages.languageDefinitionsCount).isEqualTo(5) } @Test - fun testLoadSupportedLanguages_arabic_isNotSupported() { + fun testLoadSupportedLanguages_arabic_isSupportedForAppContentAudioTranslations() { val supportedLanguages = languageConfigRetriever.loadSupportedLanguages() - val allLanguages = supportedLanguages.languageDefinitionsList.map { it.language } - assertThat(allLanguages).doesNotContain(OppiaLanguage.ARABIC) + val definition = supportedLanguages.lookUpLanguage(OppiaLanguage.ARABIC) + assertThat(definition.hasAppStringId()).isTrue() + assertThat(definition.hasContentStringId()).isTrue() + assertThat(definition.hasAudioTranslationId()).isTrue() + assertThat(definition.fallbackMacroLanguage).isEqualTo(OppiaLanguage.LANGUAGE_UNSPECIFIED) + assertThat(definition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("ar") + assertThat(definition.appStringId.androidResourcesLanguageId.languageCode).isEqualTo("ar") + assertThat(definition.appStringId.androidResourcesLanguageId.regionCode).isEmpty() + assertThat(definition.contentStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("ar") + assertThat(definition.audioTranslationId.ietfBcp47Id.ietfLanguageTag).isEqualTo("ar") } @Test @@ -132,13 +140,29 @@ class LanguageConfigRetrieverProductionTest { } @Test - fun testLoadSupportedLangs_swahili_isNotSupported() { + fun testLoadSupportedLanguages_swahili_isNotSupported() { val supportedLanguages = languageConfigRetriever.loadSupportedLanguages() val allLanguages = supportedLanguages.languageDefinitionsList.map { it.language } assertThat(allLanguages).doesNotContain(OppiaLanguage.SWAHILI) } + @Test + fun testLoadSupportedLangs_nigerianPidgin_isSupportedForAppContentAudioTranslations() { + val supportedLanguages = languageConfigRetriever.loadSupportedLanguages() + + val definition = supportedLanguages.lookUpLanguage(OppiaLanguage.NIGERIAN_PIDGIN) + assertThat(definition.hasAppStringId()).isTrue() + assertThat(definition.hasContentStringId()).isTrue() + assertThat(definition.hasAudioTranslationId()).isTrue() + assertThat(definition.fallbackMacroLanguage).isEqualTo(OppiaLanguage.ENGLISH) + assertThat(definition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("pcm") + assertThat(definition.appStringId.androidResourcesLanguageId.languageCode).isEqualTo("pcm") + assertThat(definition.appStringId.androidResourcesLanguageId.regionCode).isEqualTo("NG") + assertThat(definition.contentStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("pcm") + assertThat(definition.audioTranslationId.ietfBcp47Id.ietfLanguageTag).isEqualTo("pcm") + } + @Test fun testLoadSupportedRegions_loadsNonDefaultProtoFromAssets() { val supportedRegions = languageConfigRetriever.loadSupportedRegions() @@ -147,13 +171,13 @@ class LanguageConfigRetrieverProductionTest { } @Test - fun testLoadSupportedRegions_hasThreeSupportedRegions() { + fun testLoadSupportedRegions_hasFourSupportedRegions() { val supportedRegions = languageConfigRetriever.loadSupportedRegions() // Change detector test to ensure changes to the configuration are reflected in tests since // changes to the configuration can have a major impact on the app (and may require additional // work to be done to support the changes). - assertThat(supportedRegions.regionDefinitionsCount).isEqualTo(3) + assertThat(supportedRegions.regionDefinitionsCount).isEqualTo(4) } @Test @@ -189,7 +213,18 @@ class LanguageConfigRetrieverProductionTest { val definition = supportedRegions.lookUpRegion(OppiaRegion.KENYA) assertThat(definition.regionId.ietfRegionTag).isEqualTo("KE") - assertThat(definition.languagesList).containsExactly(OppiaLanguage.ENGLISH) + assertThat(definition.languagesList) + .containsExactly(OppiaLanguage.ENGLISH, OppiaLanguage.SWAHILI) + } + + @Test + fun testLoadSupportedRegions_nigerianPidgin_hasCorrectRegionIdAndSupportedLanguages() { + val supportedRegions = languageConfigRetriever.loadSupportedRegions() + + val definition = supportedRegions.lookUpRegion(OppiaRegion.NIGERIA) + assertThat(definition.regionId.ietfRegionTag).isEqualTo("NG") + assertThat(definition.languagesList) + .containsExactly(OppiaLanguage.ENGLISH, OppiaLanguage.NIGERIAN_PIDGIN) } private fun SupportedLanguages.lookUpLanguage( diff --git a/domain/src/test/java/org/oppia/android/domain/locale/LanguageConfigRetrieverTest.kt b/domain/src/test/java/org/oppia/android/domain/locale/LanguageConfigRetrieverTest.kt index cbfb8957e4b..aeae202724f 100644 --- a/domain/src/test/java/org/oppia/android/domain/locale/LanguageConfigRetrieverTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/locale/LanguageConfigRetrieverTest.kt @@ -49,21 +49,22 @@ class LanguageConfigRetrieverTest { } @Test - fun testOppiaLanguage_hasSupportForSevenLanguages() { + fun testOppiaLanguage_hasSupportForEightLanguages() { // While it's a bit strange to test a proto, and particularly in this file, this suite is // generally responsible for verifying language & region configuration sanity. Part of that - // requires verifying that all languages are tested below. Note that '9' is because the base - // 7 languages are supported + LANGUAGE_UNSPECIFIED and UNRECOGNIZED (auto-generated by - // Protobuf). Finally, note that the values themselves are not checked since it doesn't provide - // any benefit (being able to reference an enum constant without a compiler error is sufficient - // proof that constant is available). - assertThat(OppiaLanguage.values()).hasLength(9) + // requires verifying that all languages are tested below. Note that number test below is + // two higher than the number of supported languages because the base languages are supported + + // LANGUAGE_UNSPECIFIED and UNRECOGNIZED (auto-generated by Protobuf). Finally, note that the + // values themselves are not checked since it doesn't provide any benefit (being able to + // reference an enum constant without a compiler error is sufficient proof that constant is + // available and unique). + assertThat(OppiaLanguage.values()).hasLength(10) } @Test - fun testOppiaRegion_hasSupportForFourRegions() { + fun testOppiaRegion_hasSupportForFiveRegions() { // See above test for context on why this test is here & for why the number is correct. - assertThat(OppiaRegion.values()).hasLength(6) + assertThat(OppiaRegion.values()).hasLength(7) } @Test @@ -74,13 +75,13 @@ class LanguageConfigRetrieverTest { } @Test - fun testLoadSupportedLanguages_hasSevenSupportedLanguages() { + fun testLoadSupportedLanguages_hasEightSupportedLanguages() { val supportedLanguages = languageConfigRetriever.loadSupportedLanguages() // Change detector test to ensure changes to the configuration are reflected in tests since // changes to the configuration can have a major impact on the app (and may require additional // work to be done to support the changes). - assertThat(supportedLanguages.languageDefinitionsCount).isEqualTo(7) + assertThat(supportedLanguages.languageDefinitionsCount).isEqualTo(8) } @Test @@ -187,6 +188,22 @@ class LanguageConfigRetrieverTest { assertThat(definition.audioTranslationId.ietfBcp47Id.ietfLanguageTag).isEqualTo("sw") } + @Test + fun testLoadSupportedLangs_nigerianPidgin_isSupportedForAppContentAudioTranslations() { + val supportedLanguages = languageConfigRetriever.loadSupportedLanguages() + + val definition = supportedLanguages.lookUpLanguage(OppiaLanguage.NIGERIAN_PIDGIN) + assertThat(definition.hasAppStringId()).isTrue() + assertThat(definition.hasContentStringId()).isTrue() + assertThat(definition.hasAudioTranslationId()).isTrue() + assertThat(definition.fallbackMacroLanguage).isEqualTo(OppiaLanguage.ENGLISH) + assertThat(definition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("pcm") + assertThat(definition.appStringId.androidResourcesLanguageId.languageCode).isEqualTo("pcm") + assertThat(definition.appStringId.androidResourcesLanguageId.regionCode).isEqualTo("NG") + assertThat(definition.contentStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("pcm") + assertThat(definition.audioTranslationId.ietfBcp47Id.ietfLanguageTag).isEqualTo("pcm") + } + @Test fun testLoadSupportedRegions_loadsNonDefaultProtoFromAssets() { val supportedRegions = languageConfigRetriever.loadSupportedRegions() @@ -195,13 +212,13 @@ class LanguageConfigRetrieverTest { } @Test - fun testLoadSupportedRegions_hasFourSupportedRegions() { + fun testLoadSupportedRegions_hasFiveSupportedRegions() { val supportedRegions = languageConfigRetriever.loadSupportedRegions() // Change detector test to ensure changes to the configuration are reflected in tests since // changes to the configuration can have a major impact on the app (and may require additional // work to be done to support the changes). - assertThat(supportedRegions.regionDefinitionsCount).isEqualTo(4) + assertThat(supportedRegions.regionDefinitionsCount).isEqualTo(5) } @Test @@ -243,6 +260,16 @@ class LanguageConfigRetrieverTest { .containsExactly(OppiaLanguage.ENGLISH, OppiaLanguage.SWAHILI) } + @Test + fun testLoadSupportedRegions_nigerianPidgin_hasCorrectRegionIdAndSupportedLanguages() { + val supportedRegions = languageConfigRetriever.loadSupportedRegions() + + val definition = supportedRegions.lookUpRegion(OppiaRegion.NIGERIA) + assertThat(definition.regionId.ietfRegionTag).isEqualTo("NG") + assertThat(definition.languagesList) + .containsExactly(OppiaLanguage.ENGLISH, OppiaLanguage.NIGERIAN_PIDGIN) + } + private fun SupportedLanguages.lookUpLanguage( language: OppiaLanguage ): LanguageSupportDefinition { diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserverTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserverTest.kt index 0d5d660b15e..2d7db431fcf 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserverTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserverTest.kt @@ -396,7 +396,7 @@ class ApplicationLifecycleObserverTest { } private fun setUpTestApplicationWithLearnerStudy() { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(true) + TestPlatformParameterModule.forceEnableLoggingLearnerStudyIds(true) setUpTestApplicationComponent() } diff --git a/domain/src/test/java/org/oppia/android/domain/profile/ProfileManagementControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/profile/ProfileManagementControllerTest.kt index da02dd711db..88cead207a8 100644 --- a/domain/src/test/java/org/oppia/android/domain/profile/ProfileManagementControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/profile/ProfileManagementControllerTest.kt @@ -52,6 +52,7 @@ 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.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE import org.oppia.android.util.platformparameter.PlatformParameterValue import org.oppia.android.util.threading.BackgroundDispatcher @@ -1282,6 +1283,17 @@ class ProfileManagementControllerTest { override val value: Boolean = enableFeature } } + + @Provides + @Singleton + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds(): PlatformParameterValue { + // Snapshot the value so that it doesn't change between injection and use. + val enableFeature = enableLearnerStudyAnalytics + return object : PlatformParameterValue { + override val value: Boolean = enableFeature + } + } } @Module diff --git a/domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt index 6f66a236376..195a53887db 100644 --- a/domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt @@ -25,6 +25,7 @@ import org.oppia.android.app.model.OppiaLanguage.BRAZILIAN_PORTUGUESE import org.oppia.android.app.model.OppiaLanguage.ENGLISH import org.oppia.android.app.model.OppiaLanguage.HINDI import org.oppia.android.app.model.OppiaLanguage.LANGUAGE_UNSPECIFIED +import org.oppia.android.app.model.OppiaLanguage.NIGERIAN_PIDGIN import org.oppia.android.app.model.OppiaLanguage.PORTUGUESE import org.oppia.android.app.model.OppiaLanguage.SWAHILI import org.oppia.android.app.model.OppiaLocaleContext.LanguageUsageMode.APP_STRINGS @@ -1794,9 +1795,10 @@ class TranslationControllerTest { val languageListProvider = translationController.getSupportedAppLanguages() val languageListData = monitorFactory.waitForNextSuccessfulResult(languageListProvider) - assertThat(languageListData[0].name).isEqualTo(ARABIC.name) - assertThat(languageListData[4].name).isEqualTo(SWAHILI.name) - assertThat(languageListData).hasSize(5) + // All developer languages should be available. This is a change detector test to ensure that + // the language selection system provides exactly the list of intended languages. + assertThat(languageListData) + .containsExactly(ARABIC, ENGLISH, HINDI, BRAZILIAN_PORTUGUESE, SWAHILI, NIGERIAN_PIDGIN) } private fun setUpTestApplicationComponent() { diff --git a/model/src/main/proto/BUILD.bazel b/model/src/main/proto/BUILD.bazel index 56cdecb31bb..cc7e9fa399c 100644 --- a/model/src/main/proto/BUILD.bazel +++ b/model/src/main/proto/BUILD.bazel @@ -57,6 +57,12 @@ java_lite_proto_library( deps = [":event_logger_proto"], ) +java_proto_library( + name = "event_logger_java_proto", + visibility = ["//scripts:oppia_script_library_visibility"], + deps = [":event_logger_proto"], +) + oppia_proto_library( name = "performance_metrics_event_logger_proto", srcs = ["performance_metrics.proto"], diff --git a/model/src/main/proto/languages.proto b/model/src/main/proto/languages.proto index ff2fcb27a26..92522feea0f 100644 --- a/model/src/main/proto/languages.proto +++ b/model/src/main/proto/languages.proto @@ -30,6 +30,11 @@ enum OppiaLanguage { // Corresponds to the Swahili (Kiswahili) macro language. IETF BCP 47 language tag: sw. SWAHILI = 7; + + // Corresponds to the Nigerian English-based creole (Naijá) language (a pidgin language). + // IETF BCP 47 language tag: pcm (as of RFC 5646 since pcm was introduced in ISO 639-3). However, + // since Android doesn't support pcm natively the app uses pcm-NG for app string translations. + NIGERIAN_PIDGIN = 8; } // The list of regions explicitly supported natively by the Android app. Note that the app is not @@ -57,6 +62,10 @@ enum OppiaRegion { // Corresponds to Kenya (Jamhuri ya Kenya). IETF BCP 47 region tag: KE. KENYA = 4; + + // Corresponds to Nigeria (Jamhuriyar Tarayyar Najeriya / Ọ̀hàńjíkọ̀ Ọ̀hànézè Naìjíríyà / Orílẹ̀-èdè + // Olómìniira Àpapọ̀ Nàìjíríà). IETF BCP 47 region tag: NG (per ISO 3166). + NIGERIA = 5; } // Defines the list of supported languages in the app. @@ -142,17 +151,27 @@ message LanguageSupportDefinition { // details on how IETF language tags are formed: https://www.unfoldingword.org/ietf. Current tag // registry: http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry. // Note that the list above will contain languages not supported on all Android platforms. + // + // Note that this ID type might also be used to represent IETF BCP 47-recognized creole languages + // (which are those that derive a languages into a simpler and mixed form, often a pidgin, e.g. + // Nigerian Pidgin which is derived largely from English). See: + // https://en.wikipedia.org/wiki/Creole_language. + // + // Note that these largely differs from macaronic languages in that rather than combining two + // separate languages into a mixed language, these are primarily derived from a single language + // and often highly localized to a particular region (even though there may be multiple creole + // varieties in near proximity). message IetfBcp47LanguageId { // The language tag according to the IETF BCP 47 standard. string ietf_language_tag = 1; } // An identifier representation for macaronic languages (which are languages which combine two - // others, e.g.: Hinglish). + // others, e.g.: Hinglish). See: https://en.wikipedia.org/wiki/Macaronic_language. message MacaronicLanguageId { // The combined language code for this macaronic language (e.g. 'hi-en' for Hinglish). Note that // the constituent parts of the language may not necessarily correspond to ISO 639-1 language - // codes. It's also expected that order matters here: hi-en and en_hi would not correspond to + // codes. It's also expected that order matters here: hi-en and en-hi would not correspond to // the same macaronic language. string combined_language_code = 1; } diff --git a/model/src/main/proto/profile.proto b/model/src/main/proto/profile.proto index ea839969b7b..a7aa4f72ff9 100644 --- a/model/src/main/proto/profile.proto +++ b/model/src/main/proto/profile.proto @@ -135,4 +135,6 @@ enum AudioLanguage { FRENCH_AUDIO_LANGUAGE = 4; CHINESE_AUDIO_LANGUAGE = 5; BRAZILIAN_PORTUGUESE_LANGUAGE = 6; + ARABIC_LANGUAGE = 7; + NIGERIAN_PIDGIN_LANGUAGE = 8; } diff --git a/oppia_android_application.bzl b/oppia_android_application.bzl index c1539e28e7e..e941becc4a4 100644 --- a/oppia_android_application.bzl +++ b/oppia_android_application.bzl @@ -38,7 +38,7 @@ def _convert_module_aab_to_structured_zip_impl(ctx): command = """ # Extract AAB to working directory. WORKING_DIR=$(mktemp -d) - unzip -d $WORKING_DIR {0} + unzip -q -d $WORKING_DIR {0} # Create the expected directory structure for an app bundle. # Reference for copying all other files to root: https://askubuntu.com/a/951768. @@ -52,7 +52,7 @@ def _convert_module_aab_to_structured_zip_impl(ctx): # passed via arguments (necessitating changing into the working directory). DEST_FILE_PATH="$(pwd)/{1}" cd $WORKING_DIR - zip -r $DEST_FILE_PATH . + zip -q -r $DEST_FILE_PATH . """.format(input_file.path, output_file.path) # Reference: https://docs.bazel.build/versions/main/skylark/lib/actions.html#run_shell. @@ -140,7 +140,7 @@ def _package_metadata_into_deployable_aab_impl(ctx): $ Repackage the AAB file into the destination. DEST_FILE_PATH="$(pwd)/{2}" cd $WORKING_DIR - zip -Dur temp.aab BUNDLE-METADATA || exit 255 + zip -q -Dur temp.aab BUNDLE-METADATA || exit 255 cp temp.aab $DEST_FILE_PATH || exit 255 """.format(input_aab_file.path, proguard_map_file.path, output_aab_file.path) diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel index 50a0cbf452f..01390e600e7 100644 --- a/scripts/BUILD.bazel +++ b/scripts/BUILD.bazel @@ -251,3 +251,10 @@ java_binary( "//scripts/src/java/org/oppia/android/scripts/build:filter_per_language_resources_lib", ], ) + +java_binary( + name = "decode_user_study_event_string", + testonly = True, + main_class = "org.oppia.android.scripts.telemetry.DecodeUserStudyEventStringKt", + runtime_deps = ["//scripts/src/java/org/oppia/android/scripts/telemetry:decode_user_study_event_string_lib"], +) diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index 55895bbdfc2..7e69c30f7ce 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -677,6 +677,7 @@ exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/l exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerFactory.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModule.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt" +exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterSingletonImpl.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterSingletonModule.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerFactory.kt" @@ -713,6 +714,7 @@ exempted_file_path: "scripts/src/java/org/oppia/android/scripts/license/model/De exempted_file_path: "scripts/src/java/org/oppia/android/scripts/maven/model/MavenListDependency.kt" exempted_file_path: "scripts/src/java/org/oppia/android/scripts/maven/model/MavenListDependencies.kt" exempted_file_path: "scripts/src/java/org/oppia/android/scripts/maven/model/MavenListDependencyTree.kt" +exempted_file_path: "scripts/src/java/org/oppia/android/scripts/telemetry/DecodeUserStudyEventString.kt" exempted_file_path: "scripts/src/java/org/oppia/android/scripts/todo/model/Issue.kt" exempted_file_path: "scripts/src/java/org/oppia/android/scripts/todo/model/Todo.kt" exempted_file_path: "testing/src/main/java/org/oppia/android/testing/AccessibilityTestRule.kt" diff --git a/scripts/src/java/org/oppia/android/scripts/build/FilterPerLanguageResources.kt b/scripts/src/java/org/oppia/android/scripts/build/FilterPerLanguageResources.kt index 5da2e00c56b..96cdcef2282 100644 --- a/scripts/src/java/org/oppia/android/scripts/build/FilterPerLanguageResources.kt +++ b/scripts/src/java/org/oppia/android/scripts/build/FilterPerLanguageResources.kt @@ -78,9 +78,9 @@ private class FilterPerLanguageResources { val removedLanguageCodes = allReferencedLanguageCodes - supportedLanguageCodes val updatedResourceTable = resourceTable.recompute(supportedLanguageCodes) println( - "${removedLanguageCodes.size} resources are being removed that are tied to unsupported" + - " languages: $removedLanguageCodes (size reduction:" + - " ${resourceTable.serializedSize - updatedResourceTable.serializedSize} bytes)." + "${resourceTable.countResources() - updatedResourceTable.countResources()} resources are" + + " being removed that are tied to unsupported languages: $removedLanguageCodes (size" + + " reduction: ${resourceTable.serializedSize - updatedResourceTable.serializedSize} bytes)." ) ZipOutputStream(outputModuleZip.outputStream()).use { outputStream -> @@ -95,6 +95,8 @@ private class FilterPerLanguageResources { } } + private fun ResourceTable.countResources(): Int = packageList.sumOf { it.countResources() } + private fun ResourceTable.recompute(allowedLanguageCodes: Set): ResourceTable { val updatedPackages = packageList.mapNotNull { it.recompute(allowedLanguageCodes) } return toBuilder().apply { @@ -103,6 +105,8 @@ private class FilterPerLanguageResources { }.build() } + private fun Package.countResources(): Int = typeList.sumOf { it.countResources() } + private fun Package.recompute(allowedLanguageCodes: Set): Package? { val updatedTypes = typeList.mapNotNull { it.recompute(allowedLanguageCodes) } return if (updatedTypes.isNotEmpty()) { @@ -113,6 +117,8 @@ private class FilterPerLanguageResources { } else null } + private fun Type.countResources(): Int = entryList.sumOf { it.configValueCount } + private fun Type.recompute(allowedLanguageCodes: Set): Type? { val updatedEntries = entryList.mapNotNull { it.recompute(allowedLanguageCodes) } return if (updatedEntries.isNotEmpty()) { diff --git a/scripts/src/java/org/oppia/android/scripts/telemetry/BUILD.bazel b/scripts/src/java/org/oppia/android/scripts/telemetry/BUILD.bazel new file mode 100644 index 00000000000..7afddfa12db --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/telemetry/BUILD.bazel @@ -0,0 +1,23 @@ +""" +Libraries corresponding to telemetry scripts, including tools for locally analyzing event data. +""" + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") + +kt_jvm_library( + name = "decode_user_study_event_string_lib", + testonly = True, + srcs = ["DecodeUserStudyEventString.kt"], + visibility = ["//scripts:oppia_script_binary_visibility"], + runtime_deps = [ + "//third_party:org_eclipse_parsson_parsson", + "//third_party:org_snakeyaml_snakeyaml-engine", + ], + deps = [ + "//model/src/main/proto:event_logger_java_proto", + "//third_party:com_google_protobuf_protobuf-java", + "//third_party:com_google_protobuf_protobuf-java-util", + "//third_party:io_xlate_yaml-json", + "//third_party:jakarta_json_jakarta_json-api", + ], +) diff --git a/scripts/src/java/org/oppia/android/scripts/telemetry/DecodeUserStudyEventString.kt b/scripts/src/java/org/oppia/android/scripts/telemetry/DecodeUserStudyEventString.kt new file mode 100644 index 00000000000..479b38baff8 --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/telemetry/DecodeUserStudyEventString.kt @@ -0,0 +1,217 @@ +package org.oppia.android.scripts.telemetry + +import com.google.protobuf.Message +import com.google.protobuf.TextFormat +import com.google.protobuf.util.JsonFormat +import io.xlate.yamljson.Yaml +import jakarta.json.Json +import jakarta.json.JsonReader +import org.oppia.android.app.model.OppiaEventLogs +import java.io.File +import java.io.InputStream +import java.io.StringReader +import java.io.StringWriter +import java.util.Base64 +import java.util.zip.GZIPInputStream + +/** + * Script for decoding the Base64 events string that can be shared via the learner study analytics + * screen (available via the app's administrator controls panel). + * + * Usage: + * bazel run //scripts:decode_user_study_event_string -- + * + * + * Arguments: + * - decode_user_study_event_string: absolute path to a file containing the single Base64 string to + * decode. Note that whitespace is automatically removed upon import so reformatting isn't + * necessary. + * - path_to_output_file: absolute path to the output file that will contain the decoded event logs. + * The extension of this file is used to determine which export format to use. All supported + * formats are: YAML (*.yml or *.yaml), JSON (*.json), and text Protobuf (*.textproto). + * + * Example: + * bazel run //scripts:decode_user_study_event_string -- $(pwd)/input.log $(pwd)/output.json + */ +fun main(vararg args: String) { + require(args.size == 2) { + "Use: bazel run //scripts:decode_user_study_event_string --" + + " " + } + val (base64Path, outputPath) = args + val inputFile = File(base64Path).absoluteFile.normalize().also { + require(it.exists() && it.isFile) { + "Expected input base 64 path to correspond to an existing file: $base64Path." + } + } + val outputFile = File(outputPath).absoluteFile.normalize().also { + require(!it.exists()) { "Error: output file already exists: $outputPath." } + } + val outputFormat = when (outputFile.extension) { + "textproto" -> DecodeUserStudyEventString.OutputFormat.TEXT_PROTO + "json" -> DecodeUserStudyEventString.OutputFormat.JSON + "yaml", "yml" -> DecodeUserStudyEventString.OutputFormat.YAML + else -> error("Unsupported extension in: $outputPath (expected one of: textproto/json/yaml).") + } + DecodeUserStudyEventString().decodeEventString(inputFile, outputFile, outputFormat) +} + +/** Utility for decoding compressed Base64 encodings of an [OppiaEventLogs] instance. */ +class DecodeUserStudyEventString { + /** + * Decodes a compressed Base64-encoded and outputs it in a specified format. + * + * @param inputFile the file containing the Base64 string to decode + * @param outputFile the file that should contain the output decoded event logs + * @param outputFormat the [OutputFormat] to use to encode [outputFile] + */ + fun decodeEventString(inputFile: File, outputFile: File, outputFormat: OutputFormat) { + println("Reading input: ${inputFile.path}.") + println("Writing format $outputFormat to: ${outputFile.path}.") + + val oppiaEventLogs = + inputFile.inputStream().use { it.fromCompressedBase64(OppiaEventLogs.getDefaultInstance()) } + + println( + "Decoded ${oppiaEventLogs.uploadedEventLogsCount} uploaded events, and" + + " ${oppiaEventLogs.eventLogsToUploadCount} pending events." + ) + + val convertedText = when (outputFormat) { + OutputFormat.TEXT_PROTO -> oppiaEventLogs.convertToText() + OutputFormat.JSON -> oppiaEventLogs.convertToJson() + OutputFormat.YAML -> oppiaEventLogs.convertToYaml() + } + + outputFile.writeText(convertedText) + } + + /** Encoding format that may be used when representing a decoded version of [OppiaEventLogs]. */ + enum class OutputFormat { + /** Corresponds text-based protos: https://protobuf.dev/reference/protobuf/textformat-spec/. */ + TEXT_PROTO, + + /** Corresponds to JSON: https://www.json.org/json-en.html. */ + JSON, + + /** Corresponds to YAML: https://yaml.org/. */ + YAML + } + + private companion object { + private const val CARRIAGE_RETURN = '\r'.toInt() + private const val NEW_LINE = '\n'.toInt() + private const val SPACE = ' '.toInt() + private val base64Decoder by lazy { Base64.getDecoder() } + + private inline fun InputStream.fromCompressedBase64(baseMessage: M): M { + println("[1/5] Reading file...") + val rawData = readBytes() + + println("[2/5] Stripping whitespace...") + val stripped = rawData.tryTransform(::WhitespaceStrippingInputStream) + + println("[3/5] Decoding Base64...") + val decoded = stripped.tryTransform(base64Decoder::wrap) + + println("[4/5] Decompressing using GZIP...") + val inflated = decoded.tryTransform(::GZIPInputStream) + + println("[5/5] Reading binary proto...") + return baseMessage.newBuilderForType().also { + try { + it.mergeFrom(inflated) + } catch (e: Exception) { + println("Failed to deflate all data in the protocol buffer.") + e.printStackTrace(System.out) + } + }.build() as M + } + + private fun ByteArray.tryTransform(inputFactory: (InputStream) -> InputStream): ByteArray { + val byteStream = inputStream() + return inputFactory(byteStream).use { it.recoverAsManyBytesAsPossible() }.also { + if (it.exception != null) { + val byteCount = size - byteStream.available() + println( + "Encountered failure during stage: $byteCount/$size bytes were read, producing" + + " ${it.data.size} bytes for the next stage." + ) + it.exception.printStackTrace(System.out) + println() + } + }.data + } + + private fun InputStream.recoverAsManyBytesAsPossible(): RecoveryResult { + val bytes = mutableListOf() + var nextByte: Int + do { + nextByte = when (val latestRead = tryRead()) { + is ReadResult.HasByte -> latestRead.value + is ReadResult.HasFailure -> + return RecoveryResult(bytes.toByteArray(), latestRead.exception) + } + if (nextByte != -1) bytes += nextByte.toByte() + } while (nextByte != -1) + return RecoveryResult(bytes.toByteArray(), exception = null) + } + + private fun InputStream.tryRead(): ReadResult = + try { ReadResult.HasByte(read()) } catch (e: Exception) { ReadResult.HasFailure(e) } + + private fun Message.convertToText(): String = + TextFormat.printer().escapingNonAscii(false).printToString(this) + + private fun Message.convertToJson(): String = + JsonFormat.printer().includingDefaultValueFields().print(this) + + private fun Message.convertToYaml(): String { + // There's no direct way to convert from proto to yaml, so convert to json first. + val structure = Json.createReader(StringReader(convertToJson())).use(JsonReader::read) + return StringWriter().also { writer -> + Yaml.createWriter(writer).use { it.write(structure) } + }.toString() + } + + private class WhitespaceStrippingInputStream(private val base: InputStream) : InputStream() { + override fun read(): Int { + // Remove newlines, carriage returns, and spaces. + return when (val value = base.read()) { + -1 -> value // The stream has ended. + CARRIAGE_RETURN, NEW_LINE, SPACE -> read() // Skip the byte. + else -> value // Otherwise, pass along the value. + } + } + + override fun close() = base.close() + } + + /** + * The result of attempting to decode/translate data. + * + * @property data the resulting data (which should contain as much sequential data that could be + * recovered as was possible) + * @property exception the failure which resulted in no more data being collected, or ``null`` + * if the transfer succeeded without data loss + */ + private class RecoveryResult(val data: ByteArray, val exception: Exception?) + + /** The result of trying to read a single byte from an [InputStream]. */ + private sealed class ReadResult { + /** + * A [ReadResult] that indicates the read was successful. + * + * @property value the single byte value that was successfully read + */ + data class HasByte(val value: Int) : ReadResult() + + /** + * A [ReadResult] that indicates the read was a failure. + * + * @property exception the [Exception] that was encountered when trying to read a byte + */ + data class HasFailure(val exception: Exception) : ReadResult() + } + } +} diff --git a/scripts/src/java/org/oppia/android/scripts/xml/StringResourceParser.kt b/scripts/src/java/org/oppia/android/scripts/xml/StringResourceParser.kt index 955138b337b..2f35352b2e8 100644 --- a/scripts/src/java/org/oppia/android/scripts/xml/StringResourceParser.kt +++ b/scripts/src/java/org/oppia/android/scripts/xml/StringResourceParser.kt @@ -79,7 +79,10 @@ class StringResourceParser(private val repoRoot: File) { ENGLISH(valuesDirectoryName = "values"), /** Corresponds to Swahili (sw) translations. */ - SWAHILI(valuesDirectoryName = "values-sw"); + SWAHILI(valuesDirectoryName = "values-sw"), + + /** Corresponds to Nigerian Pidgin (pcm) translations. */ + NIGERIAN_PIDGIN(valuesDirectoryName = "values-pcm-rNG") } /** diff --git a/scripts/src/javatests/org/oppia/android/scripts/build/FilterPerLanguageResourcesTest.kt b/scripts/src/javatests/org/oppia/android/scripts/build/FilterPerLanguageResourcesTest.kt index 4b12a0ee953..8c217740d10 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/build/FilterPerLanguageResourcesTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/build/FilterPerLanguageResourcesTest.kt @@ -43,11 +43,12 @@ class FilterPerLanguageResourcesTest { private val STR_RESOURCE_0_EN = StringResource(mapOf("" to "en str0")) private val STR_RESOURCE_1_EN_PT = StringResource(mapOf("" to "en str1", "pt-BR" to "pt str1")) - private val STR_RESOURCE_2_EN_SW = StringResource(mapOf("" to "en str2", "sw" to "sw str2")) + private val STR_RESOURCE_2_EN_SW_PCM = + StringResource(mapOf("" to "en str2", "sw" to "sw str2", "pcm" to "pcm str 3")) private val COLOR_RESOURCE_0_EN_PT = ColorResource(mapOf("" to "0xDEF", "pt-BR" to "0xABC")) - private val RESOURCE_TABLE_EN_PT_SW = + private val RESOURCE_TABLE_EN_PT_SW_PCM = createResourceTable( - STR_RESOURCE_0_EN, STR_RESOURCE_1_EN_PT, STR_RESOURCE_2_EN_SW, COLOR_RESOURCE_0_EN_PT + STR_RESOURCE_0_EN, STR_RESOURCE_1_EN_PT, STR_RESOURCE_2_EN_SW_PCM, COLOR_RESOURCE_0_EN_PT ) private val ENGLISH = @@ -60,10 +61,12 @@ class FilterPerLanguageResourcesTest { createLanguageSupportDefinition(language = OppiaLanguage.SWAHILI, languageCode = "sw") private val ARABIC = createLanguageSupportDefinition(language = OppiaLanguage.ARABIC, languageCode = "ar") + private val NIGERIAN_PIDGIN = + createLanguageSupportDefinition(language = OppiaLanguage.NIGERIAN_PIDGIN, languageCode = "pcm") private val SUPPORTED_LANGUAGES_EN = createSupportedLanguages(ENGLISH) private val SUPPORTED_LANGUAGES_EN_PT = createSupportedLanguages(ENGLISH, BRAZILIAN_PORTUGUESE) - private val SUPPORTED_LANGUAGES_EN_PT_SW = - createSupportedLanguages(ENGLISH, BRAZILIAN_PORTUGUESE, SWAHILI) + private val SUPPORTED_LANGUAGES_EN_PT_SW_PCM = + createSupportedLanguages(ENGLISH, BRAZILIAN_PORTUGUESE, SWAHILI, NIGERIAN_PIDGIN) private val SUPPORTED_LANGUAGES_EN_AR = createSupportedLanguages(ENGLISH, ARABIC) @field:[Rule JvmField] var tempFolder = TemporaryFolder() @@ -156,7 +159,7 @@ class FilterPerLanguageResourcesTest { // "No supported languages" always implies English (since English must be supported). createZipWith( fileName = "input.zip", - resourceTable = RESOURCE_TABLE_EN_PT_SW, + resourceTable = RESOURCE_TABLE_EN_PT_SW_PCM, supportedLanguages = SUPPORTED_LANGUAGES_EN ) @@ -172,7 +175,7 @@ class FilterPerLanguageResourcesTest { // "No supported languages" always implies English (since English must be supported). createZipWith( fileName = "input.zip", - resourceTable = RESOURCE_TABLE_EN_PT_SW, + resourceTable = RESOURCE_TABLE_EN_PT_SW_PCM, supportedLanguages = SUPPORTED_LANGUAGES_EN_PT ) @@ -188,15 +191,15 @@ class FilterPerLanguageResourcesTest { // "No supported languages" always implies English (since English must be supported). createZipWith( fileName = "input.zip", - resourceTable = RESOURCE_TABLE_EN_PT_SW, - supportedLanguages = SUPPORTED_LANGUAGES_EN_PT_SW + resourceTable = RESOURCE_TABLE_EN_PT_SW_PCM, + supportedLanguages = SUPPORTED_LANGUAGES_EN_PT_SW_PCM ) runScript(tempFolder.getFilePath("input.zip"), tempFolder.getFilePath("output.zip")) // All resources should be kept. val presentLanguages = readSupportedResourceLanguagesFromZip(fileName = "output.zip") - assertThat(presentLanguages).containsExactly("", "pt-BR", "sw") + assertThat(presentLanguages).containsExactly("", "pt-BR", "sw", "pcm") } @Test @@ -204,7 +207,7 @@ class FilterPerLanguageResourcesTest { // "No supported languages" always implies English (since English must be supported). createZipWith( fileName = "input.zip", - resourceTable = RESOURCE_TABLE_EN_PT_SW, + resourceTable = RESOURCE_TABLE_EN_PT_SW_PCM, supportedLanguages = SUPPORTED_LANGUAGES_EN_AR ) @@ -220,7 +223,7 @@ class FilterPerLanguageResourcesTest { // "No supported languages" always implies English (since English must be supported). createZipWith( fileName = "input.zip", - resourceTable = RESOURCE_TABLE_EN_PT_SW, + resourceTable = RESOURCE_TABLE_EN_PT_SW_PCM, supportedLanguages = SUPPORTED_LANGUAGES_EN ) @@ -229,8 +232,8 @@ class FilterPerLanguageResourcesTest { val outputLine = readStandardOutputLines().single() assertThat(outputLine) .isEqualTo( - "2 resources are being removed that are tied to unsupported languages: [pt-BR, sw] (size" + - " reduction: 73 bytes)." + "4 resources are being removed that are tied to unsupported languages: [pcm, pt-BR, sw]" + + " (size reduction: 99 bytes)." ) } diff --git a/scripts/src/javatests/org/oppia/android/scripts/xml/StringLanguageTranslationCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/xml/StringLanguageTranslationCheckTest.kt index ebf36a61a72..00916b400e4 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/xml/StringLanguageTranslationCheckTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/xml/StringLanguageTranslationCheckTest.kt @@ -36,6 +36,10 @@ class StringLanguageTranslationCheckTest { private val SWAHILI_STRINGS_SHARED = mapOf("shared_string" to "Kicheza Ugunduzi") private val SWAHILI_STRINGS_EXTRAS = mapOf("swahili_only_string" to "Badili Wasifu") + + private val NIGERIAN_PIDGIN_STRINGS_SHARED = mapOf("shared_string" to "Pause di audio") + private val NIGERIAN_PIDGIN_STRINGS_EXTRAS = + mapOf("nigerian_pidgin_only_string" to "Abeg select all di correct choices.") } @field:[Rule JvmField] var tempFolder = TemporaryFolder() @@ -83,6 +87,7 @@ class StringLanguageTranslationCheckTest { populateBrazilianPortugueseTranslations(BRAZILIAN_PORTUGUESE_STRINGS_SHARED) populateEnglishTranslations(ENGLISH_STRINGS_SHARED) populateSwahiliTranslations(SWAHILI_STRINGS_SHARED) + populateNigerianPidginTranslations(NIGERIAN_PIDGIN_STRINGS_SHARED) runScript(tempFolder.root.absolutePath) @@ -95,6 +100,7 @@ class StringLanguageTranslationCheckTest { populateBrazilianPortugueseTranslations(BRAZILIAN_PORTUGUESE_STRINGS_SHARED) populateEnglishTranslations(ENGLISH_STRINGS_SHARED) populateSwahiliTranslations(SWAHILI_STRINGS_SHARED) + populateNigerianPidginTranslations(NIGERIAN_PIDGIN_STRINGS_SHARED) runScript(tempFolder.root.absolutePath) @@ -115,6 +121,7 @@ class StringLanguageTranslationCheckTest { populateBrazilianPortugueseTranslations(BRAZILIAN_PORTUGUESE_STRINGS_EXTRAS) populateEnglishTranslations(ENGLISH_STRINGS_SHARED) populateSwahiliTranslations(SWAHILI_STRINGS_SHARED) + populateNigerianPidginTranslations(NIGERIAN_PIDGIN_STRINGS_SHARED) runScript(tempFolder.root.absolutePath) @@ -135,6 +142,7 @@ class StringLanguageTranslationCheckTest { populateBrazilianPortugueseTranslations(BRAZILIAN_PORTUGUESE_STRINGS_SHARED) populateEnglishTranslations(ENGLISH_STRINGS_SHARED) populateSwahiliTranslations(SWAHILI_STRINGS_EXTRAS) + populateNigerianPidginTranslations(NIGERIAN_PIDGIN_STRINGS_SHARED) runScript(tempFolder.root.absolutePath) @@ -149,29 +157,55 @@ class StringLanguageTranslationCheckTest { ) } + @Test + fun testScript_presentTranslations_missingSomeNigerianPidgin_outputsMissingTranslations() { + populateArabicTranslations(ARABIC_STRINGS_SHARED) + populateBrazilianPortugueseTranslations(BRAZILIAN_PORTUGUESE_STRINGS_SHARED) + populateEnglishTranslations(ENGLISH_STRINGS_SHARED) + populateSwahiliTranslations(SWAHILI_STRINGS_SHARED) + populateNigerianPidginTranslations(NIGERIAN_PIDGIN_STRINGS_EXTRAS) + + runScript(tempFolder.root.absolutePath) + + assertThat(outContent.asString().trim()).isEqualTo( + """ + 1 translation(s) were found missing. + + Missing translations: + NIGERIAN_PIDGIN (1/1): + - shared_string + """.trimIndent().trim() + ) + } + @Test fun testScript_presentTranslations_missingMultiple_outputsMissingTranslationsWithTotalCount() { populateArabicTranslations(ARABIC_STRINGS_EXTRAS) populateBrazilianPortugueseTranslations(BRAZILIAN_PORTUGUESE_STRINGS_EXTRAS) populateEnglishTranslations(ENGLISH_STRINGS_SHARED + ENGLISH_STRINGS_EXTRAS) populateSwahiliTranslations(SWAHILI_STRINGS_EXTRAS) + populateNigerianPidginTranslations(NIGERIAN_PIDGIN_STRINGS_EXTRAS) runScript(tempFolder.root.absolutePath) assertThat(outContent.asString().trim()).isEqualTo( """ - 6 translation(s) were found missing. + 8 translation(s) were found missing. Missing translations: - ARABIC (2/6): + ARABIC (2/8): - shared_string - english_only_string - BRAZILIAN_PORTUGUESE (2/6): + BRAZILIAN_PORTUGUESE (2/8): - shared_string - english_only_string - SWAHILI (2/6): + SWAHILI (2/8): + - shared_string + - english_only_string + + NIGERIAN_PIDGIN (2/8): - shared_string - english_only_string """.trimIndent().trim() @@ -186,21 +220,27 @@ class StringLanguageTranslationCheckTest { ) populateEnglishTranslations(ENGLISH_STRINGS_SHARED + ENGLISH_STRINGS_EXTRAS) populateSwahiliTranslations(SWAHILI_STRINGS_SHARED + SWAHILI_STRINGS_EXTRAS) + populateNigerianPidginTranslations( + NIGERIAN_PIDGIN_STRINGS_SHARED + NIGERIAN_PIDGIN_STRINGS_EXTRAS + ) runScript(tempFolder.root.absolutePath) assertThat(outContent.asString().trim()).isEqualTo( """ - 3 translation(s) were found missing. + 4 translation(s) were found missing. Missing translations: - ARABIC (1/3): + ARABIC (1/4): + - english_only_string + + BRAZILIAN_PORTUGUESE (1/4): - english_only_string - BRAZILIAN_PORTUGUESE (1/3): + SWAHILI (1/4): - english_only_string - SWAHILI (1/3): + NIGERIAN_PIDGIN (1/4): - english_only_string """.trimIndent().trim() ) @@ -212,6 +252,7 @@ class StringLanguageTranslationCheckTest { populateBrazilianPortugueseTranslations(BRAZILIAN_PORTUGUESE_STRINGS_SHARED) populateEnglishTranslations(mapOf()) populateSwahiliTranslations(SWAHILI_STRINGS_SHARED) + populateNigerianPidginTranslations(NIGERIAN_PIDGIN_STRINGS_SHARED) runScript(tempFolder.root.absolutePath) @@ -237,6 +278,10 @@ class StringLanguageTranslationCheckTest { populateTranslations(appResources, "values-sw", strings) } + private fun populateNigerianPidginTranslations(strings: Map) { + populateTranslations(appResources, "values-pcm-rNG", strings) + } + private fun populateTranslations( resourceDir: File, valuesDirName: String, diff --git a/scripts/src/javatests/org/oppia/android/scripts/xml/StringResourceParserTest.kt b/scripts/src/javatests/org/oppia/android/scripts/xml/StringResourceParserTest.kt index 474b0ceba83..0e1edd3fa30 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/xml/StringResourceParserTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/xml/StringResourceParserTest.kt @@ -8,6 +8,7 @@ import org.junit.rules.TemporaryFolder import org.oppia.android.scripts.xml.StringResourceParser.TranslationLanguage.ARABIC import org.oppia.android.scripts.xml.StringResourceParser.TranslationLanguage.BRAZILIAN_PORTUGUESE import org.oppia.android.scripts.xml.StringResourceParser.TranslationLanguage.ENGLISH +import org.oppia.android.scripts.xml.StringResourceParser.TranslationLanguage.NIGERIAN_PIDGIN import org.oppia.android.scripts.xml.StringResourceParser.TranslationLanguage.SWAHILI import org.oppia.android.testing.assertThrows import org.w3c.dom.Document @@ -45,6 +46,11 @@ class StringResourceParserTest { "shared_string" to "Kicheza Ugunduzi", "swahili_only_string" to "Badili Wasifu" ) + + private val NIGERIAN_PIDGIN_STRINGS = mapOf( + "shared_string" to "Pause di audio", + "nigerian_pidgin_only_string" to "Abeg select all di correct choices." + ) } private val documentBuilderFactory by lazy { DocumentBuilderFactory.newInstance() } @@ -79,6 +85,7 @@ class StringResourceParserTest { populateArabicTranslations() populateBrazilianPortugueseTranslations() populateSwahiliTranslations() + populateNigerianPidginTranslations() val parser = StringResourceParser(tempFolder.root) val exception = assertThrows(IllegalStateException::class) { parser.retrieveBaseStringFile() } @@ -93,6 +100,7 @@ class StringResourceParserTest { populateBrazilianPortugueseTranslations() populateEnglishTranslations() populateSwahiliTranslations() + populateNigerianPidginTranslations() val parser = StringResourceParser(tempFolder.root) val exception = assertThrows(IllegalStateException::class) { parser.retrieveBaseStringFile() } @@ -107,6 +115,7 @@ class StringResourceParserTest { populateArabicTranslations() populateEnglishTranslations() populateSwahiliTranslations() + populateNigerianPidginTranslations() val parser = StringResourceParser(tempFolder.root) val exception = assertThrows(IllegalStateException::class) { parser.retrieveBaseStringFile() } @@ -121,6 +130,7 @@ class StringResourceParserTest { populateArabicTranslations() populateBrazilianPortugueseTranslations() populateEnglishTranslations() + populateNigerianPidginTranslations() val parser = StringResourceParser(tempFolder.root) val exception = assertThrows(IllegalStateException::class) { parser.retrieveBaseStringFile() } @@ -130,6 +140,21 @@ class StringResourceParserTest { .contains("Missing translation strings for language(s): SWAHILI") } + @Test + fun testRetrieveBaseStringFile_noNigerianPidginStrings_throwsException() { + populateArabicTranslations() + populateBrazilianPortugueseTranslations() + populateEnglishTranslations() + populateSwahiliTranslations() + val parser = StringResourceParser(tempFolder.root) + + val exception = assertThrows(IllegalStateException::class) { parser.retrieveBaseStringFile() } + + assertThat(exception) + .hasMessageThat() + .contains("Missing translation strings for language(s): NIGERIAN_PIDGIN") + } + @Test fun testRetrieveBaseStringFile_extraStringsDirectory_throwsException() { populateAllAppTranslations() @@ -151,6 +176,7 @@ class StringResourceParserTest { populateArabicTranslations() populateBrazilianPortugueseTranslations() populateSwahiliTranslations() + populateNigerianPidginTranslations() populateTranslations(utilityResources, "values", mapOf()) val parser = StringResourceParser(tempFolder.root) @@ -167,6 +193,7 @@ class StringResourceParserTest { populateArabicTranslations() populateBrazilianPortugueseTranslations() populateSwahiliTranslations() + populateNigerianPidginTranslations() writeTranslationsFile(appResources, "values", "") val parser = StringResourceParser(tempFolder.root) @@ -203,10 +230,11 @@ class StringResourceParserTest { val nonEnglishTranslations = parser.retrieveAllNonEnglishTranslations() - assertThat(nonEnglishTranslations).hasSize(3) + assertThat(nonEnglishTranslations).hasSize(4) assertThat(nonEnglishTranslations).containsKey(ARABIC) assertThat(nonEnglishTranslations).containsKey(BRAZILIAN_PORTUGUESE) assertThat(nonEnglishTranslations).containsKey(SWAHILI) + assertThat(nonEnglishTranslations).containsKey(NIGERIAN_PIDGIN) assertThat(nonEnglishTranslations).doesNotContainKey(ENGLISH) // Only non-English are included. val arFile = nonEnglishTranslations[ARABIC] assertThat(arFile?.language).isEqualTo(ARABIC) @@ -223,6 +251,11 @@ class StringResourceParserTest { assertThat(swFile?.file?.toRelativeString(tempFolder.root)) .isEqualTo("app/src/main/res/values-sw/strings.xml") assertThat(swFile?.strings).containsExactlyEntriesIn(SWAHILI_STRINGS) + val pcmFile = nonEnglishTranslations[NIGERIAN_PIDGIN] + assertThat(pcmFile?.language).isEqualTo(NIGERIAN_PIDGIN) + assertThat(pcmFile?.file?.toRelativeString(tempFolder.root)) + .isEqualTo("app/src/main/res/values-pcm-rNG/strings.xml") + assertThat(pcmFile?.strings).containsExactlyEntriesIn(NIGERIAN_PIDGIN_STRINGS) } private fun populateAllAppTranslations() { @@ -230,6 +263,7 @@ class StringResourceParserTest { populateBrazilianPortugueseTranslations() populateEnglishTranslations() populateSwahiliTranslations() + populateNigerianPidginTranslations() } private fun populateArabicTranslations() { @@ -248,6 +282,10 @@ class StringResourceParserTest { populateTranslations(appResources, "values-sw", SWAHILI_STRINGS) } + private fun populateNigerianPidginTranslations() { + populateTranslations(appResources, "values-pcm-rNG", NIGERIAN_PIDGIN_STRINGS) + } + private fun populateTranslations( resourceDir: File, valuesDirName: String, diff --git a/scripts/src/javatests/org/oppia/android/scripts/xml/StringResourceValidationCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/xml/StringResourceValidationCheckTest.kt index c7fc526f687..e2152950a99 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/xml/StringResourceValidationCheckTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/xml/StringResourceValidationCheckTest.kt @@ -35,6 +35,10 @@ class StringResourceValidationCheckTest { private const val SW_STRING_NO_NEWLINES = "Msaada" private const val SW_STRING_ONE_NEWLINE = "\\nMsaada" private const val SW_STRING_TWO_NEWLINES = "\\nMsaada\\n" + + private const val PCM_STRING_NO_NEWLINES = "Pause di audio" + private const val PCM_STRING_ONE_NEWLINE = "\\nPause di audio" + private const val PCM_STRING_TWO_NEWLINES = "\\nPause di audio\\n" } @field:[Rule JvmField] var tempFolder = TemporaryFolder() @@ -82,6 +86,7 @@ class StringResourceValidationCheckTest { populateBrazilianPortugueseTranslations(mapOf("str1" to PT_BR_STRING_ONE_NEWLINE)) populateEnglishTranslations(mapOf("str1" to EN_STRING_ONE_NEWLINE)) populateSwahiliTranslations(mapOf("str1" to SW_STRING_ONE_NEWLINE)) + populateNigerianPidginTranslations(mapOf("str1" to PCM_STRING_ONE_NEWLINE)) runScript(tempFolder.root.absolutePath) @@ -98,6 +103,7 @@ class StringResourceValidationCheckTest { mapOf("str1" to EN_STRING_ONE_NEWLINE, "str2" to EN_STRING_ONE_NEWLINE) ) populateSwahiliTranslations(mapOf("str1" to SW_STRING_ONE_NEWLINE)) + populateNigerianPidginTranslations(mapOf("str1" to PCM_STRING_ONE_NEWLINE)) val exception = assertThrows(Exception::class) { runScript(tempFolder.root.absolutePath) } @@ -125,6 +131,7 @@ class StringResourceValidationCheckTest { mapOf("str1" to EN_STRING_ONE_NEWLINE, "str2" to EN_STRING_ONE_NEWLINE) ) populateSwahiliTranslations(mapOf("str1" to SW_STRING_ONE_NEWLINE)) + populateNigerianPidginTranslations(mapOf("str1" to PCM_STRING_ONE_NEWLINE)) val exception = assertThrows(Exception::class) { runScript(tempFolder.root.absolutePath) } @@ -152,6 +159,7 @@ class StringResourceValidationCheckTest { populateSwahiliTranslations( mapOf("str1" to SW_STRING_NO_NEWLINES, "str2" to SW_STRING_TWO_NEWLINES) ) + populateNigerianPidginTranslations(mapOf("str1" to PCM_STRING_ONE_NEWLINE)) val exception = assertThrows(Exception::class) { runScript(tempFolder.root.absolutePath) } @@ -169,6 +177,34 @@ class StringResourceValidationCheckTest { ) } + @Test + fun testScript_inconsistentLines_nigerianPidgin_failsWithFindings() { + populateArabicTranslations(mapOf("str1" to AR_STRING_ONE_NEWLINE)) + populateBrazilianPortugueseTranslations(mapOf("str1" to PT_BR_STRING_ONE_NEWLINE)) + populateEnglishTranslations( + mapOf("str1" to EN_STRING_ONE_NEWLINE, "str2" to EN_STRING_ONE_NEWLINE) + ) + populateSwahiliTranslations(mapOf("str1" to SW_STRING_ONE_NEWLINE)) + populateNigerianPidginTranslations( + mapOf("str1" to PCM_STRING_NO_NEWLINES, "str2" to PCM_STRING_TWO_NEWLINES) + ) + + val exception = assertThrows(Exception::class) { runScript(tempFolder.root.absolutePath) } + + // This output check also inadvertently verifies that the script doesn't care about missing + // strings in translated string files. + assertThat(exception).hasMessageThat().contains("STRING RESOURCE VALIDATION CHECKS FAILED") + assertThat(outContent.asString().trim()).isEqualTo( + """ + 1 language(s) were found with string consistency errors. + + 2 consistency error(s) were found for NIGERIAN_PIDGIN strings (file: app/src/main/res/values-pcm-rNG/strings.xml): + - string str1: original translation uses 2 line(s) but translation uses 1 line(s). Please remove any extra lines or add any that are missing. + - string str2: original translation uses 2 line(s) but translation uses 3 line(s). Please remove any extra lines or add any that are missing. + """.trimIndent().trim() + ) + } + @Test fun testScript_inconsistentLines_allLanguages_failsWithFindings() { populateArabicTranslations( @@ -183,6 +219,9 @@ class StringResourceValidationCheckTest { populateSwahiliTranslations( mapOf("str1" to SW_STRING_NO_NEWLINES, "str2" to SW_STRING_TWO_NEWLINES) ) + populateNigerianPidginTranslations( + mapOf("str1" to PCM_STRING_NO_NEWLINES, "str2" to PCM_STRING_TWO_NEWLINES) + ) val exception = assertThrows(Exception::class) { runScript(tempFolder.root.absolutePath) } @@ -191,7 +230,7 @@ class StringResourceValidationCheckTest { assertThat(exception).hasMessageThat().contains("STRING RESOURCE VALIDATION CHECKS FAILED") assertThat(outContent.asString().trim()).isEqualTo( """ - 3 language(s) were found with string consistency errors. + 4 language(s) were found with string consistency errors. 2 consistency error(s) were found for ARABIC strings (file: app/src/main/res/values-ar/strings.xml): - string str1: original translation uses 2 line(s) but translation uses 1 line(s). Please remove any extra lines or add any that are missing. @@ -204,6 +243,10 @@ class StringResourceValidationCheckTest { 2 consistency error(s) were found for SWAHILI strings (file: app/src/main/res/values-sw/strings.xml): - string str1: original translation uses 2 line(s) but translation uses 1 line(s). Please remove any extra lines or add any that are missing. - string str2: original translation uses 2 line(s) but translation uses 3 line(s). Please remove any extra lines or add any that are missing. + + 2 consistency error(s) were found for NIGERIAN_PIDGIN strings (file: app/src/main/res/values-pcm-rNG/strings.xml): + - string str1: original translation uses 2 line(s) but translation uses 1 line(s). Please remove any extra lines or add any that are missing. + - string str2: original translation uses 2 line(s) but translation uses 3 line(s). Please remove any extra lines or add any that are missing. """.trimIndent().trim() ) } @@ -226,6 +269,10 @@ class StringResourceValidationCheckTest { populateTranslations(appResources, "values-sw", strings) } + private fun populateNigerianPidginTranslations(strings: Map) { + populateTranslations(appResources, "values-pcm-rNG", strings) + } + private fun populateTranslations( resourceDir: File, valuesDirName: String, diff --git a/testing/src/main/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRule.kt b/testing/src/main/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRule.kt index c2f88f9990a..0166f25ec6b 100644 --- a/testing/src/main/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRule.kt +++ b/testing/src/main/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRule.kt @@ -126,7 +126,7 @@ class InitializeDefaultLocaleRule : TestRule { contentStringId = constructLanguageId(ietfTag = "en", combinedMacaronicId = null) audioTranslationId = constructLanguageId(ietfTag = "en", combinedMacaronicId = null) }.build() - regionDefinition = defineContext.getRegionDefinition() + defineContext.getRegionDefinition()?.let { regionDefinition = it } usageMode = OppiaLocaleContext.LanguageUsageMode.APP_STRINGS }.build() } @@ -152,15 +152,20 @@ class InitializeDefaultLocaleRule : TestRule { } private fun DefineAppLanguageLocaleContext.getRegionDefinition(): RegionSupportDefinition? { - return RegionSupportDefinition.newBuilder().apply { - getOppiaRegion()?.let { region = it } - addAllLanguages(getOppiaRegionLanguages()) - regionId = RegionSupportDefinition.IetfBcp47RegionId.newBuilder().apply { - regionIetfTag.tryExtractAnnotationStringConstant()?.let { - ietfRegionTag = it + val oppiaRegion = getOppiaRegion() + val regionLanguages = getOppiaRegionLanguages() + val ietfRegionTag = regionIetfTag.tryExtractAnnotationStringConstant() + return if (oppiaRegion != null || regionLanguages.isNotEmpty() || ietfRegionTag != null) { + RegionSupportDefinition.newBuilder().apply { + oppiaRegion?.let { region = it } + addAllLanguages(regionLanguages) + ietfRegionTag?.let { + regionId = RegionSupportDefinition.IetfBcp47RegionId.newBuilder().apply { + this.ietfRegionTag = it + }.build() } }.build() - }.build() + } else null } private fun DefineAppLanguageLocaleContext.getOppiaRegion() = diff --git a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt index 6dd32e0d855..bf3a397ad9e 100644 --- a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt +++ b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt @@ -21,14 +21,18 @@ import org.oppia.android.util.platformparameter.EnableContinueButtonAnimation import org.oppia.android.util.platformparameter.EnableDownloadsSupport import org.oppia.android.util.platformparameter.EnableEditAccountsOptionsUi import org.oppia.android.util.platformparameter.EnableExtraTopicTabsUi +import org.oppia.android.util.platformparameter.EnableFastLanguageSwitchingInLesson import org.oppia.android.util.platformparameter.EnableInteractionConfigChangeStateRetention import org.oppia.android.util.platformparameter.EnableLanguageSelectionUi import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.EnablePerformanceMetricsCollection import org.oppia.android.util.platformparameter.EnableSpotlightUi +import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSON_DEFAULT_VALUE import org.oppia.android.util.platformparameter.FORCED_APP_UPDATE_VERSION_CODE import org.oppia.android.util.platformparameter.ForcedAppUpdateVersionCode import org.oppia.android.util.platformparameter.LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE import org.oppia.android.util.platformparameter.LOWEST_SUPPORTED_API_LEVEL import org.oppia.android.util.platformparameter.LOWEST_SUPPORTED_API_LEVEL_DEFAULT_VALUE import org.oppia.android.util.platformparameter.LowestSupportedApiLevel @@ -127,6 +131,16 @@ class TestPlatformParameterModule { fun provideLearnerStudyAnalytics(): PlatformParameterValue = PlatformParameterValue.createDefaultParameter(enableLearnerStudyAnalytics) + @Provides + @EnableFastLanguageSwitchingInLesson + fun provideFastInLessonLanguageSwitching(): PlatformParameterValue = + PlatformParameterValue.createDefaultParameter(enableFastLanguageSwitchingInLesson) + + @Provides + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds(): PlatformParameterValue = + PlatformParameterValue.createDefaultParameter(enableLoggingLearnerStudyIds) + @Provides @CacheLatexRendering fun provideCacheLatexRendering( @@ -254,6 +268,9 @@ class TestPlatformParameterModule { private var enableLanguageSelectionUi = ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE private var enableEditAccountsOptionsUi = ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE private var enableLearnerStudyAnalytics = LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE + private var enableFastLanguageSwitchingInLesson = + FAST_LANGUAGE_SWITCHING_IN_LESSON_DEFAULT_VALUE + private var enableLoggingLearnerStudyIds = LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE private var enableExtraTopicTabsUi = ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE private var enableInteractionConfigChangeStateRetention = ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE @@ -287,6 +304,18 @@ class TestPlatformParameterModule { enableLearnerStudyAnalytics = value } + /** Enables forcing [EnableFastLanguageSwitchingInLesson] platform parameter flag from tests. */ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + fun forceEnableFastLanguageSwitchingInLesson(value: Boolean) { + enableFastLanguageSwitchingInLesson = value + } + + /** Enables forcing [EnableLoggingLearnerStudyIds] platform parameter flag from tests. */ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + fun forceEnableLoggingLearnerStudyIds(value: Boolean) { + enableLoggingLearnerStudyIds = value + } + /** Enables forcing [EnableExtraTopicTabsUi] platform parameter flag from tests. */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) fun forceEnableExtraTopicTabsUi(value: Boolean) { @@ -323,6 +352,8 @@ class TestPlatformParameterModule { enableLanguageSelectionUi = ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE enableEditAccountsOptionsUi = ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE enableLearnerStudyAnalytics = LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE + enableFastLanguageSwitchingInLesson = FAST_LANGUAGE_SWITCHING_IN_LESSON_DEFAULT_VALUE + enableLoggingLearnerStudyIds = LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE enableExtraTopicTabsUi = ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE enableInteractionConfigChangeStateRetention = ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE diff --git a/third_party/maven_install.json b/third_party/maven_install.json index 3522dd4d415..6ac87f7eab6 100644 --- a/third_party/maven_install.json +++ b/third_party/maven_install.json @@ -1,8 +1,8 @@ { "dependency_tree": { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": -109720896, - "__RESOLVED_ARTIFACTS_HASH": 860179600, + "__INPUT_ARTIFACTS_HASH": 521507787, + "__RESOLVED_ARTIFACTS_HASH": -1467267841, "conflict_resolution": { "androidx.constraintlayout:constraintlayout:1.1.3": "androidx.constraintlayout:constraintlayout:2.0.1", "androidx.core:core:1.0.1": "androidx.core:core:1.3.1", @@ -989,9 +989,9 @@ "commons-io:commons-io:2.4", "com.google.guava:guava:30.1.1-android", "com.googlecode.juniversalchardet:juniversalchardet:1.0.3", + "com.google.code.gson:gson:2.8.6", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10", "com.squareup:javapoet:1.11.1", - "com.google.code.gson:gson:2.8.5", "org.jetbrains.kotlin:kotlin-stdlib:1.5.0" ], "directDependencies": [ @@ -1003,9 +1003,9 @@ "commons-io:commons-io:2.4", "com.google.guava:guava:30.1.1-android", "com.googlecode.juniversalchardet:juniversalchardet:1.0.3", + "com.google.code.gson:gson:2.8.6", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10", - "com.squareup:javapoet:1.11.1", - "com.google.code.gson:gson:2.8.5" + "com.squareup:javapoet:1.11.1" ], "file": "v1/https/maven.google.com/androidx/databinding/databinding-compiler-common/3.4.2/databinding-compiler-common-3.4.2.jar", "mirror_urls": [ @@ -1028,10 +1028,10 @@ "com.android.databinding:baseLibrary:jar:sources:3.4.2", "org.antlr:antlr4:jar:sources:4.5.3", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:sources:1.4.10", + "com.google.code.gson:gson:jar:sources:2.8.6", "androidx.databinding:databinding-common:jar:sources:3.4.2", "com.squareup:javapoet:jar:sources:1.11.1", "commons-io:commons-io:jar:sources:2.4", - "com.google.code.gson:gson:jar:sources:2.8.5", "com.googlecode.juniversalchardet:juniversalchardet:jar:sources:1.0.3" ], "directDependencies": [ @@ -1041,10 +1041,10 @@ "com.android.databinding:baseLibrary:jar:sources:3.4.2", "org.antlr:antlr4:jar:sources:4.5.3", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:sources:1.4.10", + "com.google.code.gson:gson:jar:sources:2.8.6", "androidx.databinding:databinding-common:jar:sources:3.4.2", "com.squareup:javapoet:jar:sources:1.11.1", "commons-io:commons-io:jar:sources:2.4", - "com.google.code.gson:gson:jar:sources:2.8.5", "com.googlecode.juniversalchardet:juniversalchardet:jar:sources:1.0.3" ], "file": "v1/https/maven.google.com/androidx/databinding/databinding-compiler-common/3.4.2/databinding-compiler-common-3.4.2-sources.jar", @@ -1069,10 +1069,10 @@ "commons-io:commons-io:2.4", "com.google.guava:guava:30.1.1-android", "com.googlecode.juniversalchardet:juniversalchardet:1.0.3", + "com.google.code.gson:gson:2.8.6", "androidx.databinding:databinding-compiler-common:3.4.2", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10", "com.squareup:javapoet:1.11.1", - "com.google.code.gson:gson:2.8.5", "commons-codec:commons-codec:1.10", "org.jetbrains.kotlin:kotlin-stdlib:1.5.0" ], @@ -1108,10 +1108,10 @@ "androidx.databinding:databinding-compiler-common:jar:sources:3.4.2", "org.antlr:antlr4:jar:sources:4.5.3", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:sources:1.4.10", + "com.google.code.gson:gson:jar:sources:2.8.6", "androidx.databinding:databinding-common:jar:sources:3.4.2", "com.squareup:javapoet:jar:sources:1.11.1", "commons-io:commons-io:jar:sources:2.4", - "com.google.code.gson:gson:jar:sources:2.8.5", "com.googlecode.juniversalchardet:juniversalchardet:jar:sources:1.0.3" ], "directDependencies": [ @@ -4238,11 +4238,11 @@ { "coord": "com.android.tools.build.jetifier:jetifier-core:1.0.0-beta04", "dependencies": [ - "com.google.code.gson:gson:2.8.5", - "org.jetbrains.kotlin:kotlin-stdlib:1.5.0" + "org.jetbrains.kotlin:kotlin-stdlib:1.5.0", + "com.google.code.gson:gson:2.8.6" ], "directDependencies": [ - "com.google.code.gson:gson:2.8.5", + "com.google.code.gson:gson:2.8.6", "org.jetbrains.kotlin:kotlin-stdlib:1.5.0" ], "file": "v1/https/maven.google.com/com/android/tools/build/jetifier/jetifier-core/1.0.0-beta04/jetifier-core-1.0.0-beta04.jar", @@ -4259,11 +4259,11 @@ { "coord": "com.android.tools.build.jetifier:jetifier-core:jar:sources:1.0.0-beta04", "dependencies": [ - "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.5.0", - "com.google.code.gson:gson:jar:sources:2.8.5" + "com.google.code.gson:gson:jar:sources:2.8.6", + "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.5.0" ], "directDependencies": [ - "com.google.code.gson:gson:jar:sources:2.8.5", + "com.google.code.gson:gson:jar:sources:2.8.6", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.5.0" ], "file": "v1/https/maven.google.com/com/android/tools/build/jetifier/jetifier-core/1.0.0-beta04/jetifier-core-1.0.0-beta04-sources.jar", @@ -5511,34 +5511,34 @@ "url": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar" }, { - "coord": "com.google.code.gson:gson:2.8.5", + "coord": "com.google.code.gson:gson:2.8.6", "dependencies": [], "directDependencies": [], - "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", "mirror_urls": [ - "https://maven.google.com/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar", - "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar", - "https://maven.fabric.io/public/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar", - "https://maven.google.com/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar", - "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar" + "https://maven.google.com/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", + "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", + "https://maven.fabric.io/public/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", + "https://maven.google.com/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", + "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" ], - "sha256": "233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81", - "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar" + "sha256": "c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f", + "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" }, { - "coord": "com.google.code.gson:gson:jar:sources:2.8.5", + "coord": "com.google.code.gson:gson:jar:sources:2.8.6", "dependencies": [], "directDependencies": [], - "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", "mirror_urls": [ - "https://maven.google.com/com/google/code/gson/gson/2.8.5/gson-2.8.5-sources.jar", - "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5-sources.jar", - "https://maven.fabric.io/public/com/google/code/gson/gson/2.8.5/gson-2.8.5-sources.jar", - "https://maven.google.com/com/google/code/gson/gson/2.8.5/gson-2.8.5-sources.jar", - "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5-sources.jar" + "https://maven.google.com/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", + "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", + "https://maven.fabric.io/public/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", + "https://maven.google.com/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", + "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar" ], - "sha256": "512b4bf6927f4864acc419b8c5109c23361c30ed1f5798170248d33040de068e", - "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.5/gson-2.8.5-sources.jar" + "sha256": "da4d787939dc8de214724a20d88614b70ef8c3a4931d9c694300b5d9098ed9bc", + "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar" }, { "coord": "com.google.dagger:dagger-compiler:2.28.1", @@ -6244,13 +6244,13 @@ "dependencies": [ "com.google.code.findbugs:jsr305:3.0.2", "com.google.guava:guava:30.1.1-android", + "com.google.code.gson:gson:2.8.6", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10", - "com.google.code.gson:gson:2.8.5", "com.google.android.gms:strict-version-matcher-plugin:1.2.1" ], "directDependencies": [ "com.google.android.gms:strict-version-matcher-plugin:1.2.1", - "com.google.code.gson:gson:2.8.5", + "com.google.code.gson:gson:2.8.6", "com.google.guava:guava:30.1.1-android" ], "file": "v1/https/maven.google.com/com/google/gms/google-services/4.3.3/google-services-4.3.3.jar", @@ -6270,12 +6270,12 @@ "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:sources:1.4.10", - "com.google.code.gson:gson:jar:sources:2.8.5", + "com.google.code.gson:gson:jar:sources:2.8.6", "com.google.android.gms:strict-version-matcher-plugin:jar:sources:1.2.1" ], "directDependencies": [ "com.google.android.gms:strict-version-matcher-plugin:jar:sources:1.2.1", - "com.google.code.gson:gson:jar:sources:2.8.5", + "com.google.code.gson:gson:jar:sources:2.8.6", "com.google.guava:guava:jar:sources:30.1.1-android" ], "file": "v1/https/maven.google.com/com/google/gms/google-services/4.3.3/google-services-4.3.3-sources.jar", @@ -6464,6 +6464,66 @@ "sha256": "ba4df669fec153fa4cd0ef8d02c6d3ef0702b7ac4cabe080facf3b6e490bb972", "url": "https://repo1.maven.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3-sources.jar" }, + { + "coord": "com.google.protobuf:protobuf-java-util:3.17.3", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.guava:guava:30.1.1-android", + "com.google.errorprone:error_prone_annotations:2.7.1", + "com.google.code.gson:gson:2.8.6", + "com.google.protobuf:protobuf-java:3.17.3", + "com.google.guava:failureaccess:1.0.1", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "directDependencies": [ + "com.google.code.gson:gson:2.8.6", + "com.google.errorprone:error_prone_annotations:2.7.1", + "com.google.guava:guava:30.1.1-android", + "com.google.protobuf:protobuf-java:3.17.3" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "mirror_urls": [ + "https://maven.google.com/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "https://maven.fabric.io/public/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "https://maven.google.com/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar" + ], + "sha256": "bf320ed076000e1d8c7cbf7601b056acaecab80f75b9a659b9f6398d0d7e3f79", + "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar" + }, + { + "coord": "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "com.google.code.gson:gson:jar:sources:2.8.6", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.7.1" + ], + "directDependencies": [ + "com.google.code.gson:gson:jar:sources:2.8.6", + "com.google.errorprone:error_prone_annotations:jar:sources:2.7.1", + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "mirror_urls": [ + "https://maven.google.com/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "https://maven.fabric.io/public/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "https://maven.google.com/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar" + ], + "sha256": "4046612802edfa6f9e201b2a53d10439a4ebbab5324ae415874e041cd1d70bbf", + "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar" + }, { "coord": "com.google.protobuf:protobuf-java:3.17.3", "dependencies": [], @@ -7343,6 +7403,66 @@ "sha256": "36df4b321ec95e31226ff5abaae73e66f3a99dedddbc2d03054c4e141c8aaa5c", "url": "https://maven.google.com/io/fabric/sdk/android/fabric/1.4.7/fabric-1.4.7.aar" }, + { + "coord": "io.xlate:yaml-json:0.1.0", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0.jar", + "mirror_urls": [ + "https://maven.google.com/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0.jar", + "https://repo1.maven.org/maven2/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0.jar", + "https://maven.fabric.io/public/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0.jar", + "https://maven.google.com/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0.jar", + "https://repo1.maven.org/maven2/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0.jar" + ], + "sha256": "713e1d0c4f0f7c4c93a6b366b361dd1493f406cc532986784c884c205e049558", + "url": "https://repo1.maven.org/maven2/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0.jar" + }, + { + "coord": "io.xlate:yaml-json:jar:sources:0.1.0", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0-sources.jar", + "mirror_urls": [ + "https://maven.google.com/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0-sources.jar", + "https://repo1.maven.org/maven2/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0-sources.jar", + "https://maven.fabric.io/public/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0-sources.jar", + "https://maven.google.com/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0-sources.jar", + "https://repo1.maven.org/maven2/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0-sources.jar" + ], + "sha256": "f445d4f5c9fcaa110c3fd2e6d97ad3cc37351afc8faead453f9d0461912f70e6", + "url": "https://repo1.maven.org/maven2/io/xlate/yaml-json/0.1.0/yaml-json-0.1.0-sources.jar" + }, + { + "coord": "jakarta.json:jakarta.json-api:2.1.2", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2.jar", + "mirror_urls": [ + "https://maven.google.com/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2.jar", + "https://repo1.maven.org/maven2/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2.jar", + "https://maven.fabric.io/public/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2.jar", + "https://maven.google.com/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2.jar", + "https://repo1.maven.org/maven2/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2.jar" + ], + "sha256": "f2472507ad2cc12f2aef08a2f7a628cd1c3f855668a6dcb2aa9a30d9b909b998", + "url": "https://repo1.maven.org/maven2/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2.jar" + }, + { + "coord": "jakarta.json:jakarta.json-api:jar:sources:2.1.2", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2-sources.jar", + "mirror_urls": [ + "https://maven.google.com/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2-sources.jar", + "https://repo1.maven.org/maven2/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2-sources.jar", + "https://maven.fabric.io/public/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2-sources.jar", + "https://maven.google.com/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2-sources.jar", + "https://repo1.maven.org/maven2/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2-sources.jar" + ], + "sha256": "6f8b2473a6cae740f0e5f0bae65a34646fb95d1ebbc27bf66374cdab2685e97d", + "url": "https://repo1.maven.org/maven2/jakarta/json/jakarta.json-api/2.1.2/jakarta.json-api-2.1.2-sources.jar" + }, { "coord": "javax.annotation:javax.annotation-api:1.3.2", "dependencies": [], @@ -7749,6 +7869,44 @@ "sha256": "52fd5b908ed38b2c543fac371c2192ff2896fec0f3ddea29f23b5db117a7ea6e", "url": "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0-sources.jar" }, + { + "coord": "org.eclipse.parsson:parsson:1.1.2", + "dependencies": [ + "jakarta.json:jakarta.json-api:2.1.2" + ], + "directDependencies": [ + "jakarta.json:jakarta.json-api:2.1.2" + ], + "file": "v1/https/repo1.maven.org/maven2/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2.jar", + "mirror_urls": [ + "https://maven.google.com/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2.jar", + "https://repo1.maven.org/maven2/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2.jar", + "https://maven.fabric.io/public/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2.jar", + "https://maven.google.com/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2.jar", + "https://repo1.maven.org/maven2/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2.jar" + ], + "sha256": "0a0d5fef906ddbbddae6e894f6cf42e7b952d24952f1df09adb0a2426f496bf6", + "url": "https://repo1.maven.org/maven2/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2.jar" + }, + { + "coord": "org.eclipse.parsson:parsson:jar:sources:1.1.2", + "dependencies": [ + "jakarta.json:jakarta.json-api:jar:sources:2.1.2" + ], + "directDependencies": [ + "jakarta.json:jakarta.json-api:jar:sources:2.1.2" + ], + "file": "v1/https/repo1.maven.org/maven2/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2-sources.jar", + "mirror_urls": [ + "https://maven.google.com/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2-sources.jar", + "https://repo1.maven.org/maven2/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2-sources.jar", + "https://maven.fabric.io/public/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2-sources.jar", + "https://maven.google.com/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2-sources.jar", + "https://repo1.maven.org/maven2/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2-sources.jar" + ], + "sha256": "d00ff02f31469dd5a10cbb78a105cd90519a1f8e041c8ec76056f89318aca718", + "url": "https://repo1.maven.org/maven2/org/eclipse/parsson/parsson/1.1.2/parsson-1.1.2-sources.jar" + }, { "coord": "org.hamcrest:hamcrest-core:1.3", "dependencies": [], @@ -9607,6 +9765,36 @@ "sha256": "f8b7e1a3ed9916c1d2529ede178af4bd6dc3b2f41fc9be57c22476f3e91ffdb4", "url": "https://repo1.maven.org/maven2/org/robolectric/utils/4.5/utils-4.5-sources.jar" }, + { + "coord": "org.snakeyaml:snakeyaml-engine:2.6", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6.jar", + "mirror_urls": [ + "https://maven.google.com/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6.jar", + "https://repo1.maven.org/maven2/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6.jar", + "https://maven.fabric.io/public/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6.jar", + "https://maven.google.com/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6.jar", + "https://repo1.maven.org/maven2/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6.jar" + ], + "sha256": "2652199af40c9aa2f1782400d2dfbbf4e5226208c4e05ddd4059c3d6d9cd1505", + "url": "https://repo1.maven.org/maven2/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6.jar" + }, + { + "coord": "org.snakeyaml:snakeyaml-engine:jar:sources:2.6", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6-sources.jar", + "mirror_urls": [ + "https://maven.google.com/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6-sources.jar", + "https://repo1.maven.org/maven2/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6-sources.jar", + "https://maven.fabric.io/public/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6-sources.jar", + "https://maven.google.com/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6-sources.jar", + "https://repo1.maven.org/maven2/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6-sources.jar" + ], + "sha256": "da277b3176dca953b66bc4377de8c1ce44da2a96b39dfa07dcd31aa1eb437644", + "url": "https://repo1.maven.org/maven2/org/snakeyaml/snakeyaml-engine/2.6/snakeyaml-engine-2.6-sources.jar" + }, { "coord": "androidx.constraintlayout:constraintlayout-solver:jar:sources:2.0.1", "dependencies": [], diff --git a/third_party/versions.bzl b/third_party/versions.bzl index 391c54d952a..b51f0c4b457 100644 --- a/third_party/versions.bzl +++ b/third_party/versions.bzl @@ -93,11 +93,15 @@ MAVEN_TEST_DEPENDENCY_VERSIONS = { "androidx.work:work-testing": "2.4.0", "com.github.bumptech.glide:mocks": "4.11.0", "com.google.protobuf:protobuf-java": "3.17.3", + "com.google.protobuf:protobuf-java-util": "3.17.3", "com.google.truth.extensions:truth-liteproto-extension": "1.1.3", "com.google.truth:truth": "0.43", "com.squareup.okhttp3:mockwebserver": "4.7.2", "com.squareup.retrofit2:retrofit-mock": "2.5.0", + "io.xlate:yaml-json": "0.1.0", + "jakarta.json:jakarta.json-api": "2.1.2", "junit:junit": "4.12", + "org.eclipse.parsson:parsson": "1.1.2", "org.jetbrains.kotlin:kotlin-compiler-embeddable": "1.5.0", "org.jetbrains.kotlin:kotlin-reflect": "1.3.41", "org.jetbrains.kotlin:kotlin-test-junit": "1.3.72", @@ -106,6 +110,7 @@ MAVEN_TEST_DEPENDENCY_VERSIONS = { "org.mockito:mockito-core": "2.19.0", "org.robolectric:annotations": "4.5", "org.robolectric:robolectric": "4.5", + "org.snakeyaml:snakeyaml-engine": "2.6", } # Note to developers: Please keep this dict sorted by key to make it easier to find dependencies. diff --git a/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleFactory.kt b/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleFactory.kt index f90a42c52d7..ffe3e186646 100644 --- a/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleFactory.kt +++ b/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleFactory.kt @@ -3,133 +3,322 @@ package org.oppia.android.util.locale import android.os.Build import org.oppia.android.app.model.LanguageSupportDefinition import org.oppia.android.app.model.LanguageSupportDefinition.LanguageId +import org.oppia.android.app.model.LanguageSupportDefinition.LanguageId.LanguageTypeCase.IETF_BCP47_ID +import org.oppia.android.app.model.LanguageSupportDefinition.LanguageId.LanguageTypeCase.LANGUAGETYPE_NOT_SET +import org.oppia.android.app.model.LanguageSupportDefinition.LanguageId.LanguageTypeCase.MACARONIC_ID import org.oppia.android.app.model.OppiaLocaleContext -import org.oppia.android.app.model.RegionSupportDefinition +import org.oppia.android.app.model.OppiaLocaleContext.LanguageUsageMode +import org.oppia.android.app.model.OppiaLocaleContext.LanguageUsageMode.APP_STRINGS import java.util.Locale +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject +import javax.inject.Singleton /** * Factory for creating new Android [Locale]s. This is meant only to be used within the locale * domain package. */ +@Singleton class AndroidLocaleFactory @Inject constructor( - private val machineLocale: OppiaLocale.MachineLocale + private val profileChooserSelector: ProposalChooser.Selector ) { + private val memoizedLocales by lazy { ConcurrentHashMap() } + /** - * Returns a new [Locale] that matches the given [OppiaLocaleContext]. Note this will - * automatically fail over to the context's backup fallback language if the primary language - * doesn't match any available locales on the device. Further, if no locale can be found, the - * returned [Locale] will be forced to match the specified context (which will result in some - * default/root locale behavior in Android). + * Creates a new [Locale] that matches the given [OppiaLocaleContext]. + * + * This function uses the following prioritization algorithm when trying to create an + * Android-compatible [Locale] (steps are executed in order from top to bottom): + * 1. Try to find a system [Locale] that matches the primary language code & region. + * 2. If (1) fails and the primary language is configured for Android, create a forced [Locale]. + * 3. If (2) fails, try to find a system [Locale] that matches the secondary language code/region. + * 4. If (3) fails and the secondary language is configured for Android, create a forced [Locale]. + * 5. If (4) fails, compute a forced [Locale] for the primary language. + * + * Note that steps (2) and (4) are only used in cases when the provided [localeContext] has + * [LanguageUsageMode.APP_STRINGS] usage since prioritizing Android ID matching only affects + * resource selection (and is based on the [Locale] used). + * + * The returned [Locale] will never match a supported app language that is not supported on the + * current running version of Android (from a rendering perspective). + * + * 'Forced locale' means an app-constructed [Locale] is used instead of one of the available + * system [Locale]s. Using this [Locale] will have one of two effects depending on how it's used: + * - For resource selection, Android will respect the custom [Locale] iff it includes a region + * code (e.g. resources in a "values-hi-rEN/" directory will be used if the activity's + * configured [Locale] has a language code of "hi" and region code of "en"). + * - For other locale-based operations, the forced [Locale] will behave like the system's + * [Locale.ROOT]. + * + * @param localeContext the [OppiaLocaleContext] to use as a basis for finding a similar [Locale] + * @return the best [Locale] to match the provided [localeContext] */ fun createAndroidLocale(localeContext: OppiaLocaleContext): Locale { - val languageId = localeContext.getLanguageId() - val fallbackLanguageId = localeContext.getFallbackLanguageId() - - // Locale is always computed based on the Android resource app string identifier if that's - // defined. If it isn't, the routine falls back to app language & region country codes (which - // also provides interoperability with system-derived contexts). Note that if either identifier - // is missing for the primary language, the fallback is used instead (if available), except that - // IETF BCP 47 tags from the primary language are used before Android resource codes from the - // fallback. Thus, the order of this list is important. Finally, a basic check is done here to - // make sure this version of Android can actually render the target language. - val potentialProfiles = - computePotentialLanguageProfiles(localeContext, languageId) + - computePotentialFallbackLanguageProfiles(localeContext, fallbackLanguageId) - - // Either find the first supported profile or force the locale to use the exact definition - // values, depending on whether to fail over to a forced locale. - val firstSupportedProfile = potentialProfiles.findFirstSupported() - val selectedProfile = firstSupportedProfile - ?: languageId.computeForcedProfile(localeContext.regionDefinition) - return Locale(selectedProfile.languageCode, selectedProfile.getNonWildcardRegionCode()) + // Note: computeIfAbsent is used here instead of getOrPut to ensure atomicity across multiple + // threads calling into this create function. + return memoizedLocales.computeIfAbsent(localeContext) { + val chooser = profileChooserSelector.findBestChooser(localeContext) + val primaryLocaleSource = LocaleSource.createFromPrimary(localeContext) + val fallbackLocaleSource = LocaleSource.createFromFallback(localeContext) + val proposal = chooser.findBestProposal(primaryLocaleSource, fallbackLocaleSource) + return@computeIfAbsent proposal.computedLocale + } } - private fun computePotentialLanguageProfiles( - localeContext: OppiaLocaleContext, - languageId: LanguageId - ): List = - computeLanguageProfiles(localeContext, localeContext.languageDefinition, languageId) - - private fun computePotentialFallbackLanguageProfiles( - localeContext: OppiaLocaleContext, - fallbackLanguageId: LanguageId - ): List { - return computeLanguageProfiles( - localeContext, localeContext.fallbackLanguageDefinition, fallbackLanguageId - ) - } + /** + * A proposal of a [AndroidLocaleProfile] that may potentially be used to create a [Locale]. See + * [isViable]. + */ + sealed class LocaleProfileProposal { + /** The [AndroidLocaleProfile] being considered in this proposal. */ + protected abstract val profile: AndroidLocaleProfile - private fun computeLanguageProfiles( - localeContext: OppiaLocaleContext, - definition: LanguageSupportDefinition, - languageId: LanguageId - ): List { - return if (definition.minAndroidSdkVersion <= Build.VERSION.SDK_INT) { - listOfNotNull( - languageId.computeLocaleProfileFromAndroidId(), - AndroidLocaleProfile.createFromIetfDefinitions(languageId, localeContext.regionDefinition), - AndroidLocaleProfile.createFromMacaronicLanguage(languageId) - ) - } else listOf() - } + /** + * A computed [Locale] that most closely represents the [AndroidLocaleProfile] of this proposal. + */ + val computedLocale: Locale + get() = Locale(profile.languageCode, profile.getNonWildcardRegionCode()) - private fun LanguageId.computeLocaleProfileFromAndroidId(): AndroidLocaleProfile? { - return if (hasAndroidResourcesLanguageId()) { - androidResourcesLanguageId.run { - // Empty region codes are allowed for Android resource IDs since they should always be used - // verbatim to ensure the correct Android resource string can be computed (such as for macro - // languages). - maybeConstructProfileWithWildcardSupport(languageCode, regionCode) + /** + * Determines whether the [AndroidLocaleProfile] of this proposal is a viable choice for using + * to compute a [Locale] (e.g. via [computedLocale]). + * + * @param machineLocale the app's [OppiaLocale.MachineLocale] + * @param systemProfiles [AndroidLocaleProfile]s representing the system's available locales + * @return whether this proposal has a viable profile for creating a [Locale] + */ + abstract fun isViable( + machineLocale: OppiaLocale.MachineLocale, + systemProfiles: List + ): Boolean + + /** + * A [LocaleProfileProposal] that is only viable if its [profile] is among the available system + * locales and its [minAndroidSdkVersion] is below, or at, the current system's SDK version. + */ + data class SystemProposal( + override val profile: AndroidLocaleProfile, + val minAndroidSdkVersion: Int + ) : LocaleProfileProposal() { + override fun isViable( + machineLocale: OppiaLocale.MachineLocale, + systemProfiles: List + ): Boolean { + return systemProfiles.any { it.matches(machineLocale, profile) } && + minAndroidSdkVersion <= Build.VERSION.SDK_INT } - } else null + } + + /** + * A [LocaleProfileProposal] that is only viable if its [minAndroidSdkVersion] is below, or at, + * the current system's SDK version. + * + * This proposal ignores system locales when considering viability. + */ + data class ForcedProposal( + override val profile: AndroidLocaleProfile, + val minAndroidSdkVersion: Int + ) : LocaleProfileProposal() { + override fun isViable( + machineLocale: OppiaLocale.MachineLocale, + systemProfiles: List + ): Boolean = minAndroidSdkVersion <= Build.VERSION.SDK_INT + } + + private companion object { + private fun AndroidLocaleProfile.getNonWildcardRegionCode(): String = + regionCode.takeIf { it != AndroidLocaleProfile.REGION_WILDCARD } ?: "" + } } /** - * Returns an [AndroidLocaleProfile] for this [LanguageId] and the specified - * [RegionSupportDefinition] based on the language's & region's IETF BCP 47 codes regardless of - * whether they're defined (i.e. it's fine to default to empty string here since that will - * leverage Android's own root locale behavior). + * A producer of [LocaleProfileProposal]s for a given context and for various situations. + * + * New instances should be created using [createFromPrimary] or [createFromFallback]. + * + * @property localeContext the broader [OppiaLocaleContext] from which to source profiles + * @property definition the specific language definition to consider for possible profiles + * @property languageId the specific language ID to consider for possible profiles */ - private fun LanguageId.computeForcedProfile( - regionDefinition: RegionSupportDefinition - ): AndroidLocaleProfile { - if (hasAndroidResourcesLanguageId()) { - // Create a locale exactly matching the Android ID profile. - return AndroidLocaleProfile( - androidResourcesLanguageId.languageCode, androidResourcesLanguageId.regionCode + class LocaleSource private constructor( + private val localeContext: OppiaLocaleContext, + private val definition: LanguageSupportDefinition, + private val languageId: LanguageId + ) { + private val regionDefinition by lazy { + localeContext.regionDefinition.takeIf { localeContext.hasRegionDefinition() } + } + + /** + * Returns all [LocaleProfileProposal]s which require matching against system locales for + * viability (see [LocaleProfileProposal.SystemProposal]) for this source's configured language + * context, or an empty list if there are none. + */ + fun computeSystemMatchingProposals(): List { + return listOfNotNull( + computeLocaleProfileFromAndroidId()?.toSystemProposal(), + createIetfProfile()?.toSystemProposal(), + createMacaronicProfile()?.toSystemProposal() ) } - return when (languageTypeCase) { - LanguageId.LanguageTypeCase.IETF_BCP47_ID -> { + + /** + * Returns a [LocaleProfileProposal] representing a [LocaleProfileProposal.ForcedProposal] + * specifically for this source's Android language context (e.g. + * [LanguageSupportDefinition.AndroidLanguageId]), or null if there is no such Android ID + * configured for this source's context. + */ + fun computeForcedAndroidProposal(): LocaleProfileProposal? = + computeLocaleProfileFromAndroidId()?.toForcedProposal() + + /** + * Returns a [LocaleProfileProposal] representing a [LocaleProfileProposal.ForcedProposal] that + * is guaranteed to match best to the language context of this source. + * + * Note that the returned proposal will prioritize its Android ID configuration over + * alternatives (such as IETF BCP 47 or a macaronic language configuration). + */ + fun computeForcedProposal(): LocaleProfileProposal = + computeForcedAndroidProposal() ?: languageId.toForcedProposal() + + private fun computeLocaleProfileFromAndroidId(): AndroidLocaleProfile? { + return languageId.androidResourcesLanguageId.takeIf { + languageId.hasAndroidResourcesLanguageId() && it.languageCode.isNotEmpty() + }?.let { + // Empty region codes are allowed for Android resource IDs since they should always be used + // verbatim to ensure the correct Android resource string can be computed (such as for macro + // languages). AndroidLocaleProfile( - ietfBcp47Id.ietfLanguageTag, regionDefinition.regionId.ietfRegionTag + it.languageCode, + regionCode = it.regionCode.ifEmpty { AndroidLocaleProfile.REGION_WILDCARD } ) } - LanguageId.LanguageTypeCase.MACARONIC_ID -> { - AndroidLocaleProfile.createFromMacaronicLanguage(this) - ?: error("Invalid macaronic ID: ${macaronicId.combinedLanguageCode}") + } + + private fun LanguageId.toForcedProposal(): LocaleProfileProposal { + return when (languageId.languageTypeCase) { + IETF_BCP47_ID -> createIetfProfile().expectedProfile() + MACARONIC_ID -> createMacaronicProfile().expectedProfile() + LANGUAGETYPE_NOT_SET, null -> error("Invalid language case: $languageTypeCase.") + }.toForcedProposal() + } + + private fun createIetfProfile(): AndroidLocaleProfile? = + AndroidLocaleProfile.createFromIetfDefinitions(languageId, regionDefinition) + + private fun createMacaronicProfile(): AndroidLocaleProfile? = + AndroidLocaleProfile.createFromMacaronicLanguage(languageId) + + private fun AndroidLocaleProfile?.expectedProfile() = this ?: error("Invalid ID: $languageId.") + + private fun AndroidLocaleProfile.toSystemProposal() = + LocaleProfileProposal.SystemProposal(profile = this, definition.minAndroidSdkVersion) + + private fun AndroidLocaleProfile.toForcedProposal() = + LocaleProfileProposal.ForcedProposal(profile = this, definition.minAndroidSdkVersion) + + companion object { + /** + * Return a new [LocaleSource] that maps to [localeContext]'s primary language configuration + * (i.e. fallback language details will be ignored). + */ + fun createFromPrimary(localeContext: OppiaLocaleContext): LocaleSource = + LocaleSource(localeContext, localeContext.languageDefinition, localeContext.getLanguageId()) + + /** + * Return a new [LocaleSource] that maps to [localeContext]'s fallback (secondary) language + * configuration (i.e. primary language details will be ignored). + */ + fun createFromFallback(localeContext: OppiaLocaleContext): LocaleSource { + return LocaleSource( + localeContext, + localeContext.fallbackLanguageDefinition, + localeContext.getFallbackLanguageId() + ) } - LanguageId.LanguageTypeCase.LANGUAGETYPE_NOT_SET, null -> - error("Invalid language case: $languageTypeCase") } } - private fun maybeConstructProfileWithWildcardSupport( - languageCode: String, - regionCode: String - ): AndroidLocaleProfile? { - return if (languageCode.isNotEmpty()) { - val adjustedRegionCode = if (regionCode.isEmpty()) { - AndroidLocaleProfile.REGION_WILDCARD - } else regionCode - AndroidLocaleProfile(languageCode, adjustedRegionCode) - } else null + /** + * A chooser for finding [LocaleProfileProposal]s that best matches a specific + * [OppiaLocaleContext]. + * + * See [findBestProposal] for details on the selection process. + * + * Instances of this interface can be retrieved via an application-injected [Selector]. + */ + interface ProposalChooser { + /** + * Finds the [LocaleProfileProposal] that *best* matches the contexts represented by the + * provided sources. + * + * Note that the returned proposal is not guaranteed to produce a [Locale] that matches existing + * system locales (and, in fact, it may not even if there are such proposals available among the + * provided sources depending on the behavior of the implementation). + * + * @param primarySource the [LocaleSource] whose profiles should take priority + * @param fallbackSource the [LocaleSource] whose profiles should only be considered if no + * profiles from [primarySource] are viable + * @return the best matching [LocaleProfileProposal] + */ + fun findBestProposal( + primarySource: LocaleSource, + fallbackSource: LocaleSource + ): LocaleProfileProposal + + /** Application-level selector for [ProposalChooser]s. See [findBestChooser]. */ + class Selector @Inject constructor( + private val localePreferred: MatchedLocalePreferredChooser, + private val androidResourcePreferred: AndroidResourceCompatibilityPreferredChooser + ) { + /** + * Returns the [ProposalChooser] that best matches the provided [localeContext]. + * + * Generally, [MatchedLocalePreferredChooser] is used in most cases. In circumstances where + * app strings may use the computed [Locale], [AndroidResourceCompatibilityPreferredChooser] + * may be returned, instead. + */ + fun findBestChooser(localeContext: OppiaLocaleContext): ProposalChooser = + if (localeContext.usageMode == APP_STRINGS) androidResourcePreferred else localePreferred + } + } + + /** + * A [ProposalChooser] that prioritizes finding [LocaleProfileProposal]s which match available + * system locales. + */ + class MatchedLocalePreferredChooser @Inject constructor( + private val machineLocale: OppiaLocale.MachineLocale + ) : ProposalChooser { + override fun findBestProposal( + primarySource: LocaleSource, + fallbackSource: LocaleSource + ): LocaleProfileProposal { + return primarySource.computeSystemMatchingProposals().findFirstViable(machineLocale) + ?: fallbackSource.computeSystemMatchingProposals().findFirstViable(machineLocale) + ?: primarySource.computeForcedProposal() + } } - private fun List.findFirstSupported(): AndroidLocaleProfile? = find { - availableLocaleProfiles.any { availableProfile -> - availableProfile.matches(machineLocale, it) + /** + * A [ProposalChooser] that prioritizes finding [LocaleProfileProposal]s which match available + * system locales first, and secondarily a [LocaleSource]'s strongly Android compatible proposal + * (see [LocaleSource.computeForcedAndroidProposal]). Note that Android ID proposals take priority + * over fallbacks in this chooser since it's assumed that the Android system can properly handle + * [Locale]s produced by such profiles in order to correctly produce app UI strings. + */ + class AndroidResourceCompatibilityPreferredChooser @Inject constructor( + private val machineLocale: OppiaLocale.MachineLocale + ) : ProposalChooser { + override fun findBestProposal( + primarySource: LocaleSource, + fallbackSource: LocaleSource + ): LocaleProfileProposal { + return primarySource.computeSystemMatchingProposals().findFirstViable(machineLocale) + ?: primarySource.computeForcedAndroidProposal()?.takeOnlyIfViable(machineLocale) + ?: fallbackSource.computeSystemMatchingProposals().findFirstViable(machineLocale) + ?: fallbackSource.computeForcedAndroidProposal()?.takeOnlyIfViable(machineLocale) + ?: primarySource.computeForcedProposal() } } @@ -138,10 +327,12 @@ class AndroidLocaleFactory @Inject constructor( Locale.getAvailableLocales().map(AndroidLocaleProfile::createFrom) } - private fun AndroidLocaleProfile.getNonWildcardRegionCode(): String { - return if (regionCode != AndroidLocaleProfile.REGION_WILDCARD) { - regionCode - } else "" - } + private fun List.findFirstViable( + machineLocale: OppiaLocale.MachineLocale + ) = firstOrNull { it.isViable(machineLocale, availableLocaleProfiles) } + + private fun LocaleProfileProposal.takeOnlyIfViable( + machineLocale: OppiaLocale.MachineLocale + ): LocaleProfileProposal? = takeIf { isViable(machineLocale, availableLocaleProfiles) } } } diff --git a/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleProfile.kt b/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleProfile.kt index 140575f7f84..a410c2b06b6 100644 --- a/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleProfile.kt +++ b/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleProfile.kt @@ -8,7 +8,7 @@ import java.util.Locale * A profile to represent an Android [Locale] object which can be used to easily compare different * locales (based on the properties the app cares about), or reconstruct a [Locale] object. * - * @property languageCode the IETF BCP 47 or ISO 639-2 language code + * @property languageCode the IETF BCP 47 or ISO 639-2/3 language code * @property regionCode the IETF BCP 47 or ISO 3166 alpha-2 region code */ data class AndroidLocaleProfile(val languageCode: String, val regionCode: String) { @@ -65,8 +65,8 @@ data class AndroidLocaleProfile(val languageCode: String, val regionCode: String languageId: LanguageId, regionDefinition: RegionSupportDefinition? ): AndroidLocaleProfile? { - if (!languageId.hasIetfBcp47Id()) return null return when { + !languageId.hasIetfBcp47Id() -> null "-" in languageId.ietfBcp47Id.ietfLanguageTag -> { val (languageCode, regionCode) = languageId.ietfBcp47Id.ietfLanguageTag.divide("-") ?: return null @@ -93,9 +93,7 @@ data class AndroidLocaleProfile(val languageCode: String, val regionCode: String * it's malformed. Macaronic IDs are always expected to include language and region components, * so both fields are guaranteed to be populated in a returned [AndroidLocaleProfile]. */ - fun createFromMacaronicLanguage( - languageId: LanguageId - ): AndroidLocaleProfile? { + fun createFromMacaronicLanguage(languageId: LanguageId): AndroidLocaleProfile? { if (!languageId.hasMacaronicId()) return null val (languageCode, regionCode) = languageId.macaronicId.combinedLanguageCode.divide("-") ?: return null diff --git a/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt b/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt index 3a5492a3433..e297801c763 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt @@ -73,8 +73,9 @@ import org.oppia.android.util.logging.EventBundleCreator.PerformanceMetricsLogga import org.oppia.android.util.logging.EventBundleCreator.PerformanceMetricsLoggableMetricType.NetworkUsageLoggableMetric import org.oppia.android.util.logging.EventBundleCreator.PerformanceMetricsLoggableMetricType.StartupLatencyLoggableMetric import org.oppia.android.util.logging.EventBundleCreator.PerformanceMetricsLoggableMetricType.StorageUsageLoggableMetric -import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.PlatformParameterValue +import java.util.concurrent.atomic.AtomicInteger import javax.inject.Inject import javax.inject.Singleton import org.oppia.android.app.model.EventLog.CardContext as CardEventContext @@ -108,12 +109,13 @@ private const val MAX_CHARACTERS_IN_PARAMETER_NAME = 40 class EventBundleCreator @Inject constructor( private val context: Context, private val eventTypeNameConverter: EventTypeToHumanReadableNameConverter, - @EnableLearnerStudyAnalytics - private val enableLearnerStudyAnalytics: PlatformParameterValue + @EnableLoggingLearnerStudyIds + private val enableLoggingLearnerStudyIds: PlatformParameterValue ) { private val androidSdkVersion by lazy { Build.VERSION.SDK_INT } private val appVersionCode by lazy { context.getVersionCode() } private val appVersionName by lazy { context.getVersionName() } + private val eventCount by lazy { AtomicInteger() } /** * Fills the specified [bundle] with a logging-ready representation of [eventLog] and returns a @@ -127,6 +129,7 @@ class EventBundleCreator @Inject constructor( bundle.putInt("android_sdk", androidSdkVersion) bundle.putString("app_version_name", appVersionName) bundle.putInt("app_version_code", appVersionCode) + bundle.putInt("dbg_event_count_since_app_open", eventCount.incrementAndGet()) bundle.putString("oppia_app_lang", eventLog.appLanguageSelection.toAnalyticsText()) bundle.putString( "oppia_content_lang", eventLog.writtenTranslationLanguageSelection.toAnalyticsText() @@ -139,7 +142,7 @@ class EventBundleCreator @Inject constructor( eventContext.storeValue( PropertyStore( bundle, - allowUserIds = enableLearnerStudyAnalytics.value + allowUserIds = enableLoggingLearnerStudyIds.value ) ) }.activityName @@ -702,6 +705,7 @@ class EventBundleCreator @Inject constructor( OppiaLanguage.PORTUGUESE -> "Portuguese" OppiaLanguage.BRAZILIAN_PORTUGUESE -> "Brazilian Portuguese" OppiaLanguage.SWAHILI -> "Swahili" + OppiaLanguage.NIGERIAN_PIDGIN -> "Nigerian Pidgin" OppiaLanguage.UNRECOGNIZED -> "unrecognized_language" } } diff --git a/utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterConstants.kt b/utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterConstants.kt index 04b6f2b8ea6..fd3de79f4a3 100644 --- a/utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterConstants.kt +++ b/utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterConstants.kt @@ -79,9 +79,8 @@ const val SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS_DEFAULT_VALUE = 12 @Qualifier annotation class EnableLanguageSelectionUi -// TODO(#52): Enable this feature by default once it's completed. /** Default value for the feature flag corresponding to [EnableLanguageSelectionUi]. */ -const val ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE = false +const val ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE = true /** * Qualifier for the feature flag corresponding to enabling the extra topic tabs: practice and info. @@ -111,6 +110,37 @@ const val LEARNER_STUDY_ANALYTICS = "learner_study_analytics" */ const val LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE = false +/** + * Qualifier for a feature flag that controls whether learners may be allowed (via an + * admin-controlled setting) to use a special in-lesson button for quickly switching between content + * languages. + * + * This is generally expected to only be used in tandem with [EnableLearnerStudyAnalytics]. + */ +@Qualifier annotation class EnableFastLanguageSwitchingInLesson + +/** The platform parameter name corresponding to [EnableFastLanguageSwitchingInLesson]. */ +const val FAST_LANGUAGE_SWITCHING_IN_LESSON = "fast_language_switching_in_lesson" + +/** + * The default enabled state for the feature corresponding to [EnableFastLanguageSwitchingInLesson]. + */ +const val FAST_LANGUAGE_SWITCHING_IN_LESSON_DEFAULT_VALUE = false + +/** + * Qualifier for a feature flag that controls whether learner study IDs should be generated and + * logged with outgoing events. + * + * This is generally expected to only be used in tandem with [EnableLearnerStudyAnalytics]. + */ +@Qualifier annotation class EnableLoggingLearnerStudyIds + +/** The platform parameter name corresponding to [EnableLoggingLearnerStudyIds]. */ +const val LOGGING_LEARNER_STUDY_IDS = "logging_learner_study_ids" + +/** The default enabled state for the feature corresponding to [EnableLoggingLearnerStudyIds]. */ +const val LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE = false + /** * Qualifier for the platform parameter that controls whether to cache LaTeX rendering using Glide. */ diff --git a/utility/src/test/java/org/oppia/android/util/locale/AndroidLocaleFactoryTest.kt b/utility/src/test/java/org/oppia/android/util/locale/AndroidLocaleFactoryTest.kt index 4b593fc477d..c0b45f7e9fd 100644 --- a/utility/src/test/java/org/oppia/android/util/locale/AndroidLocaleFactoryTest.kt +++ b/utility/src/test/java/org/oppia/android/util/locale/AndroidLocaleFactoryTest.kt @@ -167,7 +167,7 @@ class AndroidLocaleFactoryTest { } @Test - fun testCreateLocale_appStrings_withAndroidId_incompatible_returnsFallbackAndroidIdLocale() { + fun testCreateLocale_appStrings_withPrimarySecondaryAndroidIds_incompat_returnsPrimaryLocale() { val context = createAppStringsContext( language = OppiaLanguage.LANGUAGE_UNSPECIFIED, @@ -178,7 +178,26 @@ class AndroidLocaleFactoryTest { val locale = androidLocaleFactory.createAndroidLocale(context) - // pt-BR should be picked because the primary language doesn't match a real locale. + // 'qq' is picked because it's an Android ID for app strings, so it's always taken as a forced + // locale over any fallback options. + assertThat(locale.language).isEqualTo("qq") + assertThat(locale.country).isEmpty() + } + + @Test + fun testCreateLocale_appStrings_withSecondaryAndroidIds_incompat_returnsSecondaryLocale() { + val context = + createAppStringsContext( + language = OppiaLanguage.LANGUAGE_UNSPECIFIED, + appStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), + fallbackAppStringId = createLanguageId(androidLanguageId = PT_BR_ANDROID_LANGUAGE_ID), + regionDefinition = REGION_BRAZIL + ) + + val locale = androidLocaleFactory.createAndroidLocale(context) + + // pt-BR should be picked because the primary language doesn't match a real locale, and it's not + // an Android ID that would take precedence. assertThat(locale.language).isEqualTo("pt") assertThat(locale.country).isEqualTo("BR") } @@ -287,7 +306,7 @@ class AndroidLocaleFactoryTest { } @Test - fun testCreateLocale_appStrings_incompat_androidAndIetfFallback_returnsAndroidIdLocale() { + fun testCreateLocale_appStrings_incompatAndroidId_androidAndIetfFallback_returnsPrimaryLocale() { val context = createAppStringsContext( language = OppiaLanguage.LANGUAGE_UNSPECIFIED, @@ -301,13 +320,36 @@ class AndroidLocaleFactoryTest { val locale = androidLocaleFactory.createAndroidLocale(context) - // pt-BR should be picked since Android IDs take precedence among multiple fallback options. + // 'qq' is picked because it's an Android ID for app strings, so it's always taken as a forced + // locale over any fallback options. + assertThat(locale.language).isEqualTo("qq") + assertThat(locale.country).isEmpty() + } + + @Test + fun testCreateLocale_appStrings_incompatIetfId_androidAndIetfFallback_returnsFallbackLocale() { + val context = + createAppStringsContext( + language = OppiaLanguage.LANGUAGE_UNSPECIFIED, + appStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), + fallbackAppStringId = createLanguageId( + androidLanguageId = PT_BR_ANDROID_LANGUAGE_ID, + ietfBcp47LanguageId = HI_IETF_LANGUAGE_ID + ), + regionDefinition = REGION_INDIA + ) + + val locale = androidLocaleFactory.createAndroidLocale(context) + + // pt-BR should be picked because the primary language doesn't match a real locale, and it's not + // an Android ID that would take precedence. Beyond that, the fallback's Android ID should take + // precedence. assertThat(locale.language).isEqualTo("pt") assertThat(locale.country).isEqualTo("BR") } @Test - fun testCreateLocale_appStrings_incompat_androidMacaronicFallbacks_returnsAndroidIdLocale() { + fun testCreateLocale_appStrings_incompatAndroidPrimary_androidMacaronicFallbacks_returnsPrim() { val context = createAppStringsContext( language = OppiaLanguage.LANGUAGE_UNSPECIFIED, @@ -321,18 +363,58 @@ class AndroidLocaleFactoryTest { val locale = androidLocaleFactory.createAndroidLocale(context) - // pt-BR should be picked since Android IDs take precedence among multiple fallback options. + // 'qq' is picked because it's an Android ID for app strings, so it's always taken as a forced + // locale over any fallback options. + assertThat(locale.language).isEqualTo("qq") + assertThat(locale.country).isEmpty() + } + + @Test + fun testCreateLocale_appStrings_incompatIetfPrimary_androidMacaronicFallbacks_returnsFallback() { + val context = + createAppStringsContext( + language = OppiaLanguage.LANGUAGE_UNSPECIFIED, + appStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), + fallbackAppStringId = createLanguageId( + androidLanguageId = PT_BR_ANDROID_LANGUAGE_ID, + macaronicLanguageId = HI_IN_MACARONIC_LANGUAGE_ID + ), + regionDefinition = REGION_INDIA + ) + + val locale = androidLocaleFactory.createAndroidLocale(context) + + // pt-BR should be picked since Android IDs take precedence among multiple fallback options, and + // none of the primary options are viable. assertThat(locale.language).isEqualTo("pt") assertThat(locale.country).isEqualTo("BR") } + @Test + fun testCreateLocale_appStrings_incompatIetfPrimary_incompatAndroidFallback_returnsFallback() { + val context = + createAppStringsContext( + language = OppiaLanguage.LANGUAGE_UNSPECIFIED, + appStringId = createLanguageId(ietfBcp47LanguageId = QQ_ZZ_IETF_LANGUAGE_ID), + fallbackAppStringId = createLanguageId(androidLanguageId = QQ_ANDROID_LANGUAGE_ID), + regionDefinition = REGION_INDIA + ) + + val locale = androidLocaleFactory.createAndroidLocale(context) + + // 'qq' is picked over the primary language because it's an Android ID and the primary language + // doesn't match any locales. + assertThat(locale.language).isEqualTo("qq") + assertThat(locale.country).isEmpty() + } + @Test fun testCreateLocale_appStrings_androidId_allIncompat_returnsForcedAndroidIdLocale() { val context = createAppStringsContext( language = OppiaLanguage.LANGUAGE_UNSPECIFIED, appStringId = createLanguageId(androidLanguageId = QQ_ANDROID_LANGUAGE_ID), - fallbackAppStringId = createLanguageId(androidLanguageId = QQ_ANDROID_LANGUAGE_ID), + fallbackAppStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), regionDefinition = REGION_INDIA ) @@ -352,7 +434,7 @@ class AndroidLocaleFactoryTest { androidLanguageId = QQ_ANDROID_LANGUAGE_ID, ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID ), - fallbackAppStringId = createLanguageId(androidLanguageId = QQ_ANDROID_LANGUAGE_ID), + fallbackAppStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), regionDefinition = REGION_INDIA ) @@ -373,7 +455,7 @@ class AndroidLocaleFactoryTest { androidLanguageId = QQ_ANDROID_LANGUAGE_ID, macaronicLanguageId = HI_EN_MACARONIC_LANGUAGE_ID ), - fallbackAppStringId = createLanguageId(androidLanguageId = QQ_ANDROID_LANGUAGE_ID), + fallbackAppStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), regionDefinition = REGION_INDIA ) @@ -391,7 +473,7 @@ class AndroidLocaleFactoryTest { createAppStringsContext( language = OppiaLanguage.LANGUAGE_UNSPECIFIED, appStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), - fallbackAppStringId = createLanguageId(androidLanguageId = QQ_ANDROID_LANGUAGE_ID), + fallbackAppStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), regionDefinition = REGION_INDIA ) @@ -409,7 +491,7 @@ class AndroidLocaleFactoryTest { createAppStringsContext( language = OppiaLanguage.LANGUAGE_UNSPECIFIED, appStringId = createLanguageId(macaronicLanguageId = HI_EN_MACARONIC_LANGUAGE_ID), - fallbackAppStringId = createLanguageId(androidLanguageId = QQ_ANDROID_LANGUAGE_ID), + fallbackAppStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), regionDefinition = REGION_INDIA ) @@ -427,7 +509,7 @@ class AndroidLocaleFactoryTest { createAppStringsContext( language = OppiaLanguage.ENGLISH, appStringId = createLanguageId(macaronicLanguageId = INVALID_MACARONIC_LANGUAGE_ID), - fallbackAppStringId = createLanguageId(androidLanguageId = QQ_ANDROID_LANGUAGE_ID), + fallbackAppStringId = createLanguageId(ietfBcp47LanguageId = QQ_IETF_LANGUAGE_ID), regionDefinition = REGION_INDIA ) @@ -435,7 +517,7 @@ class AndroidLocaleFactoryTest { androidLocaleFactory.createAndroidLocale(context) } - assertThat(exception).hasMessageThat().contains("Invalid macaronic ID") + assertThat(exception).hasMessageThat().contains("Invalid ID") } @Test @@ -867,7 +949,7 @@ class AndroidLocaleFactoryTest { androidLocaleFactory.createAndroidLocale(context) } - assertThat(exception).hasMessageThat().contains("Invalid macaronic ID") + assertThat(exception).hasMessageThat().contains("Invalid ID") } @Test @@ -1299,7 +1381,7 @@ class AndroidLocaleFactoryTest { androidLocaleFactory.createAndroidLocale(context) } - assertThat(exception).hasMessageThat().contains("Invalid macaronic ID") + assertThat(exception).hasMessageThat().contains("Invalid ID") } @Test diff --git a/utility/src/test/java/org/oppia/android/util/logging/EventBundleCreatorTest.kt b/utility/src/test/java/org/oppia/android/util/logging/EventBundleCreatorTest.kt index b1fa6410ad9..d2062e9097f 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/EventBundleCreatorTest.kt +++ b/utility/src/test/java/org/oppia/android/util/logging/EventBundleCreatorTest.kt @@ -88,8 +88,8 @@ import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.Parameter import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.RunParameterized import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.SelectRunnerPlatform import org.oppia.android.testing.junit.ParameterizedRobolectricTestRunner -import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics -import org.oppia.android.util.platformparameter.LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds +import org.oppia.android.util.platformparameter.LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE import org.oppia.android.util.platformparameter.PlatformParameterValue import org.robolectric.Shadows import org.robolectric.annotation.Config @@ -165,7 +165,7 @@ class EventBundleCreatorTest { @After fun tearDown() { - TestModule.enableLearnerStudyAnalytics = LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE + TestModule.enableLoggingLearnerStudyIds = LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE } @Test @@ -176,7 +176,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(EventLog.getDefaultInstance(), bundle) assertThat(typeName).isEqualTo("ERROR_internal_logging_failure") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(0) assertThat(bundle).string("priority").isEqualTo("unspecified_priority") assertThat(bundle).integer("event_type").isEqualTo(ACTIVITYCONTEXT_NOT_SET.number) @@ -222,7 +222,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("ERROR_internal_logging_failure") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACTIVITYCONTEXT_NOT_SET.number) @@ -283,7 +283,8 @@ class EventBundleCreatorTest { Iteration("hi_en", "inLang=HINGLISH", "expLang=Hinglish"), Iteration("pt", "inLang=PORTUGUESE", "expLang=Portuguese"), Iteration("pt_br", "inLang=BRAZILIAN_PORTUGUESE", "expLang=Brazilian Portuguese"), - Iteration("sw", "inLang=SWAHILI", "expLang=Swahili") + Iteration("sw", "inLang=SWAHILI", "expLang=Swahili"), + Iteration("pcm", "inLang=NIGERIAN_PIDGIN", "expLang=Nigerian Pidgin") ) fun testFillEventBundle_eventWithSelectedAppLanguage_savesCorrectAppLanguageInBundle() { setUpTestApplicationComponent() @@ -323,7 +324,8 @@ class EventBundleCreatorTest { Iteration("hi_en", "inLang=HINGLISH", "expLang=Hinglish"), Iteration("pt", "inLang=PORTUGUESE", "expLang=Portuguese"), Iteration("pt_br", "inLang=BRAZILIAN_PORTUGUESE", "expLang=Brazilian Portuguese"), - Iteration("sw", "inLang=SWAHILI", "expLang=Swahili") + Iteration("sw", "inLang=SWAHILI", "expLang=Swahili"), + Iteration("pcm", "inLang=NIGERIAN_PIDGIN", "expLang=Nigerian Pidgin") ) fun testFillEventBundle_eventWithSelectedWrittenTranslationsLanguage_savesCorrectWrittenLang() { setUpTestApplicationComponent() @@ -361,7 +363,8 @@ class EventBundleCreatorTest { Iteration("hi_en", "inLang=HINGLISH", "expLang=Hinglish"), Iteration("pt", "inLang=PORTUGUESE", "expLang=Portuguese"), Iteration("pt_br", "inLang=BRAZILIAN_PORTUGUESE", "expLang=Brazilian Portuguese"), - Iteration("sw", "inLang=SWAHILI", "expLang=Swahili") + Iteration("sw", "inLang=SWAHILI", "expLang=Swahili"), + Iteration("pcm", "inLang=NIGERIAN_PIDGIN", "expLang=Nigerian Pidgin") ) fun testFillEventBundle_eventWithSelectedAudioTranslationsLanguage_savesCorrectAudioLang() { setUpTestApplicationComponent() @@ -518,6 +521,46 @@ class EventBundleCreatorTest { assertThat(bundle).string("is_app_in_foreground").isEqualTo("false") } + @Test + fun testFillEventBundle_basicEvent_includesEventCount() { + setUpTestApplicationComponent() + val eventLog = createEventLog(context = createOpenExplorationActivity()) + + val bundle = Bundle().also { eventBundleCreator.fillEventBundle(eventLog, it) } + + assertThat(bundle).integer("dbg_event_count_since_app_open").isEqualTo(1) + } + + @Test + fun testFillEventBundle_secondEvent_includesLargerEventCount() { + setUpTestApplicationComponent() + val eventLog1 = createEventLog(context = createOpenExplorationActivity()) + eventBundleCreator.fillEventBundle(eventLog1, Bundle()) + val eventLog2 = createEventLog(context = createOpenExplorationActivity()) + + val bundle = Bundle().also { eventBundleCreator.fillEventBundle(eventLog2, it) } + + // The number is larger since there are now two events that have been marked for logging. + assertThat(bundle).integer("dbg_event_count_since_app_open").isEqualTo(2) + } + + @Test + fun testFillEventBundle_secondEvent_inDifferentApplication_includesInitialEventCount() { + // Prepare one event for logging in one application. + executeInPreviousAppInstance { testComponent -> + val eventLog1 = createEventLog(context = createOpenExplorationActivity()) + testComponent.getEventBundleCreator().fillEventBundle(eventLog1, Bundle()) + } + + // Create a second application (to simulate an app restart). + setUpTestApplicationComponent() + val eventLog2 = createEventLog(context = createOpenExplorationActivity()) + val bundle = Bundle().also { eventBundleCreator.fillEventBundle(eventLog2, it) } + + // The second event should have an initial event count since the app 'reopened'. + assertThat(bundle).integer("dbg_event_count_since_app_open").isEqualTo(1) + } + @Test fun testFillEventBundle_openExpActivityEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() @@ -527,7 +570,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_exploration_player_screen") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_EXPLORATION_ACTIVITY.number) @@ -551,7 +594,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_exploration_player_screen") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_EXPLORATION_ACTIVITY.number) @@ -577,7 +620,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("select_topic_info_tab") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_INFO_TAB.number) @@ -596,7 +639,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("select_topic_lessons_tab") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_LESSONS_TAB.number) @@ -778,7 +821,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("select_topic_practice_tab") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_PRACTICE_TAB.number) @@ -797,7 +840,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("select_topic_revision_tab") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_REVISION_TAB.number) @@ -816,7 +859,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_question_player_screen") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_QUESTION_PLAYER.number) @@ -836,7 +879,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_story_chapter_list_screen") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_STORY_ACTIVITY.number) @@ -856,7 +899,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_concept_card") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_CONCEPT_CARD.number) @@ -875,7 +918,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_revision_card") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_REVISION_CARD.number) @@ -895,7 +938,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("close_revision_card") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(CLOSE_REVISION_CARD.number) @@ -915,7 +958,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("start_exploration_card") - assertThat(bundle).hasSize(16) + assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(START_CARD_CONTEXT.number) @@ -940,7 +983,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("start_exploration_card") - assertThat(bundle).hasSize(18) + assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(START_CARD_CONTEXT.number) @@ -967,7 +1010,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("end_exploration_card") - assertThat(bundle).hasSize(16) + assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(END_CARD_CONTEXT.number) @@ -992,7 +1035,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("end_exploration_card") - assertThat(bundle).hasSize(18) + assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(END_CARD_CONTEXT.number) @@ -1019,7 +1062,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("unlock_hint") - assertThat(bundle).hasSize(16) + assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(HINT_UNLOCKED_CONTEXT.number) @@ -1044,7 +1087,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("unlock_hint") - assertThat(bundle).hasSize(18) + assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(HINT_UNLOCKED_CONTEXT.number) @@ -1071,7 +1114,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reveal_hint") - assertThat(bundle).hasSize(16) + assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACCESS_HINT_CONTEXT.number) @@ -1096,7 +1139,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reveal_hint") - assertThat(bundle).hasSize(18) + assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACCESS_HINT_CONTEXT.number) @@ -1123,7 +1166,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("unlock_solution") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SOLUTION_UNLOCKED_CONTEXT.number) @@ -1147,7 +1190,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("unlock_solution") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SOLUTION_UNLOCKED_CONTEXT.number) @@ -1173,7 +1216,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reveal_solution") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACCESS_SOLUTION_CONTEXT.number) @@ -1197,7 +1240,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reveal_solution") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACCESS_SOLUTION_CONTEXT.number) @@ -1223,7 +1266,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("submit_answer") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SUBMIT_ANSWER_CONTEXT.number) @@ -1249,7 +1292,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("submit_answer") - assertThat(bundle).hasSize(19) + assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SUBMIT_ANSWER_CONTEXT.number) @@ -1277,7 +1320,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("click_play_voiceover_button") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(PLAY_VOICE_OVER_CONTEXT.number) @@ -1303,7 +1346,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("click_play_voiceover_button") - assertThat(bundle).hasSize(19) + assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(PLAY_VOICE_OVER_CONTEXT.number) @@ -1331,7 +1374,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("click_pause_voiceover_button") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(PAUSE_VOICE_OVER_CONTEXT.number) @@ -1357,7 +1400,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("click_pause_voiceover_button") - assertThat(bundle).hasSize(19) + assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(PAUSE_VOICE_OVER_CONTEXT.number) @@ -1385,7 +1428,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("send_app_to_background") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(APP_IN_BACKGROUND_CONTEXT.number) @@ -1403,7 +1446,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("send_app_to_background") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(APP_IN_BACKGROUND_CONTEXT.number) @@ -1423,7 +1466,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("bring_app_to_foreground") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(APP_IN_FOREGROUND_CONTEXT.number) @@ -1441,7 +1484,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("bring_app_to_foreground") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(APP_IN_FOREGROUND_CONTEXT.number) @@ -1461,7 +1504,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("leave_exploration") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(EXIT_EXPLORATION_CONTEXT.number) @@ -1485,7 +1528,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("leave_exploration") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(EXIT_EXPLORATION_CONTEXT.number) @@ -1511,7 +1554,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("complete_exploration") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(FINISH_EXPLORATION_CONTEXT.number) @@ -1535,7 +1578,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("complete_exploration") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(FINISH_EXPLORATION_CONTEXT.number) @@ -1561,7 +1604,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("resume_in_progress_exploration") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(RESUME_EXPLORATION_CONTEXT.number) @@ -1579,7 +1622,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("resume_in_progress_exploration") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(RESUME_EXPLORATION_CONTEXT.number) @@ -1599,7 +1642,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("restart_in_progress_exploration") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(START_OVER_EXPLORATION_CONTEXT.number) @@ -1617,7 +1660,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("restart_in_progress_exploration") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(START_OVER_EXPLORATION_CONTEXT.number) @@ -1637,7 +1680,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("delete_profile") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(DELETE_PROFILE_CONTEXT.number) @@ -1655,7 +1698,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("delete_profile") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(DELETE_PROFILE_CONTEXT.number) @@ -1675,7 +1718,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_home_screen") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_HOME.number) @@ -1693,7 +1736,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_profile_chooser_screen") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_PROFILE_CHOOSER.number) @@ -1711,7 +1754,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reach_invested_engagement") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(REACH_INVESTED_ENGAGEMENT.number) @@ -1735,7 +1778,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reach_invested_engagement") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(REACH_INVESTED_ENGAGEMENT.number) @@ -1761,7 +1804,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("click_switch_language_in_lesson") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SWITCH_IN_LESSON_LANGUAGE.number) @@ -1787,7 +1830,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("click_switch_language_in_lesson") - assertThat(bundle).hasSize(19) + assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SWITCH_IN_LESSON_LANGUAGE.number) @@ -1815,7 +1858,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("ERROR_internal_logging_failure") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(INSTALL_ID_FOR_FAILED_ANALYTICS_LOG.number) @@ -1833,7 +1876,7 @@ class EventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("ERROR_internal_logging_failure") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(INSTALL_ID_FOR_FAILED_ANALYTICS_LOG.number) @@ -2260,7 +2303,7 @@ class EventBundleCreatorTest { this.switchToLanguage = switchToLanguage }.build() - private fun registerTestApplication() { + private fun registerTestApplication(context: Context) { val packageManager = Shadows.shadowOf(context.packageManager) val applicationInfo = ApplicationInfoBuilder.newBuilder() @@ -2320,18 +2363,32 @@ class EventBundleCreatorTest { ).build() private fun setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() { - TestModule.enableLearnerStudyAnalytics = false + TestModule.enableLoggingLearnerStudyIds = false setUpTestApplicationComponent() } private fun setUpTestApplicationComponentWithLearnerAnalyticsStudy() { - TestModule.enableLearnerStudyAnalytics = true + TestModule.enableLoggingLearnerStudyIds = true setUpTestApplicationComponent() } private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) - registerTestApplication() + registerTestApplication(context) + } + + 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( + DaggerEventBundleCreatorTest_TestApplicationComponent.builder() + .setApplication(testApplication) + .build() + .also { registerTestApplication(testApplication) } + ) } // TODO(#89): Move this to a common test application component. @@ -2340,7 +2397,7 @@ class EventBundleCreatorTest { internal companion object { // This is expected to be off by default, so this helps the tests above confirm that the // feature's default value is, indeed, off. - var enableLearnerStudyAnalytics = LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE + var enableLoggingLearnerStudyIds = LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE } @Provides @@ -2353,10 +2410,10 @@ class EventBundleCreatorTest { // within the same application instance. @Provides @Singleton - @EnableLearnerStudyAnalytics - fun provideEnableLearnerStudyAnalytics(): PlatformParameterValue { + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds(): PlatformParameterValue { // Snapshot the value so that it doesn't change between injection and use. - val enableFeature = enableLearnerStudyAnalytics + val enableFeature = enableLoggingLearnerStudyIds return object : PlatformParameterValue { override val value: Boolean = enableFeature } @@ -2375,6 +2432,8 @@ class EventBundleCreatorTest { fun build(): TestApplicationComponent } + fun getEventBundleCreator(): EventBundleCreator + fun inject(test: EventBundleCreatorTest) } @@ -2388,5 +2447,9 @@ class EventBundleCreatorTest { fun inject(test: EventBundleCreatorTest) { component.inject(test) } + + public override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + } } } diff --git a/utility/src/test/java/org/oppia/android/util/logging/KenyaAlphaEventBundleCreatorTest.kt b/utility/src/test/java/org/oppia/android/util/logging/KenyaAlphaEventBundleCreatorTest.kt index ff7a8196882..562f16b4337 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/KenyaAlphaEventBundleCreatorTest.kt +++ b/utility/src/test/java/org/oppia/android/util/logging/KenyaAlphaEventBundleCreatorTest.kt @@ -63,8 +63,8 @@ import org.oppia.android.app.model.EventLog.SwitchInLessonLanguageEventContext import org.oppia.android.app.model.EventLog.TopicContext import org.oppia.android.app.model.EventLog.VoiceoverActionContext import org.oppia.android.app.model.OppiaLanguage -import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics -import org.oppia.android.util.platformparameter.LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds +import org.oppia.android.util.platformparameter.LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE import org.oppia.android.util.platformparameter.PlatformParameterValue import org.robolectric.Shadows import org.robolectric.annotation.Config @@ -121,7 +121,7 @@ class KenyaAlphaEventBundleCreatorTest { @After fun tearDown() { - TestModule.enableLearnerStudyAnalytics = LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE + TestModule.enableLoggingLearnerStudyIds = LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE } @Test @@ -132,7 +132,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(EventLog.getDefaultInstance(), bundle) assertThat(typeName).isEqualTo("unknown_activity_context") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(0) assertThat(bundle).string("priority").isEqualTo("unspecified_priority") assertThat(bundle).integer("event_type").isEqualTo(ACTIVITYCONTEXT_NOT_SET.number) @@ -150,7 +150,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("unknown_activity_context") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACTIVITYCONTEXT_NOT_SET.number) @@ -190,7 +190,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_exploration_activity") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_EXPLORATION_ACTIVITY.number) @@ -214,7 +214,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_exploration_activity") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_EXPLORATION_ACTIVITY.number) @@ -240,7 +240,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_info_tab") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_INFO_TAB.number) @@ -259,7 +259,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_lessons_tab") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_LESSONS_TAB.number) @@ -278,7 +278,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_practice_tab") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_PRACTICE_TAB.number) @@ -297,7 +297,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_revision_tab") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_REVISION_TAB.number) @@ -316,7 +316,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_question_player") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_QUESTION_PLAYER.number) @@ -336,7 +336,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_story_activity") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_STORY_ACTIVITY.number) @@ -356,7 +356,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_concept_card") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_CONCEPT_CARD.number) @@ -375,7 +375,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_revision_card") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_REVISION_CARD.number) @@ -395,7 +395,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("close_revision_card") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(CLOSE_REVISION_CARD.number) @@ -415,7 +415,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("start_card_context") - assertThat(bundle).hasSize(16) + assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(START_CARD_CONTEXT.number) @@ -440,7 +440,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("start_card_context") - assertThat(bundle).hasSize(18) + assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(START_CARD_CONTEXT.number) @@ -467,7 +467,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("end_card_context") - assertThat(bundle).hasSize(16) + assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(END_CARD_CONTEXT.number) @@ -492,7 +492,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("end_card_context") - assertThat(bundle).hasSize(18) + assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(END_CARD_CONTEXT.number) @@ -519,7 +519,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("hint_offered_context") - assertThat(bundle).hasSize(16) + assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(HINT_UNLOCKED_CONTEXT.number) @@ -544,7 +544,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("hint_offered_context") - assertThat(bundle).hasSize(18) + assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(HINT_UNLOCKED_CONTEXT.number) @@ -571,7 +571,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("access_hint_context") - assertThat(bundle).hasSize(16) + assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACCESS_HINT_CONTEXT.number) @@ -596,7 +596,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("access_hint_context") - assertThat(bundle).hasSize(18) + assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACCESS_HINT_CONTEXT.number) @@ -623,7 +623,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("solution_offered_context") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SOLUTION_UNLOCKED_CONTEXT.number) @@ -647,7 +647,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("solution_offered_context") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SOLUTION_UNLOCKED_CONTEXT.number) @@ -673,7 +673,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("access_solution_context") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACCESS_SOLUTION_CONTEXT.number) @@ -697,7 +697,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("access_solution_context") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(ACCESS_SOLUTION_CONTEXT.number) @@ -723,7 +723,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("submit_answer_context") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SUBMIT_ANSWER_CONTEXT.number) @@ -749,7 +749,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("submit_answer_context") - assertThat(bundle).hasSize(19) + assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SUBMIT_ANSWER_CONTEXT.number) @@ -777,7 +777,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("play_voice_over_context") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(PLAY_VOICE_OVER_CONTEXT.number) @@ -803,7 +803,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("play_voice_over_context") - assertThat(bundle).hasSize(19) + assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(PLAY_VOICE_OVER_CONTEXT.number) @@ -831,7 +831,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("pause_voice_over_context") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(PAUSE_VOICE_OVER_CONTEXT.number) @@ -857,7 +857,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("pause_voice_over_context") - assertThat(bundle).hasSize(19) + assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(PAUSE_VOICE_OVER_CONTEXT.number) @@ -885,7 +885,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("app_in_background_context") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(APP_IN_BACKGROUND_CONTEXT.number) @@ -903,7 +903,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("app_in_background_context") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(APP_IN_BACKGROUND_CONTEXT.number) @@ -923,7 +923,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("app_in_foreground_context") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(APP_IN_FOREGROUND_CONTEXT.number) @@ -941,7 +941,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("app_in_foreground_context") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(APP_IN_FOREGROUND_CONTEXT.number) @@ -961,7 +961,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("exit_exploration_context") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(EXIT_EXPLORATION_CONTEXT.number) @@ -985,7 +985,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("exit_exploration_context") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(EXIT_EXPLORATION_CONTEXT.number) @@ -1011,7 +1011,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("finish_exploration_context") - assertThat(bundle).hasSize(15) + assertThat(bundle).hasSize(16) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(FINISH_EXPLORATION_CONTEXT.number) @@ -1035,7 +1035,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("finish_exploration_context") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(FINISH_EXPLORATION_CONTEXT.number) @@ -1061,7 +1061,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("resume_exploration_context") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(RESUME_EXPLORATION_CONTEXT.number) @@ -1079,7 +1079,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("resume_exploration_context") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(RESUME_EXPLORATION_CONTEXT.number) @@ -1099,7 +1099,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("start_over_exploration_context") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(START_OVER_EXPLORATION_CONTEXT.number) @@ -1117,7 +1117,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("start_over_exploration_context") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(START_OVER_EXPLORATION_CONTEXT.number) @@ -1137,7 +1137,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("delete_profile_context") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(DELETE_PROFILE_CONTEXT.number) @@ -1155,7 +1155,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("delete_profile_context") - assertThat(bundle).hasSize(11) + assertThat(bundle).hasSize(12) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(DELETE_PROFILE_CONTEXT.number) @@ -1175,7 +1175,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_home") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_HOME.number) @@ -1193,7 +1193,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("open_profile_chooser") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(OPEN_PROFILE_CHOOSER.number) @@ -1211,7 +1211,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("switch_in_lesson_language") - assertThat(bundle).hasSize(17) + assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SWITCH_IN_LESSON_LANGUAGE.number) @@ -1237,7 +1237,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("switch_in_lesson_language") - assertThat(bundle).hasSize(19) + assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(SWITCH_IN_LESSON_LANGUAGE.number) @@ -1265,7 +1265,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("failed_analytics_log") - assertThat(bundle).hasSize(9) + assertThat(bundle).hasSize(10) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(INSTALL_ID_FOR_FAILED_ANALYTICS_LOG.number) @@ -1283,7 +1283,7 @@ class KenyaAlphaEventBundleCreatorTest { val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("failed_analytics_log") - assertThat(bundle).hasSize(10) + assertThat(bundle).hasSize(11) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") assertThat(bundle).integer("event_type").isEqualTo(INSTALL_ID_FOR_FAILED_ANALYTICS_LOG.number) @@ -1544,12 +1544,12 @@ class KenyaAlphaEventBundleCreatorTest { } private fun setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() { - TestModule.enableLearnerStudyAnalytics = false + TestModule.enableLoggingLearnerStudyIds = false setUpTestApplicationComponent() } private fun setUpTestApplicationComponentWithLearnerAnalyticsStudy() { - TestModule.enableLearnerStudyAnalytics = true + TestModule.enableLoggingLearnerStudyIds = true setUpTestApplicationComponent() } @@ -1564,7 +1564,7 @@ class KenyaAlphaEventBundleCreatorTest { internal companion object { // This is expected to be off by default, so this helps the tests above confirm that the // feature's default value is, indeed, off. - var enableLearnerStudyAnalytics = LEARNER_STUDY_ANALYTICS_DEFAULT_VALUE + var enableLoggingLearnerStudyIds = LOGGING_LEARNER_STUDY_IDS_DEFAULT_VALUE } @Provides @@ -1577,10 +1577,10 @@ class KenyaAlphaEventBundleCreatorTest { // within the same application instance. @Provides @Singleton - @EnableLearnerStudyAnalytics - fun provideLearnerStudyAnalytics(): PlatformParameterValue { + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds(): PlatformParameterValue { // Snapshot the value so that it doesn't change between injection and use. - val enableFeature = enableLearnerStudyAnalytics + val enableFeature = enableLoggingLearnerStudyIds return object : PlatformParameterValue { override val value: Boolean = enableFeature } diff --git a/utility/src/test/java/org/oppia/android/util/logging/firebase/LogReportingModuleTest.kt b/utility/src/test/java/org/oppia/android/util/logging/firebase/LogReportingModuleTest.kt index 64ace233081..dcac9f91ee9 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/firebase/LogReportingModuleTest.kt +++ b/utility/src/test/java/org/oppia/android/util/logging/firebase/LogReportingModuleTest.kt @@ -27,7 +27,7 @@ import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsEvent import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE import org.oppia.android.util.platformparameter.EnableLanguageSelectionUi -import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics +import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds import org.oppia.android.util.platformparameter.PlatformParameterValue import org.oppia.android.util.platformparameter.SPLASH_SCREEN_WELCOME_MSG_DEFAULT_VALUE import org.oppia.android.util.platformparameter.SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS_DEFAULT_VALUE @@ -82,7 +82,7 @@ class LogReportingModuleTest { class TestPlatformParameterModule { companion object { - var forceLearnerAnalyticsStudy: Boolean = false + var forceLoggingLearnerStudyIds: Boolean = false } @Provides @@ -108,9 +108,9 @@ class LogReportingModuleTest { } @Provides - @EnableLearnerStudyAnalytics - fun provideLearnerStudyAnalytics(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter(forceLearnerAnalyticsStudy) + @EnableLoggingLearnerStudyIds + fun provideLoggingLearnerStudyIds(): PlatformParameterValue { + return PlatformParameterValue.createDefaultParameter(forceLoggingLearnerStudyIds) } } diff --git a/version.bzl b/version.bzl index d3c570eac3d..62d47832e26 100644 --- a/version.bzl +++ b/version.bzl @@ -10,13 +10,13 @@ the app (that are potentially not broadly released yet). """ MAJOR_VERSION = 0 -MINOR_VERSION = 10 +MINOR_VERSION = 11 # TODO(#4419): Remove the Kenya-specific alpha version code. -OPPIA_DEV_VERSION_CODE = 75 -OPPIA_DEV_KITKAT_VERSION_CODE = 74 -OPPIA_ALPHA_VERSION_CODE = 73 -OPPIA_ALPHA_KITKAT_VERSION_CODE = 72 -OPPIA_ALPHA_KENYA_VERSION_CODE = 71 -OPPIA_BETA_VERSION_CODE = 70 -OPPIA_GA_VERSION_CODE = 69 +OPPIA_DEV_VERSION_CODE = 89 +OPPIA_DEV_KITKAT_VERSION_CODE = 88 +OPPIA_ALPHA_VERSION_CODE = 87 +OPPIA_ALPHA_KITKAT_VERSION_CODE = 86 +OPPIA_ALPHA_KENYA_VERSION_CODE = 85 +OPPIA_BETA_VERSION_CODE = 84 +OPPIA_GA_VERSION_CODE = 83