From 144b4f4be14639a97d4e5105c8125fcc7c83a7c6 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:10:14 +0300 Subject: [PATCH] Create firebase auth wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor firebase auth wrapper Refactor dagger bindings for firestore dependencies Ensure firestore logs show in dev event logs view. Removed redundant bindings. Localisation updates from https://translatewiki.net. (#5209) Translation updates Fixes #4708: Don't submit answer if it's invalid according to the input (#5205) Fixes #4708: Don't submit answer if it's invalid according to the input or else after submitting the recycler view will not restore items on configuration change. See #4708 for more details Video demo: [before](https://drive.google.com/file/d/1bLgo-AYro0UbffR6X8nWo8Xv7oUuNJFa/view?usp=sharing) [after](https://drive.google.com/file/d/1Oek7j6dgjJmgasyyd9FHtQo4E7zTvEBd/view?usp=sharing) Continuation of PR #5202 since that one's no longer viable due to being force pushed in a repo sync through GitHub UI. - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [ ] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing Change unrelated to concerns like dark mode, RTL, accessibility. PR demonstration of tests passing. --------- Co-authored-by: Long Wei Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Fix part of #5195: using viewLifecycleOwner (#5207) It is not a good idea to use a fragment as a lifecycle owner when subscribing to liveData objects. It would be correct to use viewLifecycleOwner. Lint report before: Lint report after - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing Fix #5032 Images in Arabic (RTL) lessons are right-aligned rather than center-aligned. (#5212) Fixes #5032, To make the image center-aligned, we are adding styling to the HTML content image and removing the left margin by setting drawableLeft bound to 0 in RTL. - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [ ] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). LTR Potrait ![beforeLTRpotrait](https://github.com/oppia/oppia-android/assets/76042077/298fc2c0-7512-40dc-8d56-444fe55f4998) LTR Landscape ![LTRlandscape](https://github.com/oppia/oppia-android/assets/76042077/94e16ccb-052c-4723-b26e-00fa0591270d) RTL Potrait ![beforeRTLpotrait](https://github.com/oppia/oppia-android/assets/76042077/59efc1c5-c695-442a-9549-8aa353d61aed) RTL Landscape ![RTLlandscape](https://github.com/oppia/oppia-android/assets/76042077/dae3c7b8-ce4e-4968-8dea-c4bc23eaf75f) RTL Potrait ![Potrait_after](https://github.com/oppia/oppia-android/assets/76042077/d5115f63-f71c-44fa-bb60-639e8f8abcef) RTL Landscape ![landscapeafter](https://github.com/oppia/oppia-android/assets/76042077/9a37d6d1-dc80-489e-827c-8b208fa33d67) LTR Potrait ![LTRpotraitafter](https://github.com/oppia/oppia-android/assets/76042077/4d51cf7c-68eb-4924-aaaa-64cb44391081) LTR Landscape ![LTRLandscape](https://github.com/oppia/oppia-android/assets/76042077/802dcd37-5adb-4f5d-8a30-cfa110d76885) If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing fix: Fix Translate Wiki Issues with Splash Activity Screen Strings (#5219) When merged, this PR will: - Remove white spaces and new lines that were added to Splash Activity Screen strings by the [App/OS Deprecation Milestone 3 PR](https://github.com/oppia/oppia-android/pull/5096). This will allow TranslateWiki to translate them. See related [comment on the PR](https://github.com/oppia/oppia-android/commit/d471d79e7bcce6de8351cf045458af23711fcdb2#r130759117). - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Fix #5214, Fix Part of #1723 : Fix onboarding issue faced by new contributors (#5210) Fix #5214 : Wiki link not redirect to correct destination ( good-first-issue link) Fix Part of #1723 : Fix onboarding issue faced by new contributors. 1. Tell contributors firmly to use "Android Studio Bumblebee | 2021.1.1 Patch 3." 2. In the wiki, make sure to mention that you need to have JDK 11 to use oppia-android. 3. Concentrate on using robolectric instead of espresso and provide clear instructions for setting up tests. - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [ ] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). Fix #5204 - Inconsistent Layout Lint Warning (#5218) Fix #5204 - [Part of #5169] To address the lint warnings, two potential solutions were considered. 1. Initially, there was an option to include the missing IDs, ensuring consistency across different layout configurations [[**Link**](https://github.com/Rd4dev/oppia-android/commit/f8977e686fcbbf362aea4a8d1a472c487bafeb0f)]. However, it was observed that implementing this solution led to functional disruptions within the current working application, as confirmed through comprehensive testing procedures (refer to the attached assets for a detailed overview). https://github.com/oppia/oppia-android/assets/122200035/82172eaf-a555-4e76-b1ae-8f0db7ddb924 2. The second option was to suppress the lint warnings for the specific views in the layout-sw600dp configuration files [[**Link**](https://github.com/Rd4dev/oppia-android/commit/a0f5b1ed9100ecc0f578c6c4a38f4df6566724ec)] to maintain the seamless functionality of the application . This was achieved by incorporating the attribute **`tools:ignore="InconsistentLayout"`** for relevant views, without impacting the current operational stability of the application. https://github.com/oppia/oppia-android/assets/122200035/93a65422-3526-4d76-873f-c08d24abc3ee Taking the necessary action, I proceeded to implement the suppression of lint warnings by submitting a pull request. - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [ ] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Fix #5112: Rendering Inline SVGs (#5208) Fixes #5112 We discovered that `mathImg_` dimesions are in **ex**, and not **px**, and this unit is not natively rendered on android. This PR introduces a method that will convert ex to px and use the resulting dimesions to compute the rendering size of a math image to be rendered inline. The formula is based on the [androidSvg library](https://github.com/oppia/androidsvg/blob/5bc9c7553e94c3476e8ea32baea3c77567228fcd/androidsvg/src/main/java/com/caverock/androidsvg/androidrendering/SVGAndroidRenderer.java#L5018) that is also based on CSS3 specs. To preserve the size computation of block SVGs, we have created a seperate method that handles the calculation of rendering dimensions for inline SVGs. We have only tested these changes through visual observation, and confirming that the images scale correctly when reading text size is changed. - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). |Before|After| |--|--| |![IMG_0705](https://github.com/oppia/oppia-android/assets/99066793/62e00542-091c-48b1-9e63-aa1ca3c9abff)|![Screenshot_1698283405](https://github.com/oppia/oppia-android/assets/59600948/e1c534ee-b379-461c-bc39-7854fe57dc70)| |![Screenshot_1698284125](https://github.com/oppia/oppia-android/assets/59600948/3f47014d-f4e3-4430-9e1e-d4b4609014e7)|![Screenshot_1698283411](https://github.com/oppia/oppia-android/assets/59600948/786445ec-0b75-41af-882b-ae93e2da9b4d)| |![Screenshot_1697714168](https://github.com/oppia/oppia-android/assets/59600948/9fa8daeb-25ca-4b90-ac66-a6228c81f120)|![Screenshot_1698284519](https://github.com/oppia/oppia-android/assets/59600948/0b39d37d-1471-4ecf-8626-13a01ec35c3e)| |![Screenshot_1697745323](https://github.com/oppia/oppia-android/assets/59600948/8152c286-8845-4f3b-8ecf-93acd03dbc3e)|![Screenshot_1698284666](https://github.com/oppia/oppia-android/assets/59600948/dbff75ba-bb08-4855-b49a-0e4d337c478b)| |![Screenshot_1697714820](https://github.com/oppia/oppia-android/assets/59600948/65b7eadc-c8b0-4963-8dbf-d4cd19590e98)|![Screenshot_1698284933](https://github.com/oppia/oppia-android/assets/59600948/9b517dc5-fc75-43f8-b6af-a4e24b1cde28)| Images scaled to Largest text and display size, and dark mode |||| |--|--|--| |![image](https://github.com/oppia/oppia-android/assets/59600948/cedd58b2-a16f-4ed7-9856-df9371242d3a)|![image](https://github.com/oppia/oppia-android/assets/59600948/acf79348-72cb-4732-b91a-1f934861d9f0)|![image](https://github.com/oppia/oppia-android/assets/59600948/fa07209d-fb54-4f21-a267-cc9bd7d26177)| |![image](https://github.com/oppia/oppia-android/assets/59600948/241f8501-21ce-488f-be6e-a6f2b4bde198)|![image](https://github.com/oppia/oppia-android/assets/59600948/82d0a270-e486-4553-96ae-559ad7b784a2)|![image](https://github.com/oppia/oppia-android/assets/59600948/6080aff6-aff0-46ad-b30c-88e086aeeac7)| --------- Co-authored-by: Ben Henning Fix #3596: Adds Audio Loading UI (#5179) Fixes #3596 This PR adds an indeterminate progress bar when the audio is loading, before it starts playing. - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing https://github.com/oppia/oppia-android/assets/84731134/5c6fe393-5d89-4bda-a144-57bdc42a9c57 --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Fix #5226: EnableContinueButtonAnimation Feature Flag (#5228) Fix #5226 Remove declarations and usages of the enableContinueButtonAnimation PlatformParameter. (A) Removed declaration of TestPlatformParameterModule.forceEnableContinueButtonAnimation() from all test functions namely: testContinueInteractionAnim_openPrototypeExp_checkContinueButtonAnimatesAfter45Seconds(), testConIntAnim_openProtExp_orientLandscapeAfter30Sec_checkAnimHasNotStarted(), testConIntAnim_openProtExp_orientLandAfter30Sec_checkAnimStartsIn15SecAfterOrientChange(), testContNavBtnAnim_openMathExp_checkContNavBtnAnimatesAfter45Seconds(), testContNavBtnAnim_openMathExp_playThroughSecondState_checkContBtnDoesNotAnimateAfter45Sec(), testConIntAnim_openFractions_expId1_checkButtonDoesNotAnimate() in StateFragmentLocalTest.kt file (B) Removed function declaration TestPlatformParameterModule.forceEnableContinueButtonAnimation(false) from setup() function in StateFragmentTest.kt 
 (C) Removed function declaration TestPlatformParameterModule.forceEnableContinueButtonAnimation(false) from setup() function in ExploreActivityTest.kt - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). Fix #5230: VectorDrawableCompat error lint (#5237) Fix #5230: VectorDrawableCompat Lint error Warning: VectorDrawableCompat Fix: opened the app's build.gradle file. Inside the android block, added the vectorDrawables section under defaultConfig with the code below: android { ... defaultConfig { ... vectorDrawables { useSupportLibrary true } } } - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). Fix #3345 Images are not fully displayed (#5234) Fixes #3345, substracting drawableWidth from maxContentItemPadding only if drawableWidth >= (maxcontentWidth - maxContentItemPadding) would solve the issue. - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). https://user-images.githubusercontent.com/82181327/122643554-54f1ac00-d108-11eb-906f-6748d2facf2f.jpg https://user-images.githubusercontent.com/101634267/245153650-cd8335df-01ec-47e6-97b2-7f7244d95efb.jpg https://user-images.githubusercontent.com/101634267/245153665-cb9506d2-6d0d-4231-8a42-5f8ab15cf4d4.jpg ![after_image_cut](https://github.com/oppia/oppia-android/assets/76042077/babacf98-1ef6-4f29-98a4-cc46c5f53303) ![after_imagecut](https://github.com/oppia/oppia-android/assets/76042077/e08cf8d0-5f25-46bb-afbe-124952d19bfc) ![after_image_cut2](https://github.com/oppia/oppia-android/assets/76042077/85e950df-1ae9-48f2-b46c-c610b838593a) ![After_RTL](https://github.com/oppia/oppia-android/assets/76042077/586ade28-a45e-4f0a-9ac8-51457ccf8b5a) ![after_no_effect_RTL](https://github.com/oppia/oppia-android/assets/76042077/75820a6d-cc76-4ec0-b90e-ede7e5a72643) If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing Localisation updates from https://translatewiki.net. (#5242) Translation updates Fix #5137: Upgrade builds to target SDK 33 (#5222) Fixes #5137 Partially mitigates #5233 This updates all build & target SDKs for Gradle & Bazel builds to now target SDK 33, per the new Play Store mandate (see https://support.google.com/googleplay/android-developer/answer/11926878?hl=en). The analysis of SDK 33 features, potential issues, and potential mitigations are described in #5137. It was determined that there are no obvious code changes needed beyond the minimum to target SDK 33. We'll be relying on some platform-level regression testing plus the Play Console's pre-submit app analysis report for finalizing the go/no-go decision for SDK 33 support. Testing consisted of manually playing through the app and seeing if there were any issues using emulators both with SDK 33 and lower versions. #5137 documents the issues found and their ultimate causes. Since no functionality changes were needed for SDK 33, no tests needed updating. Separately, tests are still running on SDK 30 due to being stuck (see #4748). One issue unrelated to SDK 33 was observed: when playing through the prototype lesson, I once again brought the app into a broken state wherein I couldn't leave the lesson via navigation. We've hit this issue a few times, but still don't know the root cause. #5233 was filed and a mitigation was introduced in this PR (+ a test for it): if the exploration player observes a failure when trying to stop the player session (for whatever reason), it still proceeds with its "end of activity" flow (e.g. finishing the activity). This provides at least an escape hatch for users who somehow hit this state. Note that this isn't a proper fix for #5233 given the lack of a known root cause, so this is only considered a mitigation. The new test was verified as failing if the mitigation is omitted: ![image](https://github.com/oppia/oppia-android/assets/12983742/10ddbf85-332c-4469-8efa-483a967170f9) Note that ``ExplorationActivityTest``'s setup was tweaked to change the parent screen since this affects back navigation routing. I opted to using lessons tab as the parent screen since it's the most common route for users to hit, so it makes sense for our tests to default to that if they don't specifically need to use a different route (and none of the existing tests seemed to have an issue with this change). - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). N/A -- This isn't changing any UIs directly, and per the analysis in 33 that would affect Oppia. Technical Analytics: Milestone 1 - Add Feature Flag Statuses and Ability To Sync Them to Cache Store (#5203) When merged, this PR will: - Create a new `FeatureFlagConstants.kt` file to contain all feature flags. - Create feature flag sync status trackers for each existing feature flag. - Modify the `PlatformParameterSyncUpWorker` to allow status trackers of all feature flags to be synced and cached in the PersistentCacheStore. - Merge the `TestBooleanPlatformParameter`, `TestStringPlatformParameter` and the `TestIntegerPlatformParameter` files into the `TestPlatformParameterConstants` file. - Add syncing with the web for the `EnableDownloadsSupport`, `EnableEditAccountsOptionsUi`, `EnableSpotlightUi`, `EnableExtraTopicTabsUi`, `EnableInteractionConfigChangeStateRetention`, and `EnableAppAndOsDeprecation` feature flags. - Write tests for the changes made. - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Co-authored-by: Ben Henning Refactor TestAuthenticationModule.kt to provide FirebaseAuthWrapper Ensure firestore logs show in dev event logs view. Removed redundant bindings. Remove FakeAuthenticationController and refactored its usages to the new FakeFirebaseWrapperImpl Remove DebugFirestoreEventLogger.kt and usages. Update AuthenticationControllerTest tests Fix static analysis checks Fix bazel build errors Reformat auth/BUILD.bazel Create wrapper for firebase auth instance Fix lint errors Fix failing tests Fix failing lint checks Fix test failures Fix BUILD file formatting Fix DebugFirestoreEventLoggerImplTest Fake a firebase instance. Fix build failures Fix test file exemption warnings Fix bazel build input file Fix bazel build input file Fix failing tests --- .github/CODEOWNERS | 4 +- .../action.yml | 4 +- BUILD.bazel | 4 +- WORKSPACE | 6 +- app/build.gradle | 5 +- app/src/main/AppAndroidManifest.xml | 2 +- app/src/main/DatabindingAdaptersManifest.xml | 2 +- app/src/main/DatabindingResourcesManifest.xml | 2 +- app/src/main/RecyclerviewAdaptersManifest.xml | 2 +- app/src/main/ViewModelManifest.xml | 2 +- app/src/main/ViewModelsManifest.xml | 2 +- app/src/main/ViewsManifest.xml | 2 +- .../app/customview/ContinueButtonView.kt | 30 ++-- .../vieweventlogs/ViewEventLogsViewModel.kt | 4 +- .../ExplorationActivityPresenter.kt | 34 ++-- .../player/state/StateFragmentPresenter.kt | 10 +- .../app/player/state/StateViewModel.kt | 4 +- .../app/survey/SurveyFragmentPresenter.kt | 9 +- .../administrator_controls_activity.xml | 3 +- .../main/res/layout-sw600dp/help_activity.xml | 9 +- .../res/layout-sw600dp/option_activity.xml | 6 +- .../options_without_drawer_activity.xml | 7 +- app/src/main/res/layout/audio_fragment.xml | 15 ++ app/src/main/res/values-pcm-rNG/strings.xml | 127 ++++++++++--- app/src/main/res/values/component_colors.xml | 1 + app/src/main/res/values/dimens.xml | 8 +- app/src/main/res/values/strings.xml | 56 ++---- .../devoptions/ViewEventLogsFragmentTest.kt | 38 ++-- .../exploration/ExplorationActivityTest.kt | 76 +++++++- .../app/player/state/StateFragmentTest.kt | 19 +- .../player/state/StateFragmentLocalTest.kt | 6 - app/src/test/resources/robolectric.properties | 2 +- build_flavors.bzl | 14 +- .../oppia/android/config/AndroidManifest.xml | 2 +- data/build.gradle | 4 +- .../src/test/resources/robolectric.properties | 2 +- domain/build.gradle | 4 +- domain/src/main/AndroidManifest.xml | 2 +- .../domain/auth/AuthenticationController.kt | 23 ++- .../domain/auth/AuthenticationModule.kt | 8 +- .../domain/auth/AuthenticationWrapper.kt | 14 -- .../org/oppia/android/domain/auth/BUILD.bazel | 44 ++++- .../domain/auth/FirebaseAuthInstance.kt | 8 + .../auth/FirebaseAuthInstanceWrapper.kt | 7 + .../auth/FirebaseAuthInstanceWrapperImpl.kt | 11 ++ .../domain/auth/FirebaseAuthWrapper.kt | 10 ++ .../domain/auth/FirebaseAuthWrapperImpl.kt | 28 +++ .../domain/auth/FirebaseUserWrapper.kt | 8 + .../analytics/FirestoreDataController.kt | 12 +- .../PlatformParameterAlphaKenyaModule.kt | 65 ++++--- .../PlatformParameterAlphaModule.kt | 67 ++++--- .../PlatformParameterModule.kt | 68 ++++--- .../PlatformParameterSingletonImpl.kt | 12 +- .../syncup/PlatformParameterSyncUpWorker.kt | 3 + .../domain/audio/AudioPlayerControllerTest.kt | 12 +- .../auth/AuthenticationControllerTest.kt | 148 +++++++++++++++ .../domain/auth/AuthenticationModuleTest.kt | 45 ++--- .../auth/FirebaseAuthWrapperImplTest.kt | 3 + .../analytics/FirestoreDataControllerTest.kt | 14 +- .../analytics/SurveyEventsLoggerTest.kt | 14 +- .../LogReportWorkManagerInitializerTest.kt | 14 +- .../loguploader/LogUploadWorkerTest.kt | 18 +- .../PlatformParameterSyncUpWorkerTest.kt | 84 +++++++++ .../ProfileManagementControllerTest.kt | 12 +- .../domain/survey/SurveyControllerTest.kt | 14 +- .../survey/SurveyProgressControllerTest.kt | 14 +- .../src/test/resources/robolectric.properties | 2 +- instrumentation/BUILD.bazel | 2 +- model/src/main/proto/platform_parameter.proto | 13 ++ scripts/assets/test_file_exemptions.textproto | 17 +- testing/build.gradle | 4 +- .../testing/FakeAuthenticationController.kt | 42 ----- .../testing/FakeFirebaseAuthWrapperImpl.kt | 37 ++++ .../testing/FakeFirestoreEventLogger.kt | 8 +- .../testing/TestAuthenticationModule.kt | 8 +- .../android/testing/TestLogReportingModule.kt | 8 +- .../testing/platformparameter/BUILD.bazel | 4 +- .../TestBooleanPlatformParameter.kt | 24 --- .../TestIntegerPlatformParameter.kt | 24 --- .../TestPlatformParameterConstants.kt | 68 +++++++ .../TestPlatformParameterModule.kt | 17 -- .../TestStringPlatformParameter.kt | 24 --- .../FakeAuthenticationControllerTest.kt | 168 ------------------ .../FakeFirebaseAuthWrapperImplTest.kt | 115 ++++++++++++ .../testing/FakeFirestoreEventLoggerTest.kt | 4 +- .../testing/TestAuthenticationModuleTest.kt | 26 +-- .../src/test/resources/robolectric.properties | 2 +- utility/build.gradle | 6 +- utility/src/main/AndroidManifest.xml | 2 +- .../android/util/logging/firebase/BUILD.bazel | 45 +++-- .../firebase/DebugFirestoreEventLogger.kt | 16 -- .../firebase/DebugFirestoreEventLoggerImpl.kt | 8 +- .../DebugFirestoreInstanceWrapperImpl.kt | 17 ++ .../firebase/DebugLogReportingModule.kt | 7 +- .../firebase/FirestoreEventLoggerProdImpl.kt | 32 +--- .../logging/firebase/FirestoreInstance.kt | 8 + .../firebase/FirestoreInstanceWrapper.kt | 7 + .../firebase/FirestoreInstanceWrapperImpl.kt | 13 ++ .../logging/firebase/LogReportingModule.kt | 8 +- .../android/util/parser/html/HtmlParser.kt | 12 +- .../util/parser/image/UrlImageParser.kt | 19 +- .../util/parser/svg/ScalableVectorGraphic.kt | 60 +++++-- .../util/parser/svg/SvgPictureDrawable.kt | 14 +- .../platformparameter/FeatureFlagConstants.kt | 157 ++++++++++++++++ .../PlatformParameterConstants.kt | 134 -------------- .../PlatformParameterValue.kt | 11 +- .../util/logging/EventBundleCreatorTest.kt | 6 +- .../KenyaAlphaEventBundleCreatorTest.kt | 6 +- .../android/util/logging/firebase/BUILD.bazel | 2 +- .../DebugFirestoreEventLoggerImplTest.kt | 27 +-- .../src/test/resources/robolectric.properties | 2 +- wiki/Installing-Oppia-Android.md | 62 +++++-- wiki/Oppia-Android-Testing.md | 27 +-- 113 files changed, 1605 insertions(+), 1029 deletions(-) delete mode 100644 domain/src/main/java/org/oppia/android/domain/auth/AuthenticationWrapper.kt create mode 100644 domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstance.kt create mode 100644 domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapper.kt create mode 100644 domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapperImpl.kt create mode 100644 domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapper.kt create mode 100644 domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapperImpl.kt create mode 100644 domain/src/main/java/org/oppia/android/domain/auth/FirebaseUserWrapper.kt create mode 100644 domain/src/test/java/org/oppia/android/domain/auth/AuthenticationControllerTest.kt create mode 100644 domain/src/test/java/org/oppia/android/domain/auth/FirebaseAuthWrapperImplTest.kt delete mode 100644 testing/src/main/java/org/oppia/android/testing/FakeAuthenticationController.kt create mode 100644 testing/src/main/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImpl.kt delete mode 100644 testing/src/main/java/org/oppia/android/testing/platformparameter/TestBooleanPlatformParameter.kt delete mode 100644 testing/src/main/java/org/oppia/android/testing/platformparameter/TestIntegerPlatformParameter.kt create mode 100644 testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterConstants.kt delete mode 100644 testing/src/main/java/org/oppia/android/testing/platformparameter/TestStringPlatformParameter.kt delete mode 100644 testing/src/test/java/org/oppia/android/testing/FakeAuthenticationControllerTest.kt create mode 100644 testing/src/test/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImplTest.kt delete mode 100644 utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLogger.kt create mode 100644 utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreInstanceWrapperImpl.kt create mode 100644 utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstance.kt create mode 100644 utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapper.kt create mode 100644 utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapperImpl.kt create mode 100644 utility/src/main/java/org/oppia/android/util/platformparameter/FeatureFlagConstants.kt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 31785d48c98..1249412ca2c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -92,8 +92,8 @@ # All domain and utility-specific shared test infrastructure. /testing/src/main/java/org/oppia/android/testing/FakeAnalyticsEventLogger.kt @oppia/android-app-infrastructure-reviewers -/testing/src/main/java/org/oppia/android/testing/FakeAuthenticationController.kt @oppia/android-app-infrastructure-reviewers /testing/src/main/java/org/oppia/android/testing/FakeExceptionLogger.kt @oppia/android-app-infrastructure-reviewers +/testing/src/main/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImpl.kt @oppia/android-app-infrastructure-reviewers /testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt @oppia/android-app-infrastructure-reviewers /testing/src/main/java/org/oppia/android/testing/FakePerformanceMetricAssessor.kt @oppia/android-app-infrastructure-reviewers /testing/src/main/java/org/oppia/android/testing/FakePerformanceMetricsEventLogger.kt @oppia/android-app-infrastructure-reviewers @@ -102,8 +102,8 @@ /testing/src/main/java/org/oppia/android/testing/TestImageLoaderModule.kt @oppia/android-app-infrastructure-reviewers /testing/src/main/java/org/oppia/android/testing/TestLogReportingModule.kt @oppia/android-app-infrastructure-reviewers /testing/src/test/java/org/oppia/android/testing/FakeAnalyticsEventLoggerTest.kt @oppia/android-app-infrastructure-reviewers -/testing/src/test/java/org/oppia/android/testing/FakeAuthenticationControllerTest.kt @oppia/android-app-infrastructure-reviewers /testing/src/test/java/org/oppia/android/testing/FakeExceptionLoggerTest.kt @oppia/android-app-infrastructure-reviewers +/testing/src/test/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImplTest.kt @oppia/android-app-infrastructure-reviewers /testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt @oppia/android-app-infrastructure-reviewers /testing/src/test/java/org/oppia/android/testing/FakePerformanceMetricAssessorTest.kt @oppia/android-app-infrastructure-reviewers /testing/src/test/java/org/oppia/android/testing/FakePerformanceMetricsEventLoggerTest.kt @oppia/android-app-infrastructure-reviewers diff --git a/.github/actions/set-up-android-bazel-build-environment/action.yml b/.github/actions/set-up-android-bazel-build-environment/action.yml index 6b3f5cf1155..6afe9f398a8 100644 --- a/.github/actions/set-up-android-bazel-build-environment/action.yml +++ b/.github/actions/set-up-android-bazel-build-environment/action.yml @@ -72,9 +72,9 @@ runs: $ANDROID_HOME/cmdline-tools/tools/bin/sdkmanager --install "platform-tools" shell: bash - - name: Install SDK 31 + - name: Install SDK 33 run: | - $ANDROID_HOME/cmdline-tools/tools/bin/sdkmanager --install "platforms;android-31" + $ANDROID_HOME/cmdline-tools/tools/bin/sdkmanager --install "platforms;android-33" shell: bash - name: Install build tools 29.0.2 diff --git a/BUILD.bazel b/BUILD.bazel index 28daa546184..cbd6507132e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -118,14 +118,14 @@ package_group( "flavor": "oppia", "min_sdk_version": 21, "multidex": "native", - "target_sdk_version": 31, + "target_sdk_version": 33, }, { "flavor": "oppia_kitkat", "main_dex_list": "//:config/kitkat_main_dex_class_list.txt", "min_sdk_version": 19, "multidex": "manual_main_dex", - "target_sdk_version": 31, + "target_sdk_version": 33, }, ] ] diff --git a/WORKSPACE b/WORKSPACE index 0c20dcd36a1..d1d218526cf 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -11,7 +11,7 @@ load("//third_party:versions.bzl", "HTTP_DEPENDENCY_VERSIONS", "get_maven_depend # TODO(#1542): Sync Android SDK version with the manifest. android_sdk_repository( name = "androidsdk", - api_level = 31, + api_level = 33, build_tools_version = "29.0.2", ) @@ -125,9 +125,9 @@ git_repository( # to correctly size in-line SVGs (such as those needed for LaTeX-based math expressions). git_repository( name = "androidsvg", - commit = "1265eb1087056cf3fc2e10442e5545bc65c109ce", + commit = "5bc9c7553e94c3476e8ea32baea3c77567228fcd", remote = "https://github.com/oppia/androidsvg", - shallow_since = "1686302944 -0700", + shallow_since = "1686304726 -0700", ) git_repository( diff --git a/app/build.gradle b/app/build.gradle index 40d8704771c..d97b6cbac28 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,12 +6,12 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 31 + compileSdkVersion 33 buildToolsVersion "29.0.2" defaultConfig { applicationId "org.oppia.android" minSdkVersion 19 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 1 versionName "1.0" multiDexEnabled true @@ -23,6 +23,7 @@ android { includeCompileClasspath true } } + vectorDrawables { useSupportLibrary true } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/app/src/main/AppAndroidManifest.xml b/app/src/main/AppAndroidManifest.xml index 9a5789e1872..211884a0158 100644 --- a/app/src/main/AppAndroidManifest.xml +++ b/app/src/main/AppAndroidManifest.xml @@ -1,5 +1,5 @@ + android:targetSdkVersion="33" /> diff --git a/app/src/main/DatabindingAdaptersManifest.xml b/app/src/main/DatabindingAdaptersManifest.xml index 0974b6b3aa6..d3e60f6d5f4 100644 --- a/app/src/main/DatabindingAdaptersManifest.xml +++ b/app/src/main/DatabindingAdaptersManifest.xml @@ -1,5 +1,5 @@ + android:targetSdkVersion="33" /> diff --git a/app/src/main/DatabindingResourcesManifest.xml b/app/src/main/DatabindingResourcesManifest.xml index 0c3dd8fa35a..c9f98dbf248 100644 --- a/app/src/main/DatabindingResourcesManifest.xml +++ b/app/src/main/DatabindingResourcesManifest.xml @@ -1,5 +1,5 @@ + android:targetSdkVersion="33" /> diff --git a/app/src/main/RecyclerviewAdaptersManifest.xml b/app/src/main/RecyclerviewAdaptersManifest.xml index f2a3273e765..6585b5ea24c 100644 --- a/app/src/main/RecyclerviewAdaptersManifest.xml +++ b/app/src/main/RecyclerviewAdaptersManifest.xml @@ -1,5 +1,5 @@ + android:targetSdkVersion="33" /> diff --git a/app/src/main/ViewModelManifest.xml b/app/src/main/ViewModelManifest.xml index 07603e895d8..c6c3e62e26b 100644 --- a/app/src/main/ViewModelManifest.xml +++ b/app/src/main/ViewModelManifest.xml @@ -3,5 +3,5 @@ + android:targetSdkVersion="33" /> diff --git a/app/src/main/ViewModelsManifest.xml b/app/src/main/ViewModelsManifest.xml index 84693784fd8..e210893ecd0 100644 --- a/app/src/main/ViewModelsManifest.xml +++ b/app/src/main/ViewModelsManifest.xml @@ -3,5 +3,5 @@ + android:targetSdkVersion="33" /> diff --git a/app/src/main/ViewsManifest.xml b/app/src/main/ViewsManifest.xml index 340e35afe29..b77df4edb19 100644 --- a/app/src/main/ViewsManifest.xml +++ b/app/src/main/ViewsManifest.xml @@ -3,5 +3,5 @@ + android:targetSdkVersion="33" /> diff --git a/app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt b/app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt index bd3e44d8865..dd599820187 100644 --- a/app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt +++ b/app/src/main/java/org/oppia/android/app/customview/ContinueButtonView.kt @@ -11,8 +11,6 @@ import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory import org.oppia.android.app.view.ViewComponentFactory import org.oppia.android.app.view.ViewComponentImpl import org.oppia.android.domain.oppialogger.OppiaLogger -import org.oppia.android.util.platformparameter.EnableContinueButtonAnimation -import org.oppia.android.util.platformparameter.PlatformParameterValue import org.oppia.android.util.system.OppiaClock import javax.inject.Inject @@ -25,12 +23,14 @@ class ContinueButtonView @JvmOverloads constructor( defStyleAttr: Int = R.style.StateButtonActive ) : androidx.appcompat.widget.AppCompatButton(context, attrs, defStyleAttr) { - @field:[Inject EnableContinueButtonAnimation] - lateinit var enableContinueButtonAnimation: PlatformParameterValue - @Inject lateinit var fragment: Fragment - @Inject lateinit var oppiaClock: OppiaClock - @Inject lateinit var lifecycleSafeTimerFactory: LifecycleSafeTimerFactory - @Inject lateinit var oppiaLogger: OppiaLogger + @Inject + lateinit var fragment: Fragment + @Inject + lateinit var oppiaClock: OppiaClock + @Inject + lateinit var lifecycleSafeTimerFactory: LifecycleSafeTimerFactory + @Inject + lateinit var oppiaLogger: OppiaLogger private var shouldAnimateContinueButtonLateinit: Boolean? = null private val shouldAnimateContinueButton: Boolean @@ -119,13 +119,11 @@ class ContinueButtonView @JvmOverloads constructor( private fun startAnimating() { val animation = AnimationUtils.loadAnimation(context, R.anim.wobble_button_animation) - if (enableContinueButtonAnimation.value) { - startAnimation(animation) - // Repeat the animation after a fixed interval. - lifecycleSafeTimerFactory.createTimer(INTERVAL_BETWEEN_CONTINUE_BUTTON_ANIM_MS) - .observe(fragment) { - startAnimating() - } - } + startAnimation(animation) + // Repeat the animation after a fixed interval. + lifecycleSafeTimerFactory.createTimer(INTERVAL_BETWEEN_CONTINUE_BUTTON_ANIM_MS) + .observe(fragment) { + startAnimating() + } } } diff --git a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsViewModel.kt b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsViewModel.kt index db1152ec2cc..d13d6dedccd 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsViewModel.kt @@ -5,7 +5,7 @@ import org.oppia.android.app.translation.AppLanguageResourceHandler import org.oppia.android.app.viewmodel.ObservableViewModel import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.logging.firebase.DebugAnalyticsEventLogger -import org.oppia.android.util.logging.firebase.DebugFirestoreEventLogger +import org.oppia.android.util.logging.firebase.DebugFirestoreEventLoggerImpl import javax.inject.Inject /** @@ -15,7 +15,7 @@ import javax.inject.Inject @FragmentScope class ViewEventLogsViewModel @Inject constructor( debugAnalyticsEventLogger: DebugAnalyticsEventLogger, - debugFirestoreEventLogger: DebugFirestoreEventLogger, + debugFirestoreEventLogger: DebugFirestoreEventLoggerImpl, private val machineLocale: OppiaLocale.MachineLocale, private val resourceHandler: AppLanguageResourceHandler ) : ObservableViewModel() { diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt index 3efadb513de..cd4a33b2d24 100644 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt @@ -286,25 +286,25 @@ class ExplorationActivityPresenter @Inject constructor( fun stopExploration(isCompletion: Boolean) { fontScaleConfigurationUtil.adjustFontScale(activity, ReadingTextSize.MEDIUM_TEXT_SIZE) - explorationDataController.stopPlayingExploration(isCompletion).toLiveData() - .observe( - activity, - { - when (it) { - is AsyncResult.Pending -> oppiaLogger.d("ExplorationActivity", "Stopping exploration") - is AsyncResult.Failure -> - oppiaLogger.e("ExplorationActivity", "Failed to stop exploration", it.error) - is AsyncResult.Success -> { - oppiaLogger.d("ExplorationActivity", "Successfully stopped exploration") - if (isCompletion) { - maybeShowSurveyDialog(profileId, topicId) - } else { - backPressActivitySelector() - } - } + explorationDataController.stopPlayingExploration(isCompletion).toLiveData().observe(activity) { + when (it) { + is AsyncResult.Pending -> + oppiaLogger.d("ExplorationActivity", "Stopping exploration") + is AsyncResult.Failure -> { + oppiaLogger.e("ExplorationActivity", "Failed to stop exploration", it.error) + // Allow the user to always exit if they get into a broken state. + backPressActivitySelector() + } + is AsyncResult.Success -> { + oppiaLogger.d("ExplorationActivity", "Successfully stopped exploration") + if (isCompletion) { + maybeShowSurveyDialog(profileId, topicId) + } else { + backPressActivitySelector() } } - ) + } + } } fun onKeyboardAction(actionCode: Int) { diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt index d13a5dca065..9aabc25f075 100755 --- a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt @@ -202,7 +202,10 @@ class StateFragmentPresenter @Inject constructor( fun onSubmitButtonClicked() { hideKeyboard() - handleSubmitAnswer(viewModel.getPendingAnswer(recyclerViewAssembler::getPendingAnswerHandler)) + val answer = viewModel.getPendingAnswer(recyclerViewAssembler::getPendingAnswerHandler) + if (answer != null) { + handleSubmitAnswer(answer) + } } fun onResponsesHeaderClicked() { @@ -215,7 +218,10 @@ class StateFragmentPresenter @Inject constructor( fun handleKeyboardAction() { hideKeyboard() if (viewModel.getCanSubmitAnswer().get() == true) { - handleSubmitAnswer(viewModel.getPendingAnswer(recyclerViewAssembler::getPendingAnswerHandler)) + val answer = viewModel.getPendingAnswer(recyclerViewAssembler::getPendingAnswerHandler) + if (answer != null) { + handleSubmitAnswer(answer) + } } } 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 54109859994..82071abed1f 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 @@ -101,12 +101,12 @@ class StateViewModel @Inject constructor( fun getPendingAnswer( retrieveAnswerHandler: (List) -> InteractionAnswerHandler? - ): UserAnswer { + ): UserAnswer? { return getPendingAnswerWithoutError( retrieveAnswerHandler( getAnswerItemList() ) - ) ?: UserAnswer.getDefaultInstance() + ) } fun canQuicklyToggleBetweenSwahiliAndEnglish( diff --git a/app/src/main/java/org/oppia/android/app/survey/SurveyFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/survey/SurveyFragmentPresenter.kt index 2d2b1ccca0f..88789ee130d 100644 --- a/app/src/main/java/org/oppia/android/app/survey/SurveyFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/survey/SurveyFragmentPresenter.kt @@ -175,11 +175,10 @@ class SurveyFragmentPresenter @Inject constructor( private fun subscribeToCurrentQuestion() { ephemeralQuestionLiveData.observe( - fragment, - { - processEphemeralQuestionResult(it) - } - ) + fragment.viewLifecycleOwner + ) { + processEphemeralQuestionResult(it) + } } private fun processEphemeralQuestionResult(result: AsyncResult) { diff --git a/app/src/main/res/layout-sw600dp/administrator_controls_activity.xml b/app/src/main/res/layout-sw600dp/administrator_controls_activity.xml index c3602b0e946..9b778df86f3 100644 --- a/app/src/main/res/layout-sw600dp/administrator_controls_activity.xml +++ b/app/src/main/res/layout-sw600dp/administrator_controls_activity.xml @@ -85,7 +85,8 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/administrator_controls_guideline" - app:layout_constraintTop_toBottomOf="@id/extra_controls_title" /> + app:layout_constraintTop_toBottomOf="@id/extra_controls_title" + tools:ignore="InconsistentLayout" /> + app:srcCompat="@drawable/ic_arrow_back_black_24_dp" + tools:ignore="InconsistentLayout" /> + app:layout_constraintTop_toTopOf="parent" + tools:ignore="InconsistentLayout" /> + app:layout_constraintTop_toBottomOf="@id/help_multipane_options_title_textview" + tools:ignore="InconsistentLayout" /> + app:layout_constraintTop_toTopOf="parent" + tools:ignore="InconsistentLayout" /> + app:layout_constraintTop_toBottomOf="@id/options_activity_selected_options_title" + tools:ignore="InconsistentLayout" /> @@ -44,7 +45,8 @@ android:textSize="18sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/multipane_guideline" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:ignore="InconsistentLayout" /> + app:layout_constraintTop_toBottomOf="@id/options_activity_selected_options_title" + tools:ignore="InconsistentLayout" /> + + Navigation header @@ -9,6 +11,7 @@ My Downloads Help Lesson Player + play exploration Help Close Change Profile @@ -19,18 +22,20 @@ Play di audio Pause di audio %s audio no dey available. - OK + OK Cancel am Audio Language You dey offline Make sure sey Wi-Fi or mobile data dey on, den try am again. - OK - OK + 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 + Concept Card 1 + Concept Card 2 Revision Card Comot go the topic page? Wetin you don do before no go save @@ -64,6 +69,7 @@ Go di former card Go di next card Submit + Submit Replay Return To Di Topic Former reply (%s) @@ -71,6 +77,7 @@ Learn Am Again See More See Less + This na sample text view FAQs Featured Questions Frequently Asked Questions @@ -91,6 +98,7 @@ Chapter %s with title %s don complete Chapter %s with title %s dey in progress Complete Chapter %s: %s to unlock dis chapter. + Chapter %s: %s dey locked currently. Abeg complete chapter %s: %s to fit 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. @@ -164,11 +172,11 @@ 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. + 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 @@ -176,11 +184,11 @@ %s GB You get am! Topic: %s - + 1 Chapter %s Chapters - + 1 Story %s Stories @@ -188,16 +196,16 @@ %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 @@ -324,7 +332,7 @@ Choose From Library Rename Profile New Name - save + Save Reset PIN Put new PIN for di user to put wan dey wan enter deir profile. 3-Digit PIN @@ -337,6 +345,7 @@ Required Back Button Next + Learner Study Analytics General Edit account Profile Management @@ -357,12 +366,12 @@ Di last update install on %s. Use di version nomba for up to send feedback about bugs. App Version App Language - Default Audio Language + Preferred Audio Language Reading Text Size Reading Text Size Story text go look like dis. A - Default Audio + Preferred Audio Language App Language Reading Text Size Small @@ -392,7 +401,7 @@ No new hint dey Show hints and solution Hint %s - Go up + Close Hints Show solution Show Solution @@ -418,12 +427,36 @@ Up Down %s %s + + 0 minutes ago + a minute ago + %s minutes ago + + + an hour ago + %s hours ago + + + a day ago + %s days ago + 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 + Unsupported app version + Dis version of di app no longer dey supported. Abeg update am from di Play Store. + Close app + App update required + A new version of %s don dey available. The new version dey more secure, and dey make your learning sweet.\n\nThis version no dey supported again. To continue to use the app, abeg update am to the latest version. + Update + Close app + New update available + A new version of %s don dey available. We go advise sey you update the app for bug fixes so that you go fit learn well. + Dismiss + Update + Update your Android OS + We go advise make you update your Android OS to fit enjoy %s\'s new features and lessons.\n\nGo your phone\'s Settings app to update your OS. + Dismiss Developer Build Alpha Beta @@ -435,9 +468,11 @@ 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. + to + Enter a ratio in di form x:y. Tap here to put text. + Write the digit for here. + Write here. Smallest text size Largest text size Coming Soon @@ -495,4 +530,52 @@ Abeg select all di correct choices. You fit select more choices. No more dan %s choices go dey selected. + Survey + Previous + Submit + Leave your feedback for here + Continue Survey + Exit + Exit Survey + You sure sey you wan exit the survey? + Your feedback go help us serve learners like you well-well. You go like complete a short survey about your experience? + Begin Survey + Maybe Later + Thank you for completing the survey. We hope sey you don enjoy using %s! + Exit survey + We go like hear how you feel! + Thank you + 0 - Not at all likely + 10 - Extremely likely + Abeg select one of the following: + I be learner + I be teacher + I be parent + Other + How you go feel if you no fit use %s again? + Very disappointed + Somewhat disappointed + Not disappointed + N/A - I no dey use %s again + We dey happy sey you don enjoy your experience with %s. Abeg share wetin help you the most: + Thanks for responding! How we go fit provide better experience for you? + Abeg help us improve your experience! Tell us the primary reason for your score: + On a scale from 0–10, how likely you dey to recommend %s to a friend or colleague? + The previous subtopic na %s + The next subtopic na %s + App Info + Spotlight Overlay Arrow + Close Spotlight Button + Previous State Navigation Button + Developer Options Icon + Administrator Controls Icon + Options Menu + Previous Button + Next Button + Language Icon + Setting Icon + Profile Picture Image View + Lock Icon + Download Status + html Content diff --git a/app/src/main/res/values/component_colors.xml b/app/src/main/res/values/component_colors.xml index 9c312ee2ed0..35df91176ed 100644 --- a/app/src/main/res/values/component_colors.xml +++ b/app/src/main/res/values/component_colors.xml @@ -245,6 +245,7 @@ @color/color_palette_concept_card_toolbar_color @color/color_palette_audio_fragment_background_color + @color/color_palette_icon_background_secondary_color @color/color_palette_icon_background_secondary_color @color/color_palette_seekbar_progress_background_color @color/color_palette_seekbar_thumb_shadow_color diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4361dce5674..65e5ca43e22 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -8,8 +8,6 @@ 12dp 1000 1000 - 8dp - 28dp 24dp 4dp 18dp @@ -769,4 +767,10 @@ 28dp 32dp 8dp + + + 8dp + 28dp + 20dp + 3dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 97547bc1638..9b924f81a97 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -472,48 +472,20 @@ completed_story_list_recyclerview_tag Please select all correct choices. - - Unsupported app version - - - This version of the app is no longer supported. Please update it through the Play Store. - - - Close app - - - App update required - - - A new version of %s is now available. The new version is more secure, and improves your learning experience.\n\nThis version is no longer supported. To continue using the app, please update to the latest version. - - - Update - - - Close app - - - New update available - - - A new version of %s is now available. We recommend that you update the app for bug fixes and a better learning experience. - - - Dismiss - - - Update - - - Update your Android OS - - - We recommend updating your Android OS to take advantage of %s\'s new features and lessons.\n\nVisit your phone\'s Settings app to update your OS. - - - Dismiss - + Unsupported app version + This version of the app is no longer supported. Please update it through the Play Store. + Close app + App update required + A new version of %s is now available. The new version is more secure, and improves your learning experience.\n\nThis version is no longer supported. To continue using the app, please update to the latest version. + Update + Close app + New update available + A new version of %s is now available. We recommend that you update the app for bug fixes and a better learning experience. + Dismiss + Update + Update your Android OS + We recommend updating your Android OS to take advantage of %s\'s new features and lessons.\n\nVisit your phone\'s Settings app to update your OS. + Dismiss Developer Build Alpha Beta diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsFragmentTest.kt index 5d31a9e5cdb..b161e4cc231 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsFragmentTest.kt @@ -17,7 +17,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.firebase.FirebaseApp import com.google.firebase.crashlytics.FirebaseCrashlytics -import dagger.Binds import dagger.Component import dagger.Module import dagger.Provides @@ -47,7 +46,6 @@ import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule import org.oppia.android.app.utility.OrientationChangeAction.Companion.orientationLandscape import org.oppia.android.data.backends.gae.NetworkConfigProdModule import org.oppia.android.data.backends.gae.NetworkModule -import org.oppia.android.domain.auth.AuthenticationWrapper import org.oppia.android.domain.classify.InteractionsModule import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule @@ -83,9 +81,10 @@ import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModu import org.oppia.android.domain.question.QuestionModule import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule -import org.oppia.android.testing.FakeAuthenticationController -import org.oppia.android.testing.FakeFirestoreEventLogger import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.RunOn +import org.oppia.android.testing.TestAuthenticationModule +import org.oppia.android.testing.TestPlatform import org.oppia.android.testing.junit.InitializeDefaultLocaleRule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers @@ -103,12 +102,13 @@ import org.oppia.android.util.logging.ExceptionLogger import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule import org.oppia.android.util.logging.firebase.DebugAnalyticsEventLogger -import org.oppia.android.util.logging.firebase.DebugFirestoreEventLogger +import org.oppia.android.util.logging.firebase.DebugFirestoreEventLoggerImpl import org.oppia.android.util.logging.firebase.FirebaseAnalyticsEventLogger import org.oppia.android.util.logging.firebase.FirebaseExceptionLogger import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.logging.firebase.FirestoreEventLogger -import org.oppia.android.util.logging.firebase.FirestoreEventLoggerProdImpl +import org.oppia.android.util.logging.firebase.FirestoreInstanceWrapper +import org.oppia.android.util.logging.firebase.FirestoreInstanceWrapperImpl import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsEventLogger @@ -157,7 +157,7 @@ class ViewEventLogsFragmentTest { lateinit var fakeOppiaClock: FakeOppiaClock @Inject - lateinit var debugFirestoreEventLogger: DebugFirestoreEventLogger + lateinit var firestoreEventLogger: FirestoreEventLogger @Before fun setUp() { @@ -397,7 +397,9 @@ class ViewEventLogsFragmentTest { } } - @Test + @Test // TODO(#5143): On robolectric, there is a conflict between Firestore's Sqlite and + // robolectric's ShadowSQLiteConnection but this is resolved in newer versions of robolectric. + @RunOn(TestPlatform.ESPRESSO) fun testViewEventLogsFragment_dateAndTimeIsDisplayedCorrectly() { launch(ViewEventLogsTestActivity::class.java).use { scenario -> testCoroutineDispatchers.runCurrent() @@ -612,7 +614,7 @@ class ViewEventLogsFragmentTest { .setTimestamp(TEST_TIMESTAMP + 50000) .build() - debugFirestoreEventLogger.uploadEvent(eventLog) + firestoreEventLogger.uploadEvent(eventLog) } private fun createOptionalSurveyResponseContext( @@ -699,14 +701,6 @@ class ViewEventLogsFragmentTest { fun provideFirestoreLogStorageCacheSize(): Int = 2 } - @Module - interface TestAuthModule { - @Binds - fun bindFakeAuthenticationController( - fakeAuthenticationController: FakeAuthenticationController - ): AuthenticationWrapper - } - @Module class TestLogReportingModule { @Provides @@ -728,12 +722,14 @@ class ViewEventLogsFragmentTest { @Provides @Singleton - fun provideFakeFirestoreEventLogger(): DebugFirestoreEventLogger = FakeFirestoreEventLogger() + fun provideDebugFirestoreEventLogger( + debugFirestoreEventLogger: DebugFirestoreEventLoggerImpl + ): FirestoreEventLogger = debugFirestoreEventLogger @Provides @Singleton - fun provideFirestoreLogger(factory: FirestoreEventLoggerProdImpl.Factory): - FirestoreEventLogger = factory.createFirestoreEventLogger() + fun provideFirebaseFirestoreInstanceWrapper(wrapperImpl: FirestoreInstanceWrapperImpl): + FirestoreInstanceWrapper = wrapperImpl } // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. @@ -766,7 +762,7 @@ class ViewEventLogsFragmentTest { PerformanceMetricsConfigurationsModule::class, TestingBuildFlavorModule::class, EventLoggingConfigurationModule::class, ActivityRouterModule::class, CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, - TestAuthModule::class, + TestAuthenticationModule::class, ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt index af3b92fd5d6..f398c4bfe68 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt @@ -227,7 +227,6 @@ class ExplorationActivityTest { @Before fun setUp() { Intents.init() - TestPlatformParameterModule.forceEnableContinueButtonAnimation(false) setUpTestApplicationComponent() testCoroutineDispatchers.registerIdlingResource() profileTestHelper.initializeProfiles() @@ -1072,7 +1071,7 @@ class ExplorationActivityTest { onView(withId(R.id.action_audio_player)).perform(click()) testCoroutineDispatchers.runCurrent() - onView(withId(R.id.play_pause_audio_icon)).check(matches(isDisplayed())) + onView(withId(R.id.audio_bar_container)).check(matches(isDisplayed())) onView(withText(context.getString(R.string.cellular_data_alert_dialog_title))) .check(doesNotExist()) } @@ -1296,6 +1295,43 @@ class ExplorationActivityTest { explorationDataController.stopPlayingExploration(isCompletion = false) } + @Test + fun testExplorationActivity_loadingAudio_progressbarIsDisplayed() { + markAllSpotlightsSeen() + setUpAudio() + launch( + createExplorationActivityIntent( + internalProfileId, + RATIOS_TOPIC_ID, + RATIOS_STORY_ID_0, + RATIOS_EXPLORATION_ID_0, + shouldSavePartialProgress = false + ) + ).use { + explorationDataController.startPlayingNewExploration( + internalProfileId, + RATIOS_TOPIC_ID, + RATIOS_STORY_ID_0, + RATIOS_EXPLORATION_ID_0 + ) + networkConnectionUtil.setCurrentConnectionStatus(ProdConnectionStatus.LOCAL) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.action_audio_player)).perform(click()) + + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.audio_bar_container)).check(matches(isDisplayed())) + onView(withId(R.id.audio_fragment_voiceover_progressbar)).check(matches(isDisplayed())) + + waitForTheView(withDrawable(R.drawable.ic_pause_circle_filled_white_24dp)) + onView(withId(R.id.play_pause_audio_icon)).check( + matches( + withDrawable(R.drawable.ic_pause_circle_filled_white_24dp) + ) + ) + } + explorationDataController.stopPlayingExploration(isCompletion = false) + } + // TODO(#89): Check this test case too. It works in pair with below test cases. @Test fun testExpActivity_showUnsavedExpDialog_cancel_dismissesDialog() { @@ -1862,6 +1898,38 @@ class ExplorationActivityTest { explorationDataController.stopPlayingExploration(isCompletion = false) } + @Test + fun testExpActivity_pressBack_whenProgressControllerBroken_stillEndsActivity() { + setUpAudioForFractionLesson() + explorationActivityTestRule.launchActivity( + createExplorationActivityIntent( + internalProfileId, + FRACTIONS_TOPIC_ID, + FRACTIONS_STORY_ID_0, + FRACTIONS_EXPLORATION_ID_0, + shouldSavePartialProgress = true + ) + ) + explorationDataController.startPlayingNewExploration( + internalProfileId, + FRACTIONS_TOPIC_ID, + FRACTIONS_STORY_ID_0, + FRACTIONS_EXPLORATION_ID_0 + ) + testCoroutineDispatchers.runCurrent() + + // Simulate cases when the data controller enters a bad state by pre-finishing the exploration + // prior to trying to exit. While this seems impossible, it's been observed in real situations + // without a known cause. If it does happen, the user needs to have an escape hatch to actually + // leave. See #5233. + explorationDataController.stopPlayingExploration(isCompletion = false) + testCoroutineDispatchers.runCurrent() + pressBack() + testCoroutineDispatchers.runCurrent() + + assertThat(explorationActivityTestRule.activity.isFinishing).isTrue() + } + @Test @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. fun testExpActivity_englishContentLang_contentIsInEnglish() { @@ -2268,13 +2336,15 @@ class ExplorationActivityTest { explorationId: String, shouldSavePartialProgress: Boolean ): Intent { + // Note that the parent screen is defaulted to TOPIC_SCREEN_LESSONS_TAB since that's the most + // typical route to playing an exploration. return ExplorationActivity.createExplorationActivityIntent( ApplicationProvider.getApplicationContext(), ProfileId.newBuilder().apply { internalId = internalProfileId }.build(), topicId, storyId, explorationId, - parentScreen = ExplorationActivityParams.ParentScreen.PARENT_SCREEN_UNSPECIFIED, + parentScreen = ExplorationActivityParams.ParentScreen.TOPIC_SCREEN_LESSONS_TAB, shouldSavePartialProgress ) } 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 6d61bf92d6f..1a3592aa433 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 @@ -443,6 +443,24 @@ class StateFragmentTest { } } + @Test + @RunOn(TestPlatform.ESPRESSO) // Robolectric tests don't rotate like this to recreate activity + fun testStateFragment_loadExp_invalidAnswer_changeConfiguration_submitButtonIsDisplayed() { + setUpTestWithLanguageSwitchingFeatureOff() + launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use { + startPlayingExploration() + clickContinueInteractionButton() + + typeFractionText("1/") + + clickSubmitAnswerButton() + + rotateToLandscape() + + onView(withId(R.id.submit_answer_button)).check(matches(isDisplayed())) + } + } + @Test fun testStateFragment_loadExp_secondState_invalidAnswer_updated_submitAnswerIsEnabled() { setUpTestWithLanguageSwitchingFeatureOff() @@ -4732,7 +4750,6 @@ class StateFragmentTest { } private fun setUpTest() { - TestPlatformParameterModule.forceEnableContinueButtonAnimation(false) Intents.init() setUpTestApplicationComponent() testCoroutineDispatchers.registerIdlingResource() 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 33c5f10e3e2..1d278a6a9da 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 @@ -270,7 +270,6 @@ class StateFragmentLocalTest { @Test fun testContinueInteractionAnim_openPrototypeExp_checkContinueButtonAnimatesAfter45Seconds() { - TestPlatformParameterModule.forceEnableContinueButtonAnimation(true) launchForExploration(TEST_EXPLORATION_ID_2).use { startPlayingExploration() testCoroutineDispatchers.runCurrent() @@ -340,7 +339,6 @@ class StateFragmentLocalTest { @Test fun testConIntAnim_openProtExp_orientLandscapeAfter30Sec_checkAnimHasNotStarted() { - TestPlatformParameterModule.forceEnableContinueButtonAnimation(true) launchForExploration(TEST_EXPLORATION_ID_2).use { startPlayingExploration() @@ -354,7 +352,6 @@ class StateFragmentLocalTest { @Test fun testConIntAnim_openProtExp_orientLandAfter30Sec_checkAnimStartsIn15SecAfterOrientChange() { - TestPlatformParameterModule.forceEnableContinueButtonAnimation(true) launchForExploration(TEST_EXPLORATION_ID_2).use { startPlayingExploration() @@ -369,7 +366,6 @@ class StateFragmentLocalTest { @Test fun testContNavBtnAnim_openMathExp_checkContNavBtnAnimatesAfter45Seconds() { - TestPlatformParameterModule.forceEnableContinueButtonAnimation(true) launchForExploration(TEST_EXPLORATION_ID_5).use { startPlayingExploration() onView(withId(R.id.state_recycler_view)).perform( @@ -392,7 +388,6 @@ class StateFragmentLocalTest { @Ignore("Continue navigation animation behavior fails during testing") @Test fun testContNavBtnAnim_openMathExp_playThroughSecondState_checkContBtnDoesNotAnimateAfter45Sec() { - TestPlatformParameterModule.forceEnableContinueButtonAnimation(true) launchForExploration(TEST_EXPLORATION_ID_5).use { startPlayingExploration() onView(withId(R.id.state_recycler_view)).perform( @@ -425,7 +420,6 @@ class StateFragmentLocalTest { @Ignore("Continue navigation animation behavior fails during testing") @Test fun testConIntAnim_openFractions_expId1_checkButtonDoesNotAnimate() { - TestPlatformParameterModule.forceEnableContinueButtonAnimation(true) launchForExploration(TEST_EXPLORATION_ID_2).use { startPlayingExploration() playThroughTestState1() diff --git a/app/src/test/resources/robolectric.properties b/app/src/test/resources/robolectric.properties index 563d60ad14b..1aafcf8ea7d 100644 --- a/app/src/test/resources/robolectric.properties +++ b/app/src/test/resources/robolectric.properties @@ -1,3 +1,3 @@ # app/src/test/resources/robolectric.properties -# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 31 +# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 33 sdk=30 diff --git a/build_flavors.bzl b/build_flavors.bzl index 9052490bf85..0440b171a55 100644 --- a/build_flavors.bzl +++ b/build_flavors.bzl @@ -46,7 +46,7 @@ _FLAVOR_METADATA = { "dev": { "manifest": "//app:src/main/AndroidManifest.xml", "min_sdk_version": 21, - "target_sdk_version": 31, + "target_sdk_version": 33, "multidex": "native", "proguard_specs": [], # Developer builds are not optimized. "production_release": False, @@ -60,7 +60,7 @@ _FLAVOR_METADATA = { "dev_kitkat": { "manifest": "//app:src/main/AndroidManifest.xml", "min_sdk_version": 19, - "target_sdk_version": 31, + "target_sdk_version": 33, "multidex": "manual_main_dex", "main_dex_list": _MAIN_DEX_LIST_TARGET_KITKAT, "proguard_specs": [], # Developer builds are not optimized. @@ -75,7 +75,7 @@ _FLAVOR_METADATA = { "alpha": { "manifest": "//app:src/main/AndroidManifest.xml", "min_sdk_version": 21, - "target_sdk_version": 31, + "target_sdk_version": 33, "multidex": "native", "proguard_specs": _PRODUCTION_PROGUARD_SPECS, "production_release": True, @@ -89,7 +89,7 @@ _FLAVOR_METADATA = { "alpha_kitkat": { "manifest": "//app:src/main/AndroidManifest.xml", "min_sdk_version": 19, - "target_sdk_version": 31, + "target_sdk_version": 33, "multidex": "manual_main_dex", "main_dex_list": _MAIN_DEX_LIST_TARGET_KITKAT, "proguard_specs": [], @@ -104,7 +104,7 @@ _FLAVOR_METADATA = { "alpha_kenya": { "manifest": "//app:src/main/AndroidManifest.xml", "min_sdk_version": 21, - "target_sdk_version": 31, + "target_sdk_version": 33, "multidex": "native", "proguard_specs": _PRODUCTION_PROGUARD_SPECS, "production_release": True, @@ -118,7 +118,7 @@ _FLAVOR_METADATA = { "beta": { "manifest": "//app:src/main/AndroidManifest.xml", "min_sdk_version": 21, - "target_sdk_version": 31, + "target_sdk_version": 33, "multidex": "native", "proguard_specs": _PRODUCTION_PROGUARD_SPECS, "production_release": True, @@ -132,7 +132,7 @@ _FLAVOR_METADATA = { "ga": { "manifest": "//app:src/main/AndroidManifest.xml", "min_sdk_version": 21, - "target_sdk_version": 31, + "target_sdk_version": 33, "multidex": "native", "proguard_specs": _PRODUCTION_PROGUARD_SPECS, "production_release": True, diff --git a/config/src/java/org/oppia/android/config/AndroidManifest.xml b/config/src/java/org/oppia/android/config/AndroidManifest.xml index 1d3f57544f1..123ff9bc501 100644 --- a/config/src/java/org/oppia/android/config/AndroidManifest.xml +++ b/config/src/java/org/oppia/android/config/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/data/build.gradle b/data/build.gradle index d51015b8c24..3f02df5e6d0 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -4,12 +4,12 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 31 + compileSdkVersion 33 buildToolsVersion "29.0.2" defaultConfig { minSdkVersion 19 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/data/src/test/resources/robolectric.properties b/data/src/test/resources/robolectric.properties index e16d090bdb2..19419ffe423 100644 --- a/data/src/test/resources/robolectric.properties +++ b/data/src/test/resources/robolectric.properties @@ -1,3 +1,3 @@ # data/src/test/resources/robolectric.properties -# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 31 +# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 33 sdk=30 diff --git a/domain/build.gradle b/domain/build.gradle index 060bffe6e87..50c0aee49bd 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -4,12 +4,12 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 31 + compileSdkVersion 33 buildToolsVersion "29.0.2" defaultConfig { minSdkVersion 19 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 1 versionName "1.0" javaCompileOptions { diff --git a/domain/src/main/AndroidManifest.xml b/domain/src/main/AndroidManifest.xml index 483a96cc057..ea5a0a7a495 100644 --- a/domain/src/main/AndroidManifest.xml +++ b/domain/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationController.kt b/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationController.kt index 5295eacdf91..c4cb54e8b3a 100644 --- a/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationController.kt +++ b/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationController.kt @@ -1,7 +1,5 @@ package org.oppia.android.domain.auth -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.FirebaseUser import kotlinx.coroutines.CompletableDeferred import org.oppia.android.util.data.AsyncResult import javax.inject.Inject @@ -10,23 +8,22 @@ import javax.inject.Singleton /** Controller for signing in and retrieving a Firebase user. */ @Singleton class AuthenticationController @Inject constructor( - private val firebaseAuth: FirebaseAuth -) : AuthenticationWrapper { + private val firebaseAuthWrapper: FirebaseAuthWrapper +) { /** Returns the current signed in user or null if there is no authenticated user. */ - override fun getCurrentSignedInUser(): FirebaseUser? { - return firebaseAuth.currentUser - } + val currentFirebaseUser: FirebaseUserWrapper? = firebaseAuthWrapper.currentUser /** Returns the result of an authentication task. */ - override fun signInAnonymously(): CompletableDeferred> { + fun signInAnonymouslyWithFirebase(): CompletableDeferred> { val deferredResult = CompletableDeferred>() - firebaseAuth.signInAnonymously() - .addOnSuccessListener { + firebaseAuthWrapper.signInAnonymously( + onSuccess = { deferredResult.complete(AsyncResult.Success(null)) + }, + onFailure = { exception -> + deferredResult.complete(AsyncResult.Failure(exception)) } - .addOnFailureListener { - deferredResult.complete(AsyncResult.Failure(it)) - } + ) return deferredResult } diff --git a/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationModule.kt b/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationModule.kt index c30a6e01aa4..58b86a8ed71 100644 --- a/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationModule.kt @@ -1,16 +1,14 @@ package org.oppia.android.domain.auth -import com.google.firebase.auth.ktx.auth -import com.google.firebase.ktx.Firebase import dagger.Module import dagger.Provides import javax.inject.Singleton -/** Provides an implementation of [AuthenticationWrapper]. */ +/** Provides an implementation of [FirebaseAuthWrapper]. */ @Module class AuthenticationModule { @Provides @Singleton - fun provideAuthenticationController(): - AuthenticationWrapper = AuthenticationController(Firebase.auth) + fun provideFirebaseAuthWrapper(firebaseAuthInstanceWrapperImpl: FirebaseAuthInstanceWrapperImpl): + FirebaseAuthWrapper = FirebaseAuthWrapperImpl(firebaseAuthInstanceWrapperImpl) } diff --git a/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationWrapper.kt b/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationWrapper.kt deleted file mode 100644 index e4fe3b8125e..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/auth/AuthenticationWrapper.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.oppia.android.domain.auth - -import com.google.firebase.auth.FirebaseUser -import kotlinx.coroutines.CompletableDeferred -import org.oppia.android.util.data.AsyncResult - -/** Wrapper for providing authentication functionality. */ -interface AuthenticationWrapper { - /** Returns the current signed in user or null if there is no authenticated user. */ - fun getCurrentSignedInUser(): FirebaseUser? - - /** Returns the authentication result. */ - fun signInAnonymously(): CompletableDeferred> -} diff --git a/domain/src/main/java/org/oppia/android/domain/auth/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/auth/BUILD.bazel index 97796c508af..451bcd48ae5 100644 --- a/domain/src/main/java/org/oppia/android/domain/auth/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/auth/BUILD.bazel @@ -7,17 +7,24 @@ load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library") kt_android_library( name = "authentication_controller", - srcs = ["AuthenticationController.kt"], + srcs = [ + "AuthenticationController.kt", + ], visibility = ["//:oppia_api_visibility"], deps = [ - ":authentication_listener", + ":firebase_auth_wrapper", "//third_party:javax_inject_javax_inject", ], ) kt_android_library( - name = "authentication_listener", - srcs = ["AuthenticationWrapper.kt"], + name = "firebase_auth_wrapper", + srcs = [ + "FirebaseAuthInstance.kt", + "FirebaseAuthInstanceWrapper.kt", + "FirebaseAuthWrapper.kt", + "FirebaseUserWrapper.kt", + ], visibility = ["//:oppia_api_visibility"], deps = [ "//third_party:com_google_firebase_firebase-auth-ktx", @@ -28,11 +35,38 @@ kt_android_library( kt_android_library( name = "auth_module", - srcs = ["AuthenticationModule.kt"], + srcs = [ + "AuthenticationModule.kt", + ], visibility = ["//:oppia_prod_module_visibility"], deps = [ ":authentication_controller", ":dagger", + ":firebase_auth_wrapper_impl", + ], +) + +kt_android_library( + name = "firebase_auth_wrapper_impl", + srcs = [ + "FirebaseAuthWrapperImpl.kt", + ], + visibility = ["//:oppia_prod_module_visibility"], + deps = [ + ":dagger", + ":firebase_auth_instance_wrapper_impl", + ], +) + +kt_android_library( + name = "firebase_auth_instance_wrapper_impl", + srcs = [ + "FirebaseAuthInstanceWrapperImpl.kt", + ], + visibility = ["//:oppia_prod_module_visibility"], + deps = [ + ":dagger", + ":firebase_auth_wrapper", ], ) diff --git a/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstance.kt b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstance.kt new file mode 100644 index 00000000000..a270857b1cd --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstance.kt @@ -0,0 +1,8 @@ +package org.oppia.android.domain.auth + +import com.google.firebase.auth.FirebaseAuth + +/** Wrapper for [FirebaseAuth], used to pass an instance of [FirebaseAuth]. */ +data class FirebaseAuthInstance( + val firebaseAuth: FirebaseAuth +) diff --git a/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapper.kt b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapper.kt new file mode 100644 index 00000000000..c85744fba35 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapper.kt @@ -0,0 +1,7 @@ +package org.oppia.android.domain.auth + +/** Interface for providing an implementation of [FirebaseAuthInstance]. */ +interface FirebaseAuthInstanceWrapper { + /** Returns a wrapped instance of FirebaseAuth. */ + val firebaseAuthInstance: FirebaseAuthInstance +} diff --git a/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapperImpl.kt b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapperImpl.kt new file mode 100644 index 00000000000..f3a8ae2b24d --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapperImpl.kt @@ -0,0 +1,11 @@ +package org.oppia.android.domain.auth + +import com.google.firebase.auth.ktx.auth +import com.google.firebase.ktx.Firebase +import javax.inject.Inject + +/** Implementation of [FirebaseAuthInstanceWrapper]. */ +class FirebaseAuthInstanceWrapperImpl @Inject constructor() : FirebaseAuthInstanceWrapper { + override val firebaseAuthInstance: FirebaseAuthInstance + get() = FirebaseAuthInstance(Firebase.auth) +} diff --git a/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapper.kt b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapper.kt new file mode 100644 index 00000000000..85c859eb62a --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapper.kt @@ -0,0 +1,10 @@ +package org.oppia.android.domain.auth + +/** Wrapper for FirebaseAuth. */ +interface FirebaseAuthWrapper { + /** Returns the current signed in user or null if there is no authenticated user. */ + val currentUser: FirebaseUserWrapper? + + /** Returns the authentication result. */ + fun signInAnonymously(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) +} diff --git a/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapperImpl.kt b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapperImpl.kt new file mode 100644 index 00000000000..4099f11566d --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapperImpl.kt @@ -0,0 +1,28 @@ +package org.oppia.android.domain.auth + +import javax.inject.Inject +import javax.inject.Singleton + +/** Production implementation of FirebaseAuthWrapper. */ +@Singleton +class FirebaseAuthWrapperImpl @Inject constructor( + private val firebaseWrapper: FirebaseAuthInstanceWrapperImpl +) : FirebaseAuthWrapper { + override val currentUser: FirebaseUserWrapper? + get() = firebaseWrapper.firebaseAuthInstance.firebaseAuth.currentUser?.let { + FirebaseUserWrapper(it.uid) + } + + override fun signInAnonymously(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) { + firebaseWrapper.firebaseAuthInstance.firebaseAuth.signInAnonymously() + .addOnSuccessListener { + onSuccess.invoke() + } + .addOnFailureListener { task -> + val exception = task.cause + if (exception != null) { + onFailure.invoke(exception) + } + } + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/auth/FirebaseUserWrapper.kt b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseUserWrapper.kt new file mode 100644 index 00000000000..16aeb8ebf5b --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/auth/FirebaseUserWrapper.kt @@ -0,0 +1,8 @@ +package org.oppia.android.domain.auth + +import com.google.firebase.auth.FirebaseUser + +/** Wrapper for [FirebaseUser]. */ +data class FirebaseUserWrapper( + val uid: String, +) diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/FirestoreDataController.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/FirestoreDataController.kt index a9f709fb0ce..079a55f65ae 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/FirestoreDataController.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/FirestoreDataController.kt @@ -7,7 +7,7 @@ import org.oppia.android.app.model.EventLog import org.oppia.android.app.model.OppiaEventLogs import org.oppia.android.app.model.ProfileId import org.oppia.android.data.persistence.PersistentCacheStore -import org.oppia.android.domain.auth.AuthenticationWrapper +import org.oppia.android.domain.auth.AuthenticationController import org.oppia.android.domain.oppialogger.FirestoreLogStorageCacheSize import org.oppia.android.util.data.AsyncResult import org.oppia.android.util.data.DataProvider @@ -29,7 +29,7 @@ class FirestoreDataController @Inject constructor( private val eventLogger: FirestoreEventLogger, private val exceptionLogger: ExceptionLogger, private val oppiaClock: OppiaClock, - private val authenticationWrapper: AuthenticationWrapper, + private val authenticationController: AuthenticationController, @BlockingDispatcher private val blockingDispatcher: CoroutineDispatcher, @FirestoreLogStorageCacheSize private val logStorageCacheSize: Int ) { @@ -93,8 +93,8 @@ class FirestoreDataController @Inject constructor( } private suspend fun authenticateAndUploadToFirestore(eventLog: EventLog) { - if (authenticationWrapper.getCurrentSignedInUser() == null) { - when (val signInResult = authenticationWrapper.signInAnonymously().await()) { + if (authenticationController.currentFirebaseUser == null) { + when (val signInResult = authenticationController.signInAnonymouslyWithFirebase().await()) { is AsyncResult.Success -> { consoleLogger.i("FirestoreDataController", "Sign in succeeded") eventLogger.uploadEvent(eventLog) @@ -106,7 +106,9 @@ class FirestoreDataController @Inject constructor( ) cacheEventForFirestore(eventLog) } - is AsyncResult.Pending -> {} // no-op + is AsyncResult.Pending -> { + consoleLogger.i("FirestoreDataController", "Signing in anonymously to Firebase") + } } } else { eventLogger.uploadEvent(eventLog) 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 1b3293d56f5..376f5abf36e 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 @@ -4,11 +4,13 @@ import android.content.Context import dagger.Module import dagger.Provides import org.oppia.android.app.utility.getVersionCode +import org.oppia.android.util.platformparameter.APP_AND_OS_DEPRECATION 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.DOWNLOADS_SUPPORT +import org.oppia.android.util.platformparameter.EDIT_ACCOUNTS_OPTIONS_UI 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 @@ -17,8 +19,8 @@ import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEF 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.ENABLE_SPOTLIGHT_UI_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.EXTRA_TOPIC_TABS_UI 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 @@ -32,6 +34,7 @@ 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.INTERACTION_CONFIG_CHANGE_STATE_RETENTION 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 @@ -58,6 +61,7 @@ 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.SPOTLIGHT_UI 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 @@ -72,8 +76,12 @@ import org.oppia.android.util.platformparameter.SyncUpWorkerTimePeriodHours class PlatformParameterAlphaKenyaModule { @Provides @EnableDownloadsSupport - fun provideEnableDownloadsSupport(): PlatformParameterValue = - PlatformParameterValue.createDefaultParameter(ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE) + fun provideEnableDownloadsSupport( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(DOWNLOADS_SUPPORT) + ?: PlatformParameterValue.createDefaultParameter(ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE) + } @Provides @SplashScreenWelcomeMsg @@ -106,8 +114,12 @@ class PlatformParameterAlphaKenyaModule { @Provides @EnableEditAccountsOptionsUi - fun provideEnableEditAccountsOptionsUi(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableEditAccountsOptionsUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + EDIT_ACCOUNTS_OPTIONS_UI + ) ?: PlatformParameterValue.createDefaultParameter( ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE ) } @@ -201,40 +213,45 @@ class PlatformParameterAlphaKenyaModule { @Provides @EnableExtraTopicTabsUi - fun provideEnableExtraTopicTabsUi(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableExtraTopicTabsUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + EXTRA_TOPIC_TABS_UI + ) ?: PlatformParameterValue.createDefaultParameter( ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE ) } @Provides @EnableInteractionConfigChangeStateRetention - fun provideEnableInteractionConfigChangeStateRetention(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableInteractionConfigChangeStateRetention( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + INTERACTION_CONFIG_CHANGE_STATE_RETENTION + ) ?: 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 @EnableSpotlightUi - fun enableSpotlightUi(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( - ENABLE_SPOTLIGHT_UI_DEFAULT_VALUE - ) + fun provideEnableSpotlightUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(SPOTLIGHT_UI) + ?: PlatformParameterValue.createDefaultParameter(ENABLE_SPOTLIGHT_UI_DEFAULT_VALUE) } @Provides @EnableAppAndOsDeprecation - fun provideEnableAppAndOsDeprecation(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableAppAndOsDeprecation( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + APP_AND_OS_DEPRECATION + ) ?: PlatformParameterValue.createDefaultParameter( ENABLE_APP_AND_OS_DEPRECATION_DEFAULT_VALUE ) } 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 index 8addae7b9fd..6574762dae5 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt @@ -4,11 +4,13 @@ import android.content.Context import dagger.Module import dagger.Provides import org.oppia.android.app.utility.getVersionCode +import org.oppia.android.util.platformparameter.APP_AND_OS_DEPRECATION 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.DOWNLOADS_SUPPORT +import org.oppia.android.util.platformparameter.EDIT_ACCOUNTS_OPTIONS_UI 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 @@ -16,8 +18,8 @@ import org.oppia.android.util.platformparameter.ENABLE_INTERACTION_CONFIG_CHANGE 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.EXTRA_TOPIC_TABS_UI 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 @@ -32,6 +34,7 @@ import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSO 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.INTERACTION_CONFIG_CHANGE_STATE_RETENTION 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 @@ -59,6 +62,7 @@ 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.SPOTLIGHT_UI 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 @@ -69,8 +73,12 @@ import org.oppia.android.util.platformparameter.SyncUpWorkerTimePeriodHours class PlatformParameterAlphaModule { @Provides @EnableDownloadsSupport - fun provideEnableDownloadsSupport(): PlatformParameterValue = - PlatformParameterValue.createDefaultParameter(ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE) + fun provideEnableDownloadsSupport( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(DOWNLOADS_SUPPORT) + ?: PlatformParameterValue.createDefaultParameter(ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE) + } @Provides @SplashScreenWelcomeMsg @@ -103,8 +111,12 @@ class PlatformParameterAlphaModule { @Provides @EnableEditAccountsOptionsUi - fun provideEnableEditAccountsOptionsUi(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableEditAccountsOptionsUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + EDIT_ACCOUNTS_OPTIONS_UI + ) ?: PlatformParameterValue.createDefaultParameter( ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE ) } @@ -197,39 +209,46 @@ class PlatformParameterAlphaModule { @Provides @EnableSpotlightUi - fun provideEnableSpotlightUi(): PlatformParameterValue = - PlatformParameterValue.createDefaultParameter(true) // Enable spotlights for alpha users. + fun provideEnableSpotlightUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(SPOTLIGHT_UI) + ?: PlatformParameterValue.createDefaultParameter(true) // Enable spotlights for alpha users. + } @Provides @EnableExtraTopicTabsUi - fun provideEnableExtraTopicTabsUi(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableExtraTopicTabsUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + EXTRA_TOPIC_TABS_UI + ) ?: PlatformParameterValue.createDefaultParameter( ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE ) } @Provides @EnableInteractionConfigChangeStateRetention - fun provideEnableInteractionConfigChangeStateRetention(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableInteractionConfigChangeStateRetention( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + INTERACTION_CONFIG_CHANGE_STATE_RETENTION + ) ?: 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 - ) + fun provideEnableAppAndOsDeprecation( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(APP_AND_OS_DEPRECATION) + ?: PlatformParameterValue.createDefaultParameter( + ENABLE_APP_AND_OS_DEPRECATION_DEFAULT_VALUE + ) } @Provides 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 c2a211472fc..4acd5804929 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 @@ -4,11 +4,13 @@ import android.content.Context import dagger.Module import dagger.Provides import org.oppia.android.app.utility.getVersionCode +import org.oppia.android.util.platformparameter.APP_AND_OS_DEPRECATION 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.DOWNLOADS_SUPPORT +import org.oppia.android.util.platformparameter.EDIT_ACCOUNTS_OPTIONS_UI 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 @@ -17,8 +19,8 @@ import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEF 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.ENABLE_SPOTLIGHT_UI_DEFAULT_VALUE +import org.oppia.android.util.platformparameter.EXTRA_TOPIC_TABS_UI 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 @@ -33,6 +35,7 @@ import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSO 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.INTERACTION_CONFIG_CHANGE_STATE_RETENTION 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 @@ -61,6 +64,7 @@ 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.SPOTLIGHT_UI 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 @@ -71,8 +75,12 @@ import org.oppia.android.util.platformparameter.SyncUpWorkerTimePeriodHours class PlatformParameterModule { @Provides @EnableDownloadsSupport - fun provideEnableDownloadsSupport(): PlatformParameterValue = - PlatformParameterValue.createDefaultParameter(ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE) + fun provideEnableDownloadsSupport( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(DOWNLOADS_SUPPORT) + ?: PlatformParameterValue.createDefaultParameter(ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE) + } @Provides @SplashScreenWelcomeMsg @@ -105,8 +113,12 @@ class PlatformParameterModule { @Provides @EnableEditAccountsOptionsUi - fun provideEnableEditAccountsOptionsUi(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableEditAccountsOptionsUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + EDIT_ACCOUNTS_OPTIONS_UI + ) ?: PlatformParameterValue.createDefaultParameter( ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE ) } @@ -199,42 +211,46 @@ class PlatformParameterModule { @Provides @EnableSpotlightUi - fun provideEnableSpotlightUi(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( - ENABLE_SPOTLIGHT_UI_DEFAULT_VALUE - ) + fun provideEnableSpotlightUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(SPOTLIGHT_UI) + ?: PlatformParameterValue.createDefaultParameter(ENABLE_SPOTLIGHT_UI_DEFAULT_VALUE) } @Provides @EnableExtraTopicTabsUi - fun provideEnableExtraTopicTabsUi(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableExtraTopicTabsUi( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + EXTRA_TOPIC_TABS_UI + ) ?: PlatformParameterValue.createDefaultParameter( ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE ) } @Provides @EnableInteractionConfigChangeStateRetention - fun provideEnableInteractionConfigChangeStateRetention(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( + fun provideEnableInteractionConfigChangeStateRetention( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter( + INTERACTION_CONFIG_CHANGE_STATE_RETENTION + ) ?: 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 - ) + fun provideEnableAppAndOsDeprecation( + platformParameterSingleton: PlatformParameterSingleton + ): PlatformParameterValue { + return platformParameterSingleton.getBooleanPlatformParameter(APP_AND_OS_DEPRECATION) + ?: PlatformParameterValue.createDefaultParameter( + ENABLE_APP_AND_OS_DEPRECATION_DEFAULT_VALUE + ) } @Provides diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterSingletonImpl.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterSingletonImpl.kt index b672ad6cdf9..0a72d4b6b44 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterSingletonImpl.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterSingletonImpl.kt @@ -24,8 +24,8 @@ class PlatformParameterSingletonImpl @Inject constructor() : PlatformParameterSi val parameter = platformParameterMap[platformParameterName] ?: return null if (!parameter.valueTypeCase.equals(PlatformParameter.ValueTypeCase.STRING)) return null return object : PlatformParameterValue { - override val value: String - get() = parameter.string + override val value = parameter.string + override val syncStatus = parameter.syncStatus } } @@ -36,8 +36,8 @@ class PlatformParameterSingletonImpl @Inject constructor() : PlatformParameterSi val parameter = platformParameterMap[platformParameterName] ?: return null if (!parameter.valueTypeCase.equals(PlatformParameter.ValueTypeCase.INTEGER)) return null return object : PlatformParameterValue { - override val value: Int - get() = parameter.integer + override val value = parameter.integer + override val syncStatus = parameter.syncStatus } } @@ -48,8 +48,8 @@ class PlatformParameterSingletonImpl @Inject constructor() : PlatformParameterSi val parameter = platformParameterMap[platformParameterName] ?: return null if (!parameter.valueTypeCase.equals(PlatformParameter.ValueTypeCase.BOOLEAN)) return null return object : PlatformParameterValue { - override val value: Boolean - get() = parameter.boolean + override val value = parameter.boolean + override val syncStatus = parameter.syncStatus } } } diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt index aaf5aad0a0d..164f7fea914 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import org.oppia.android.app.model.PlatformParameter +import org.oppia.android.app.model.PlatformParameter.SyncStatus import org.oppia.android.app.utility.getVersionName import org.oppia.android.data.backends.gae.api.PlatformParameterService import org.oppia.android.domain.oppialogger.OppiaLogger @@ -82,6 +83,7 @@ class PlatformParameterSyncUpWorker private constructor( private fun parseNetworkResponse(response: Map): List { return response.map { val platformParameter = PlatformParameter.newBuilder().setName(it.key) + .setSyncStatus(SyncStatus.SYNCED_FROM_SERVER) when (val value = it.value) { is String -> platformParameter.string = value is Int -> platformParameter.integer = value @@ -111,6 +113,7 @@ class PlatformParameterSyncUpWorker private constructor( if (response != null) { val responseBody = checkNotNull(response.body()) val platformParameterList = parseNetworkResponse(responseBody) + if (platformParameterList.isEmpty()) { throw IllegalArgumentException(EMPTY_RESPONSE_EXCEPTION_MSG) } 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 baa73089f5b..59da9110aee 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 @@ -842,9 +842,9 @@ class AudioPlayerControllerTest { fun provideLearnerStudyAnalytics(): 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 - } + return PlatformParameterValue.createDefaultParameter( + defaultValue = enableFeature + ) } @Provides @@ -853,9 +853,9 @@ class AudioPlayerControllerTest { 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 - } + return PlatformParameterValue.createDefaultParameter( + defaultValue = enableFeature + ) } } diff --git a/domain/src/test/java/org/oppia/android/domain/auth/AuthenticationControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/auth/AuthenticationControllerTest.kt new file mode 100644 index 00000000000..1a901a31575 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/auth/AuthenticationControllerTest.kt @@ -0,0 +1,148 @@ +package org.oppia.android.domain.auth + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.testing.FakeFirebaseAuthWrapperImpl +import org.oppia.android.testing.TestAuthenticationModule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.assertThrows +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.threading.BackgroundDispatcher +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [AuthenticationController]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = AuthenticationControllerTest.TestApplication::class) +class AuthenticationControllerTest { + @Inject + lateinit var firebaseAuthWrapper: FirebaseAuthWrapper + + @Inject + lateinit var fakeFirebaseAuthWrapperImpl: FakeFirebaseAuthWrapperImpl + + @Inject + lateinit var authenticationController: AuthenticationController + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @field:[Inject BackgroundDispatcher] + lateinit var backgroundDispatcher: CoroutineDispatcher + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testAuthentication_getCurrentUser_userSignedIn_returnsInstanceOfFirebaseUserWrapper() { + fakeFirebaseAuthWrapperImpl.simulateSignInSuccess() + + firebaseAuthWrapper.signInAnonymously( + onSuccess = {}, + onFailure = {} + ) + + val user = authenticationController.currentFirebaseUser + + assertThat(user).isInstanceOf(FirebaseUserWrapper::class.java) + } + + @Test + fun testAuthentication_signInAnonymously_succeeds() { + fakeFirebaseAuthWrapperImpl.simulateSignInSuccess() + + firebaseAuthWrapper.signInAnonymously( + onSuccess = {}, + onFailure = {} + ) + + val user = authenticationController.currentFirebaseUser + + assertThat(user).isInstanceOf(FirebaseUserWrapper::class.java) + } + + @Test + fun testAuthentication_signInAnonymously_failure_returnsException() { + fakeFirebaseAuthWrapperImpl.simulateSignInFailure() + + assertThrows(Throwable::class) { + firebaseAuthWrapper.signInAnonymously( + onSuccess = {}, + onFailure = {} + ) + } + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext() + .inject(this) + } + + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, RobolectricModule::class, FakeOppiaClockModule::class, + ApplicationLifecycleModule::class, TestDispatcherModule::class, + TestLogReportingModule::class, TestAuthenticationModule::class, + ] + ) + interface TestApplicationComponent : DataProvidersInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: AuthenticationControllerTest) + } + + class TestApplication : Application(), DataProvidersInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerAuthenticationControllerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: AuthenticationControllerTest) { + component.inject(test) + } + + override fun getDataProvidersInjector(): DataProvidersInjector = component + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/auth/AuthenticationModuleTest.kt b/domain/src/test/java/org/oppia/android/domain/auth/AuthenticationModuleTest.kt index 61b0d55de57..c8ee88e7bd6 100644 --- a/domain/src/test/java/org/oppia/android/domain/auth/AuthenticationModuleTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/auth/AuthenticationModuleTest.kt @@ -5,7 +5,6 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import com.google.firebase.auth.FirebaseAuth import dagger.BindsInstance import dagger.Component import dagger.Module @@ -13,11 +12,10 @@ import dagger.Provides import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.mock -import org.oppia.android.domain.auth.AuthenticationModuleTest.AuthenticationModule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestDispatcherModule import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider import org.oppia.android.util.logging.firebase.DebugLogReportingModule import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode @@ -29,11 +27,11 @@ import javax.inject.Singleton @Suppress("FunctionName") @RunWith(AndroidJUnit4::class) @LooperMode(LooperMode.Mode.PAUSED) -@Config(manifest = Config.NONE) +@Config(application = AuthenticationModuleTest.TestApplication::class) class AuthenticationModuleTest { @Inject - lateinit var wrapper: AuthenticationWrapper + lateinit var firebaseAuthWrapper: FirebaseAuthWrapper @Before fun setUp() { @@ -41,35 +39,24 @@ class AuthenticationModuleTest { } @Test - fun testModule_injectsInstanceOfAuthenticationWrapper() { - assertThat(wrapper).isInstanceOf(AuthenticationController::class.java) + fun testModule_injectsProductionImplementationOfFirebaseAuthWrapper() { + assertThat(firebaseAuthWrapper).isInstanceOf(FirebaseAuthWrapperImpl::class.java) } private fun setUpTestApplicationComponent() { - DaggerAuthenticationModuleTest_TestApplicationComponent.builder() - .setApplication(ApplicationProvider.getApplicationContext()) - .build() - .inject(this) + ApplicationProvider.getApplicationContext().inject(this) } + // TODO(#89): Move this to a common test application component. @Module class TestModule { @Provides @Singleton - fun provideContext(): Context { - return ApplicationProvider.getApplicationContext() + fun provideContext(application: Application): Context { + return application } } - @Module - class AuthenticationModule { - @Provides - @Singleton - fun provideAuthenticationController(): - AuthenticationWrapper = AuthenticationController(mock(FirebaseAuth::class.java)) - } - - // TODO(#89): Move this to a common test application component. @Singleton @Component( modules = [ @@ -88,4 +75,18 @@ class AuthenticationModuleTest { fun inject(test: AuthenticationModuleTest) } + + class TestApplication : Application(), DataProvidersInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerAuthenticationModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: AuthenticationModuleTest) { + component.inject(test) + } + + override fun getDataProvidersInjector(): DataProvidersInjector = component + } } diff --git a/domain/src/test/java/org/oppia/android/domain/auth/FirebaseAuthWrapperImplTest.kt b/domain/src/test/java/org/oppia/android/domain/auth/FirebaseAuthWrapperImplTest.kt new file mode 100644 index 00000000000..55d5d02c13d --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/auth/FirebaseAuthWrapperImplTest.kt @@ -0,0 +1,3 @@ +package org.oppia.android.domain.auth + +class FirebaseAuthWrapperImplTest diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/FirestoreDataControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/FirestoreDataControllerTest.kt index e06249ac70a..f22c7876006 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/FirestoreDataControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/FirestoreDataControllerTest.kt @@ -5,7 +5,6 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import dagger.Binds import dagger.BindsInstance import dagger.Component import dagger.Module @@ -24,12 +23,11 @@ import org.oppia.android.app.model.OppiaEventLogs import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.SurveyQuestionName import org.oppia.android.data.persistence.PersistentCacheStore -import org.oppia.android.domain.auth.AuthenticationWrapper import org.oppia.android.domain.oppialogger.FirestoreLogStorageCacheSize import org.oppia.android.domain.platformparameter.PlatformParameterModule import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.testing.FakeAuthenticationController import org.oppia.android.testing.FakeFirestoreEventLogger +import org.oppia.android.testing.TestAuthenticationModule import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.data.DataProviderTestMonitor import org.oppia.android.testing.logging.EventLogSubject @@ -418,14 +416,6 @@ class FirestoreDataControllerTest { fun provideFirestoreLogStorageCacheSize(): Int = 2 } - @Module - interface TestAuthModule { - @Binds - fun bindFakeAuthenticationController( - fakeAuthenticationController: FakeAuthenticationController - ): AuthenticationWrapper - } - // TODO(#89): Move this to a common test application component. @Singleton @Component( @@ -435,7 +425,7 @@ class FirestoreDataControllerTest { NetworkConnectionUtilDebugModule::class, LocaleProdModule::class, PlatformParameterSingletonModule::class, SyncStatusModule::class, ApplicationLifecycleModule::class, PlatformParameterModule::class, - CpuPerformanceSnapshotterModule::class, TestAuthModule::class, + CpuPerformanceSnapshotterModule::class, TestAuthenticationModule::class, ] ) interface TestApplicationComponent : DataProvidersInjector { diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/SurveyEventsLoggerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/SurveyEventsLoggerTest.kt index 0485dec5b5c..f82ab30c03d 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/SurveyEventsLoggerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/SurveyEventsLoggerTest.kt @@ -4,7 +4,6 @@ import android.app.Application import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import dagger.Binds import dagger.BindsInstance import dagger.Component import dagger.Module @@ -16,7 +15,6 @@ import org.oppia.android.app.model.MarketFitAnswer import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.SurveyQuestionName import org.oppia.android.app.model.UserTypeAnswer -import org.oppia.android.domain.auth.AuthenticationWrapper import org.oppia.android.domain.oppialogger.EventLogStorageCacheSize import org.oppia.android.domain.oppialogger.ExceptionLogStorageCacheSize import org.oppia.android.domain.oppialogger.FirestoreLogStorageCacheSize @@ -24,8 +22,8 @@ import org.oppia.android.domain.oppialogger.LoggingIdentifierModule import org.oppia.android.domain.oppialogger.survey.SurveyEventsLogger import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule import org.oppia.android.testing.FakeAnalyticsEventLogger -import org.oppia.android.testing.FakeAuthenticationController import org.oppia.android.testing.FakeFirestoreEventLogger +import org.oppia.android.testing.TestAuthenticationModule import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.logging.EventLogSubject.Companion.assertThat import org.oppia.android.testing.logging.SyncStatusTestModule @@ -185,14 +183,6 @@ class SurveyEventsLoggerTest { fun provideFirestoreLogStorageCacheSize(): Int = 2 } - @Module - interface TestAuthModule { - @Binds - fun bindFakeAuthenticationController( - fakeAuthenticationController: FakeAuthenticationController - ): AuthenticationWrapper - } - // TODO(#89): Move this to a common test application component. @Singleton @Component( @@ -202,7 +192,7 @@ class SurveyEventsLoggerTest { NetworkConnectionUtilDebugModule::class, LocaleProdModule::class, FakeOppiaClockModule::class, TestPlatformParameterModule::class, PlatformParameterSingletonModule::class, LoggingIdentifierModule::class, SyncStatusTestModule::class, - ApplicationLifecycleModule::class, AssetModule::class, TestAuthModule::class, + ApplicationLifecycleModule::class, AssetModule::class, TestAuthenticationModule::class, ] ) interface TestApplicationComponent : DataProvidersInjector { diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt index 6d2e6d1e89b..50450041a09 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt @@ -22,7 +22,6 @@ import dagger.Provides import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.oppia.android.domain.auth.AuthenticationWrapper import org.oppia.android.domain.oppialogger.EventLogStorageCacheSize import org.oppia.android.domain.oppialogger.ExceptionLogStorageCacheSize import org.oppia.android.domain.oppialogger.FirestoreLogStorageCacheSize @@ -38,8 +37,8 @@ import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWork import org.oppia.android.domain.platformparameter.PlatformParameterModule import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule import org.oppia.android.domain.testing.oppialogger.loguploader.FakeLogUploader -import org.oppia.android.testing.FakeAuthenticationController import org.oppia.android.testing.FakeExceptionLogger +import org.oppia.android.testing.TestAuthenticationModule import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers @@ -314,15 +313,6 @@ class LogReportWorkManagerInitializerTest { fun bindsFakeLogScheduler(fakeLogScheduler: FakeLogScheduler): MetricLogScheduler } - @Module - interface - TestAuthModule { - @Binds - fun bindFakeAuthenticationController( - fakeAuthenticationController: FakeAuthenticationController - ): AuthenticationWrapper - } - // TODO(#89): Move this to a common test application component. @Singleton @Component( @@ -334,7 +324,7 @@ class LogReportWorkManagerInitializerTest { LoggerModule::class, AssetModule::class, LoggerModule::class, PlatformParameterModule::class, PlatformParameterSingletonModule::class, LoggingIdentifierModule::class, SyncStatusModule::class, ApplicationLifecycleModule::class, - CpuPerformanceSnapshotterModule::class, TestAuthModule::class, + CpuPerformanceSnapshotterModule::class, TestAuthenticationModule::class, ] ) interface TestApplicationComponent : DataProvidersInjector { diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt index 38389111469..f5eefedac80 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt @@ -27,7 +27,6 @@ import org.mockito.Mockito.reset import org.oppia.android.app.model.EventLog import org.oppia.android.app.model.OppiaMetricLog import org.oppia.android.app.model.ScreenName.SCREEN_NAME_UNSPECIFIED -import org.oppia.android.domain.auth.AuthenticationWrapper import org.oppia.android.domain.oppialogger.EventLogStorageCacheSize import org.oppia.android.domain.oppialogger.ExceptionLogStorageCacheSize import org.oppia.android.domain.oppialogger.FirestoreLogStorageCacheSize @@ -42,10 +41,10 @@ import org.oppia.android.domain.oppialogger.exceptions.ExceptionsController import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule import org.oppia.android.domain.testing.oppialogger.loguploader.FakeLogUploader import org.oppia.android.testing.FakeAnalyticsEventLogger -import org.oppia.android.testing.FakeAuthenticationController import org.oppia.android.testing.FakeExceptionLogger import org.oppia.android.testing.FakeFirestoreEventLogger import org.oppia.android.testing.FakePerformanceMetricsEventLogger +import org.oppia.android.testing.TestAuthenticationModule import org.oppia.android.testing.data.DataProviderTestMonitor import org.oppia.android.testing.logging.SyncStatusTestModule import org.oppia.android.testing.logging.TestSyncStatusManager @@ -69,7 +68,6 @@ import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.DATA_UPLOADIN import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.INITIAL_UNKNOWN import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.NO_CONNECTIVITY import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.UPLOAD_ERROR -import org.oppia.android.util.logging.firebase.DebugFirestoreEventLogger import org.oppia.android.util.logging.firebase.FirestoreEventLogger import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule @@ -593,10 +591,6 @@ class LogUploadWorkerTest { fun bindFakeFirestoreEventLogger( @MockFirestoreEventLogger delegate: FirestoreEventLogger ): FirestoreEventLogger = delegate - - @Provides - fun bindFirestoreEventLogger(logger: FakeFirestoreEventLogger): - DebugFirestoreEventLogger = logger } @Module @@ -626,14 +620,6 @@ class LogUploadWorkerTest { fun bindsFakeLogUploader(fakeLogUploader: FakeLogUploader): LogUploader } - @Module - interface TestAuthModule { - @Binds - fun bindFakeAuthenticationController( - fakeAuthenticationController: FakeAuthenticationController - ): AuthenticationWrapper - } - // TODO(#89): Move this to a common test application component. @Singleton @Component( @@ -646,7 +632,7 @@ class LogUploadWorkerTest { PlatformParameterSingletonModule::class, LoggingIdentifierModule::class, SyncStatusTestModule::class, PerformanceMetricsAssessorModule::class, ApplicationLifecycleModule::class, PerformanceMetricsConfigurationsModule::class, - TestAuthModule::class, + TestAuthenticationModule::class, ] ) interface TestApplicationComponent : DataProvidersInjector { diff --git a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt index ad16b301201..82e756c79e6 100644 --- a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt @@ -25,6 +25,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.oppia.android.app.model.PlatformParameter +import org.oppia.android.app.model.PlatformParameter.SyncStatus import org.oppia.android.data.backends.gae.BaseUrl import org.oppia.android.data.backends.gae.JsonPrefixNetworkInterceptor import org.oppia.android.data.backends.gae.NetworkApiKey @@ -95,21 +96,25 @@ class PlatformParameterSyncUpWorkerTest { private val expectedTestStringParameter = PlatformParameter.newBuilder() .setName(TEST_STRING_PARAM_NAME) .setString(TEST_STRING_PARAM_SERVER_VALUE) + .setSyncStatus(SyncStatus.SYNCED_FROM_SERVER) .build() private val expectedTestIntegerParameter = PlatformParameter.newBuilder() .setName(TEST_INTEGER_PARAM_NAME) .setInteger(TEST_INTEGER_PARAM_SERVER_VALUE) + .setSyncStatus(SyncStatus.SYNCED_FROM_SERVER) .build() private val defaultTestIntegerParameter = PlatformParameter.newBuilder() .setName(TEST_INTEGER_PARAM_NAME) .setInteger(TEST_INTEGER_PARAM_DEFAULT_VALUE) + .setSyncStatus(SyncStatus.NOT_SYNCED_FROM_SERVER) .build() private val expectedTestBooleanParameter = PlatformParameter.newBuilder() .setName(TEST_BOOLEAN_PARAM_NAME) .setBoolean(TEST_BOOLEAN_PARAM_SERVER_VALUE) + .setSyncStatus(SyncStatus.SYNCED_FROM_SERVER) .build() // Not including "expectedTestBooleanParameter" in this list to prove that a refresh took place @@ -322,6 +327,85 @@ class PlatformParameterSyncUpWorkerTest { .containsEntry(TEST_INTEGER_PARAM_NAME, defaultTestIntegerParameter) } + @Test + fun testSyncUpWorker_getFeatureFlags_addSyncStatusFlags_verifyCorrectStatusReturned() { + // Set up versionName to get correct network response from mock platform parameter service. + setUpApplicationForContext(MockPlatformParameterService.appVersionForCorrectResponse) + + // Empty the Platform Parameter Database to simulate the execution of first SyncUp Work request. + platformParameterController.updatePlatformParameterDatabase(listOf()) + + val workManager = WorkManager.getInstance(context) + + val inputData = Data.Builder().putString( + PlatformParameterSyncUpWorker.WORKER_TYPE_KEY, + PlatformParameterSyncUpWorker.PLATFORM_PARAMETER_WORKER + ).build() + + val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() + .setInputData(inputData) + .build() + + // Enqueue the Work Request to fetch and cache the Platform Parameters from Remote Service. + workManager.enqueue(request) + testCoroutineDispatchers.runCurrent() + + // Retrieve the previously cached Platform Parameters from Cache Store. + monitorFactory.ensureDataProviderExecutes(platformParameterController.getParameterDatabase()) + + // Values retrieved from Cache store will be sent to Platform Parameter Singleton by the + // Controller in the form of a Map, therefore verify the retrieved values from that Map. + val platformParameterMap = platformParameterSingleton.getPlatformParameterMap() + + // Previous String Platform Parameter is still same in the Database. + assertThat(platformParameterMap) + .containsEntry(TEST_STRING_PARAM_NAME, expectedTestStringParameter) + + // SyncStatus of the platform parameter is SYNCED_FROM_SERVER + assertThat(platformParameterMap[TEST_STRING_PARAM_NAME]?.syncStatus) + .isEqualTo(SyncStatus.SYNCED_FROM_SERVER) + } + + @Test + fun testSyncUpWorker_databaseNotEmpty_getEmptyResponse_verifySyncStatusNotUpdated() { + // Set up versionName to get incorrect network response from mock platform parameter service. + setUpApplicationForContext(MockPlatformParameterService.appVersionForEmptyResponse) + + // Fill the Platform Parameter Database with mock values to simulate the execution of a SyncUp + // Work request that is not first. + platformParameterController.updatePlatformParameterDatabase(mockPlatformParameterList) + + val workManager = WorkManager.getInstance(context) + + val inputData = Data.Builder().putString( + PlatformParameterSyncUpWorker.WORKER_TYPE_KEY, + PlatformParameterSyncUpWorker.PLATFORM_PARAMETER_WORKER + ).build() + + val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() + .setInputData(inputData) + .build() + + // Enqueue the Work Request to fetch and cache the Platform Parameters from Remote Service. + workManager.enqueue(request) + testCoroutineDispatchers.runCurrent() + + // Retrieve the previously cached Platform Parameters from Cache Store. + monitorFactory.ensureDataProviderExecutes(platformParameterController.getParameterDatabase()) + + // Values retrieved from Cache store will be sent to Platform Parameter Singleton by the + // Controller in the form of a Map, therefore verify the retrieved values from that Map. + val platformParameterMap = platformParameterSingleton.getPlatformParameterMap() + + // Previous Integer Platform Parameter is still same in the Database. + assertThat(platformParameterMap) + .containsEntry(TEST_INTEGER_PARAM_NAME, defaultTestIntegerParameter) + + // SyncStatus of the platform parameter is still the same in the database + assertThat(platformParameterMap[TEST_INTEGER_PARAM_NAME]?.syncStatus) + .isEqualTo(SyncStatus.NOT_SYNCED_FROM_SERVER) + } + private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) } 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 62733f8119c..e95142771be 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 @@ -1356,9 +1356,9 @@ class ProfileManagementControllerTest { fun provideLearnerStudyAnalytics(): 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 - } + return PlatformParameterValue.createDefaultParameter( + defaultValue = enableFeature + ) } @Provides @@ -1367,9 +1367,9 @@ class ProfileManagementControllerTest { 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 - } + return PlatformParameterValue.createDefaultParameter( + defaultValue = enableFeature + ) } } diff --git a/domain/src/test/java/org/oppia/android/domain/survey/SurveyControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/survey/SurveyControllerTest.kt index eab5e104975..4517b0b6504 100644 --- a/domain/src/test/java/org/oppia/android/domain/survey/SurveyControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/survey/SurveyControllerTest.kt @@ -5,7 +5,6 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import dagger.Binds import dagger.BindsInstance import dagger.Component import dagger.Module @@ -15,13 +14,12 @@ import org.junit.Test import org.junit.runner.RunWith import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.SurveyQuestionName -import org.oppia.android.domain.auth.AuthenticationWrapper import org.oppia.android.domain.exploration.ExplorationProgressModule import org.oppia.android.domain.oppialogger.ApplicationIdSeed import org.oppia.android.domain.oppialogger.LogStorageModule import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule -import org.oppia.android.testing.FakeAuthenticationController import org.oppia.android.testing.FakeExceptionLogger +import org.oppia.android.testing.TestAuthenticationModule import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.data.DataProviderTestMonitor import org.oppia.android.testing.robolectric.RobolectricModule @@ -206,14 +204,6 @@ class SurveyControllerTest { fun provideApplicationIdSeed(): Long = applicationIdSeed } - @Module - interface TestAuthModule { - @Binds - fun bindFakeAuthenticationController( - fakeAuthenticationController: FakeAuthenticationController - ): AuthenticationWrapper - } - // TODO(#89): Move this to a common test application component. @Singleton @Component( @@ -222,7 +212,7 @@ class SurveyControllerTest { ApplicationLifecycleModule::class, TestDispatcherModule::class, LocaleProdModule::class, ExplorationProgressModule::class, TestLogReportingModule::class, AssetModule::class, NetworkConnectionUtilDebugModule::class, SyncStatusModule::class, LogStorageModule::class, - TestLoggingIdentifierModule::class, TestAuthModule::class, + TestLoggingIdentifierModule::class, TestAuthenticationModule::class, ] ) interface TestApplicationComponent : DataProvidersInjector { diff --git a/domain/src/test/java/org/oppia/android/domain/survey/SurveyProgressControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/survey/SurveyProgressControllerTest.kt index 3a28e41cb4b..e0e8e1deade 100644 --- a/domain/src/test/java/org/oppia/android/domain/survey/SurveyProgressControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/survey/SurveyProgressControllerTest.kt @@ -5,7 +5,6 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import dagger.Binds import dagger.BindsInstance import dagger.Component import dagger.Module @@ -19,15 +18,14 @@ import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.SurveyQuestionName import org.oppia.android.app.model.SurveySelectedAnswer import org.oppia.android.app.model.UserTypeAnswer -import org.oppia.android.domain.auth.AuthenticationWrapper import org.oppia.android.domain.exploration.ExplorationProgressModule import org.oppia.android.domain.oppialogger.ApplicationIdSeed import org.oppia.android.domain.oppialogger.LogStorageModule import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule import org.oppia.android.testing.FakeAnalyticsEventLogger -import org.oppia.android.testing.FakeAuthenticationController import org.oppia.android.testing.FakeExceptionLogger import org.oppia.android.testing.FakeFirestoreEventLogger +import org.oppia.android.testing.TestAuthenticationModule import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.data.DataProviderTestMonitor import org.oppia.android.testing.logging.EventLogSubject @@ -560,14 +558,6 @@ class SurveyProgressControllerTest { fun provideApplicationIdSeed(): Long = applicationIdSeed } - @Module - interface TestAuthModule { - @Binds - fun bindFakeAuthenticationController( - fakeAuthenticationController: FakeAuthenticationController - ): AuthenticationWrapper - } - // TODO(#89): Move this to a common test application component. @Singleton @Component( @@ -576,7 +566,7 @@ class SurveyProgressControllerTest { ApplicationLifecycleModule::class, TestDispatcherModule::class, LocaleProdModule::class, ExplorationProgressModule::class, TestLogReportingModule::class, AssetModule::class, NetworkConnectionUtilDebugModule::class, SyncStatusModule::class, LogStorageModule::class, - TestLoggingIdentifierModule::class, TestAuthModule::class, + TestLoggingIdentifierModule::class, TestAuthenticationModule::class, ] ) diff --git a/domain/src/test/resources/robolectric.properties b/domain/src/test/resources/robolectric.properties index eb1a9bb98c2..cedb3da0a90 100644 --- a/domain/src/test/resources/robolectric.properties +++ b/domain/src/test/resources/robolectric.properties @@ -1,3 +1,3 @@ # domain/src/test/resources/robolectric.properties -# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 31 +# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 33 sdk=30 diff --git a/instrumentation/BUILD.bazel b/instrumentation/BUILD.bazel index 22a68271e54..03c56337516 100644 --- a/instrumentation/BUILD.bazel +++ b/instrumentation/BUILD.bazel @@ -18,7 +18,7 @@ android_binary( manifest_values = { "applicationId": "org.oppia.android", "minSdkVersion": "19", - "targetSdkVersion": "31", + "targetSdkVersion": "33", "versionCode": "0", "versionName": "0.1-test", }, diff --git a/model/src/main/proto/platform_parameter.proto b/model/src/main/proto/platform_parameter.proto index 67f9fd6d772..58e45f8325c 100644 --- a/model/src/main/proto/platform_parameter.proto +++ b/model/src/main/proto/platform_parameter.proto @@ -18,6 +18,19 @@ message PlatformParameter { // Indicates a string-typed platform parameter. string string = 4; } + // Indicates the sync status of the platform parameter. + SyncStatus sync_status = 5; + + enum SyncStatus { + // Indicates that the sync status isn't yet known. + SYNC_STATUS_UNSPECIFIED = 0; + + // Indicates that the parameter isn't yet synced with the remote server. + NOT_SYNCED_FROM_SERVER = 1; + + // Indicates the parameter's value has been synced with the remote server. + SYNCED_FROM_SERVER = 2; + } } // Format of platform parameters stored on disk. It closely resembles the JSON response in cache. diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index d16a0ac1e16..f4c860cb647 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -652,8 +652,11 @@ exempted_file_path: "data/src/main/java/org/oppia/android/data/backends/gae/mode exempted_file_path: "data/src/main/java/org/oppia/android/data/backends/gae/model/GaeVoiceover.kt" exempted_file_path: "data/src/main/java/org/oppia/android/data/backends/gae/model/GaeWrittenTranslation.kt" exempted_file_path: "data/src/main/java/org/oppia/android/data/backends/gae/model/GaeWrittenTranslations.kt" -exempted_file_path: "domain/src/main/java/org/oppia/android/domain/auth/AuthenticationController.kt" -exempted_file_path: "domain/src/main/java/org/oppia/android/domain/auth/AuthenticationWrapper.kt" +exempted_file_path: "domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstance.kt" +exempted_file_path: "domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapper.kt" +exempted_file_path: "domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthInstanceWrapperImpl.kt" +exempted_file_path: "domain/src/main/java/org/oppia/android/domain/auth/FirebaseAuthWrapper.kt" +exempted_file_path: "domain/src/main/java/org/oppia/android/domain/auth/FirebaseUserWrapper.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/classify/ClassificationContext.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/classify/ClassificationResult.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/classify/GenericInteractionClassifier.kt" @@ -800,10 +803,8 @@ exempted_file_path: "testing/src/main/java/org/oppia/android/testing/network/Moc exempted_file_path: "testing/src/main/java/org/oppia/android/testing/network/MockSubtopicService.kt" exempted_file_path: "testing/src/main/java/org/oppia/android/testing/network/MockTopicService.kt" exempted_file_path: "testing/src/main/java/org/oppia/android/testing/network/RetrofitTestModule.kt" -exempted_file_path: "testing/src/main/java/org/oppia/android/testing/platformparameter/TestBooleanPlatformParameter.kt" -exempted_file_path: "testing/src/main/java/org/oppia/android/testing/platformparameter/TestIntegerPlatformParameter.kt" +exempted_file_path: "testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterConstants.kt" exempted_file_path: "testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt" -exempted_file_path: "testing/src/main/java/org/oppia/android/testing/platformparameter/TestStringPlatformParameter.kt" exempted_file_path: "testing/src/main/java/org/oppia/android/testing/robolectric/IsOnRobolectric.kt" exempted_file_path: "testing/src/main/java/org/oppia/android/testing/robolectric/RobolectricModule.kt" exempted_file_path: "testing/src/main/java/org/oppia/android/testing/threading/BackgroundTestDispatcher.kt" @@ -851,10 +852,13 @@ exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/Loggin exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/SyncStatusManager.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/SyncStatusModule.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/DebugAnalyticsEventLogger.kt" -exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLogger.kt" +exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreInstanceWrapperImpl.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/DebugLogReportingModule.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseAnalyticsEventLogger.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseExceptionLogger.kt" +exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstance.kt" +exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapper.kt" +exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapperImpl.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploader.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploaderModule.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreEventLogger.kt" @@ -892,6 +896,7 @@ exempted_file_path: "utility/src/main/java/org/oppia/android/util/parser/svg/Svg exempted_file_path: "utility/src/main/java/org/oppia/android/util/parser/svg/SvgDecoder.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/parser/svg/SvgPictureDrawable.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/parser/svg/TextSvgDrawableTranscoder.kt" +exempted_file_path: "utility/src/main/java/org/oppia/android/util/platformparameter/FeatureFlagConstants.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterConstants.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterValue.kt" exempted_file_path: "utility/src/main/java/org/oppia/android/util/statusbar/StatusBarColor.kt" diff --git a/testing/build.gradle b/testing/build.gradle index 5964bd9ba16..834206b8704 100644 --- a/testing/build.gradle +++ b/testing/build.gradle @@ -4,12 +4,12 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 31 + compileSdkVersion 33 buildToolsVersion "29.0.2" defaultConfig { minSdkVersion 19 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 1 versionName "1.0" } diff --git a/testing/src/main/java/org/oppia/android/testing/FakeAuthenticationController.kt b/testing/src/main/java/org/oppia/android/testing/FakeAuthenticationController.kt deleted file mode 100644 index 4d67905d115..00000000000 --- a/testing/src/main/java/org/oppia/android/testing/FakeAuthenticationController.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.oppia.android.testing - -import com.google.firebase.auth.FirebaseUser -import kotlinx.coroutines.CompletableDeferred -import org.oppia.android.domain.auth.AuthenticationWrapper -import org.oppia.android.util.data.AsyncResult -import javax.inject.Inject -import javax.inject.Singleton - -/** A test specific fake for the AuthenticationController. */ -@Singleton -class FakeAuthenticationController @Inject constructor() : AuthenticationWrapper { - private var signInIsSuccessful = true - private var currentUser: FirebaseUser? = null - - override fun getCurrentSignedInUser(): FirebaseUser? { - return currentUser - } - - override fun signInAnonymously(): CompletableDeferred> { - val deferredResult = CompletableDeferred>() - - if (signInIsSuccessful) { - deferredResult.complete(AsyncResult.Success(null)) - } else { - val error = Exception("Authentication failed") - deferredResult.complete(AsyncResult.Failure(error)) - } - - return deferredResult - } - - /** Sets whether sign in was successful. */ - fun setSignInSuccessStatus(signInSuccessful: Boolean) { - signInIsSuccessful = signInSuccessful - } - - /** Sets the current signed in user. */ - fun setSignedInUser(firebaseUser: FirebaseUser) { - currentUser = firebaseUser - } -} diff --git a/testing/src/main/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImpl.kt b/testing/src/main/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImpl.kt new file mode 100644 index 00000000000..f82ced1fcc9 --- /dev/null +++ b/testing/src/main/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImpl.kt @@ -0,0 +1,37 @@ +package org.oppia.android.testing + +import org.oppia.android.domain.auth.FirebaseAuthWrapper +import org.oppia.android.domain.auth.FirebaseUserWrapper +import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton + +/** A test specific fake for the [FirebaseAuthWrapper]. */ +@Singleton +class FakeFirebaseAuthWrapperImpl @Inject constructor() : FirebaseAuthWrapper { + private var simulateSuccess: Boolean = true + + /** Fake a successful auth response. */ + fun simulateSignInSuccess() { + simulateSuccess = true + } + + /** Fake a failed auth response. */ + fun simulateSignInFailure() { + simulateSuccess = false + } + + override val currentUser: FirebaseUserWrapper? + get() = if (simulateSuccess) + FirebaseUserWrapper(uid = UUID.randomUUID().toString()) + else + null + + override fun signInAnonymously(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) { + if (simulateSuccess) { + onSuccess.invoke() + } else { + onFailure.invoke(Exception("Sign-in failure")) + } + } +} diff --git a/testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt b/testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt index 56b56a4cc86..b2a16c85704 100644 --- a/testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt +++ b/testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt @@ -1,7 +1,6 @@ package org.oppia.android.testing import org.oppia.android.app.model.EventLog -import org.oppia.android.util.logging.firebase.DebugFirestoreEventLogger import org.oppia.android.util.logging.firebase.FirestoreEventLogger import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject @@ -9,9 +8,7 @@ import javax.inject.Singleton /** A test specific fake for the FirestoreEventLogger. */ @Singleton -class FakeFirestoreEventLogger @Inject constructor() : - DebugFirestoreEventLogger, - FirestoreEventLogger { +class FakeFirestoreEventLogger @Inject constructor() : FirestoreEventLogger { private val eventList = CopyOnWriteArrayList() override fun uploadEvent(eventLog: EventLog) { @@ -41,7 +38,4 @@ class FakeFirestoreEventLogger @Inject constructor() : /** Returns the number of events logged to date (and not cleared by [clearAllEvents]). */ fun getEventListCount(): Int = eventList.size - - /** Returns the list of all [EventLog]s logged since the app opened. */ - override fun getEventList(): List = eventList } diff --git a/testing/src/main/java/org/oppia/android/testing/TestAuthenticationModule.kt b/testing/src/main/java/org/oppia/android/testing/TestAuthenticationModule.kt index fa94c0daa01..ed973730f5a 100644 --- a/testing/src/main/java/org/oppia/android/testing/TestAuthenticationModule.kt +++ b/testing/src/main/java/org/oppia/android/testing/TestAuthenticationModule.kt @@ -2,14 +2,14 @@ package org.oppia.android.testing import dagger.Module import dagger.Provides -import org.oppia.android.domain.auth.AuthenticationWrapper +import org.oppia.android.domain.auth.FirebaseAuthWrapper import javax.inject.Singleton -/** Provides debug authentication dependencies. */ +/** Provides test authentication dependencies. */ @Module class TestAuthenticationModule { @Provides @Singleton - fun provideAuthenticationController(authController: FakeAuthenticationController): - AuthenticationWrapper = authController + fun provideFakeFirebaseAuthWrapper(fakeFirebaseWrapperImpl: FakeFirebaseAuthWrapperImpl): + FirebaseAuthWrapper = fakeFirebaseWrapperImpl } diff --git a/testing/src/main/java/org/oppia/android/testing/TestLogReportingModule.kt b/testing/src/main/java/org/oppia/android/testing/TestLogReportingModule.kt index a9f49c53411..f670d773457 100644 --- a/testing/src/main/java/org/oppia/android/testing/TestLogReportingModule.kt +++ b/testing/src/main/java/org/oppia/android/testing/TestLogReportingModule.kt @@ -4,8 +4,9 @@ import dagger.Binds import dagger.Module import org.oppia.android.util.logging.AnalyticsEventLogger import org.oppia.android.util.logging.ExceptionLogger -import org.oppia.android.util.logging.firebase.DebugFirestoreEventLogger +import org.oppia.android.util.logging.firebase.DebugFirestoreInstanceWrapperImpl import org.oppia.android.util.logging.firebase.FirestoreEventLogger +import org.oppia.android.util.logging.firebase.FirestoreInstanceWrapper import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsEventLogger @@ -35,7 +36,6 @@ interface TestLogReportingModule { ): FirestoreEventLogger @Binds - fun bindDebugFirestoreEventLogger( - fakeFirestoreEventLogger: FakeFirestoreEventLogger - ): DebugFirestoreEventLogger + fun bindFirebaseFirestoreInstanceWrapper(wrapperImpl: DebugFirestoreInstanceWrapperImpl): + FirestoreInstanceWrapper } diff --git a/testing/src/main/java/org/oppia/android/testing/platformparameter/BUILD.bazel b/testing/src/main/java/org/oppia/android/testing/platformparameter/BUILD.bazel index a724de2135e..2ef5618b8fc 100644 --- a/testing/src/main/java/org/oppia/android/testing/platformparameter/BUILD.bazel +++ b/testing/src/main/java/org/oppia/android/testing/platformparameter/BUILD.bazel @@ -10,9 +10,7 @@ kt_android_library( name = "test_constants", testonly = True, srcs = [ - "TestBooleanPlatformParameter.kt", - "TestIntegerPlatformParameter.kt", - "TestStringPlatformParameter.kt", + "TestPlatformParameterConstants.kt", ], visibility = [ "//:oppia_testing_visibility", diff --git a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestBooleanPlatformParameter.kt b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestBooleanPlatformParameter.kt deleted file mode 100644 index de74d381739..00000000000 --- a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestBooleanPlatformParameter.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.oppia.android.testing.platformparameter - -import javax.inject.Qualifier - -/** - * Qualifier for test boolean platform parameter. Only used in tests related to platform parameter. - */ -@Qualifier -annotation class TestBooleanParam - -/** - * Name for the test boolean platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_BOOLEAN_PARAM_NAME = "test_boolean_param_name" - -/** - * Default value for the test boolean platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_BOOLEAN_PARAM_DEFAULT_VALUE = false - -/** - * Server value for the test boolean platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_BOOLEAN_PARAM_SERVER_VALUE = true diff --git a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestIntegerPlatformParameter.kt b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestIntegerPlatformParameter.kt deleted file mode 100644 index 956d3f1d4e0..00000000000 --- a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestIntegerPlatformParameter.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.oppia.android.testing.platformparameter - -import javax.inject.Qualifier - -/** - * Qualifier for test integer platform parameter. Only used in tests related to platform parameter. - */ -@Qualifier -annotation class TestIntegerParam - -/** - * Name for the test integer platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_INTEGER_PARAM_NAME = "test_integer_param_name" - -/** - * Default value for the test integer platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_INTEGER_PARAM_DEFAULT_VALUE = 0 - -/** - * Server value for the test integer platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_INTEGER_PARAM_SERVER_VALUE = 1 diff --git a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterConstants.kt b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterConstants.kt new file mode 100644 index 00000000000..a904314f795 --- /dev/null +++ b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterConstants.kt @@ -0,0 +1,68 @@ +package org.oppia.android.testing.platformparameter + +import javax.inject.Qualifier + +/** + * Qualifier for test string platform parameter. Only used in tests related to platform parameter. + */ +@Qualifier +annotation class TestStringParam + +/** + * Name for the test string platform parameter. Only used in tests related to platform parameter. + */ +const val TEST_STRING_PARAM_NAME = "test_string_param_name" + +/** + * Default value for the test string platform parameter. Only used in tests related to platform + * parameter. + */ +const val TEST_STRING_PARAM_DEFAULT_VALUE = "test_string_param_default_value" + +/** + * Server value for the test string platform parameter. Only used in tests related to platform + * parameter. + */ +const val TEST_STRING_PARAM_SERVER_VALUE = "test_string_param_value" + +/** + * Qualifier for test boolean platform parameter. Only used in tests related to platform parameter. + */ +@Qualifier +annotation class TestBooleanParam + +/** + * Name for the test boolean platform parameter. Only used in tests related to platform parameter. + */ +const val TEST_BOOLEAN_PARAM_NAME = "test_boolean_param_name" + +/** + * Default value for the test boolean platform parameter. Only used in tests related to platform parameter. + */ +const val TEST_BOOLEAN_PARAM_DEFAULT_VALUE = false + +/** + * Server value for the test boolean platform parameter. Only used in tests related to platform parameter. + */ +const val TEST_BOOLEAN_PARAM_SERVER_VALUE = true + +/** + * Qualifier for test integer platform parameter. Only used in tests related to platform parameter. + */ +@Qualifier +annotation class TestIntegerParam + +/** + * Name for the test integer platform parameter. Only used in tests related to platform parameter. + */ +const val TEST_INTEGER_PARAM_NAME = "test_integer_param_name" + +/** + * Default value for the test integer platform parameter. Only used in tests related to platform parameter. + */ +const val TEST_INTEGER_PARAM_DEFAULT_VALUE = 0 + +/** + * Server value for the test integer platform parameter. Only used in tests related to platform parameter. + */ +const val TEST_INTEGER_PARAM_SERVER_VALUE = 1 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 7ff02772a65..9341414bfd8 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 @@ -9,7 +9,6 @@ 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 @@ -17,7 +16,6 @@ import org.oppia.android.util.platformparameter.ENABLE_INTERACTION_CONFIG_CHANGE import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE 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 @@ -206,14 +204,6 @@ class TestPlatformParameterModule { fun provideEnableInteractionConfigChangeStateRetention(): PlatformParameterValue = PlatformParameterValue.createDefaultParameter(enableInteractionConfigChangeStateRetention) - @Provides - @EnableContinueButtonAnimation - fun provideEnableContinueButtonAnimation(): PlatformParameterValue { - return PlatformParameterValue.createDefaultParameter( - enableContinueButtonAnimation - ) - } - @Provides @EnableSpotlightUi fun provideEnableSpotlightUi(): PlatformParameterValue { @@ -304,7 +294,6 @@ class TestPlatformParameterModule { fun forceEnableDownloadsSupport(value: Boolean) { enableDownloadsSupport = value } - private var enableContinueButtonAnimation = ENABLE_CONTINUE_BUTTON_ANIMATION_DEFAULT_VALUE /** Enables forcing [EnableLanguageSelectionUi] platform parameter flag from tests. */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) @@ -354,12 +343,6 @@ class TestPlatformParameterModule { enablePerformanceMetricsCollection = value } - /** Enables forcing [EnableContinueButtonAnimation] platform parameter flag from tests. */ - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - fun forceEnableContinueButtonAnimation(value: Boolean) { - enableContinueButtonAnimation = value - } - /** Enables forcing [EnableSpotlightUi] platform parameter flag from tests. */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) fun forceEnableSpotlightUi(value: Boolean) { diff --git a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestStringPlatformParameter.kt b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestStringPlatformParameter.kt deleted file mode 100644 index 330623e1ba4..00000000000 --- a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestStringPlatformParameter.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.oppia.android.testing.platformparameter - -import javax.inject.Qualifier - -/** - * Qualifier for test string platform parameter. Only used in tests related to platform parameter. - */ -@Qualifier -annotation class TestStringParam - -/** - * Name for the test string platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_STRING_PARAM_NAME = "test_string_param_name" - -/** - * Default value for the test string platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_STRING_PARAM_DEFAULT_VALUE = "test_string_param_default_value" - -/** - * Server value for the test string platform parameter. Only used in tests related to platform parameter. - */ -const val TEST_STRING_PARAM_SERVER_VALUE = "test_string_param_value" diff --git a/testing/src/test/java/org/oppia/android/testing/FakeAuthenticationControllerTest.kt b/testing/src/test/java/org/oppia/android/testing/FakeAuthenticationControllerTest.kt deleted file mode 100644 index 42498d839ac..00000000000 --- a/testing/src/test/java/org/oppia/android/testing/FakeAuthenticationControllerTest.kt +++ /dev/null @@ -1,168 +0,0 @@ -package org.oppia.android.testing - -import android.app.Application -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat -import com.google.firebase.auth.FirebaseUser -import dagger.Binds -import dagger.BindsInstance -import dagger.Component -import dagger.Module -import dagger.Provides -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.mock -import org.oppia.android.domain.auth.AuthenticationWrapper -import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule -import org.oppia.android.testing.robolectric.RobolectricModule -import org.oppia.android.testing.threading.TestCoroutineDispatchers -import org.oppia.android.testing.threading.TestDispatcherModule -import org.oppia.android.testing.time.FakeOppiaClockModule -import org.oppia.android.util.data.AsyncResult -import org.oppia.android.util.data.DataProvidersInjector -import org.oppia.android.util.data.DataProvidersInjectorProvider -import org.oppia.android.util.threading.BackgroundDispatcher -import org.robolectric.annotation.Config -import org.robolectric.annotation.LooperMode -import javax.inject.Inject -import javax.inject.Singleton - -/** Tests for [FakeAuthenticationController]. */ -// FunctionName: test names are conventionally named with underscores. -@Suppress("FunctionName") -@RunWith(AndroidJUnit4::class) -@LooperMode(LooperMode.Mode.PAUSED) -@Config(application = FakeAuthenticationControllerTest.TestApplication::class) -class FakeAuthenticationControllerTest { - @Inject - lateinit var fakeAuthenticationController: FakeAuthenticationController - - @Inject - lateinit var authenticationWrapper: AuthenticationWrapper - - @field:[Inject BackgroundDispatcher] - lateinit var backgroundDispatcher: CoroutineDispatcher - - @Inject - lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - - private lateinit var mockFirebaseUser: FirebaseUser - - @Before - fun setUp() { - setUpTestApplicationComponent() - mockFirebaseUser = mock(FirebaseUser::class.java) - } - - @Test - fun testAuthentication_getCurrentSignedInUser() { - fakeAuthenticationController.setSignedInUser(mockFirebaseUser) - val user = fakeAuthenticationController.getCurrentSignedInUser() - - assertThat(user).isInstanceOf(FirebaseUser::class.java) - } - - @Test - fun testFakeController_signInAnonymously_succeeds() { - fakeAuthenticationController.setSignInSuccessStatus(true) - - // A successful result is returned - runSynchronously { fakeAuthenticationController.signInAnonymously().await() } - } - - private fun runSynchronously(operation: suspend () -> Unit) = - CoroutineScope(backgroundDispatcher).async { operation() }.waitForSuccessfulResult() - - private fun Deferred.waitForSuccessfulResult() { - return when (val result = waitForResult()) { - is AsyncResult.Pending -> error("Deferred never finished.") - is AsyncResult.Success -> {} // Nothing to do; the result succeeded. - is AsyncResult.Failure -> throw IllegalStateException("Deferred failed", result.error) - } - } - - private fun Deferred.waitForResult() = toStateFlow().waitForLatestValue() - - private fun Deferred.toStateFlow(): StateFlow> { - val deferred = this - return MutableStateFlow>(value = AsyncResult.Pending()).also { flow -> - CoroutineScope(backgroundDispatcher).async { - try { - val result = deferred.await() - flow.emit(AsyncResult.Success(result)) - } catch (e: Throwable) { - flow.emit(AsyncResult.Failure(e)) - } - } - } - } - - private fun StateFlow.waitForLatestValue(): T = - also { testCoroutineDispatchers.runCurrent() }.value - - private fun setUpTestApplicationComponent() { - ApplicationProvider.getApplicationContext() - .inject(this) - } - - @Module - class TestModule { - @Provides - @Singleton - fun provideContext(application: Application): Context { - return application - } - } - - @Module - interface TestAuthModule { - @Binds - fun bindFakeAuthenticationController( - fakeAuthenticationController: FakeAuthenticationController - ): AuthenticationWrapper - } - - // TODO(#89): Move this to a common test application component. - @Singleton - @Component( - modules = [ - TestModule::class, RobolectricModule::class, FakeOppiaClockModule::class, - ApplicationLifecycleModule::class, TestDispatcherModule::class, TestAuthModule::class, - TestLogReportingModule::class, - ] - ) - interface TestApplicationComponent : DataProvidersInjector { - @Component.Builder - interface Builder { - @BindsInstance - fun setApplication(application: Application): Builder - - fun build(): TestApplicationComponent - } - - fun inject(test: FakeAuthenticationControllerTest) - } - - class TestApplication : Application(), DataProvidersInjectorProvider { - private val component: TestApplicationComponent by lazy { - DaggerFakeAuthenticationControllerTest_TestApplicationComponent.builder() - .setApplication(this) - .build() - } - - fun inject(test: FakeAuthenticationControllerTest) { - component.inject(test) - } - - override fun getDataProvidersInjector(): DataProvidersInjector = component - } -} diff --git a/testing/src/test/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImplTest.kt b/testing/src/test/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImplTest.kt new file mode 100644 index 00000000000..a56a5ed07f8 --- /dev/null +++ b/testing/src/test/java/org/oppia/android/testing/FakeFirebaseAuthWrapperImplTest.kt @@ -0,0 +1,115 @@ +package org.oppia.android.testing + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.auth.FirebaseUserWrapper +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.threading.BackgroundDispatcher +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [FakeFirebaseAuthWrapperImpl]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = FakeFirebaseAuthWrapperImplTest.TestApplication::class) +class FakeFirebaseAuthWrapperImplTest { + @Inject + lateinit var fakeFirebaseAuthWrapperImpl: FakeFirebaseAuthWrapperImpl + + @field:[Inject BackgroundDispatcher] + lateinit var backgroundDispatcher: CoroutineDispatcher + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testFakeAuthWrapper_getCurrentSignedInUser_userIsSignedIn_returnsFirebaseUserWrapper() { + fakeFirebaseAuthWrapperImpl.simulateSignInSuccess() + val user = fakeFirebaseAuthWrapperImpl.currentUser + + assertThat(user).isInstanceOf(FirebaseUserWrapper::class.java) + } + + @Test + fun testFakeAuthWrapper_getCurrentSignedInUser_userIsNotSignedIn_returnsNull() { + fakeFirebaseAuthWrapperImpl.simulateSignInFailure() + val user = fakeFirebaseAuthWrapperImpl.currentUser + + assertThat(user).isNull() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext() + .inject(this) + } + + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, RobolectricModule::class, FakeOppiaClockModule::class, + ApplicationLifecycleModule::class, TestDispatcherModule::class, + TestAuthenticationModule::class, TestLogReportingModule::class, + ] + ) + interface TestApplicationComponent : DataProvidersInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: FakeFirebaseAuthWrapperImplTest) + } + + class TestApplication : Application(), DataProvidersInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerFakeFirebaseAuthWrapperImplTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: FakeFirebaseAuthWrapperImplTest) { + component.inject(test) + } + + override fun getDataProvidersInjector(): DataProvidersInjector = component + } +} diff --git a/testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt b/testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt index 0afaa507962..41ce667b82f 100644 --- a/testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt @@ -18,7 +18,7 @@ import org.oppia.android.domain.oppialogger.LogStorageModule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestDispatcherModule import org.oppia.android.testing.time.FakeOppiaClockModule -import org.oppia.android.util.logging.firebase.DebugFirestoreEventLogger +import org.oppia.android.util.logging.firebase.FirestoreEventLogger import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -36,7 +36,7 @@ class FakeFirestoreEventLoggerTest { lateinit var fakeEventLogger: FakeFirestoreEventLogger @Inject - lateinit var eventLogger: DebugFirestoreEventLogger + lateinit var eventLogger: FirestoreEventLogger private val eventLog1 = EventLog.newBuilder().setPriority(Priority.ESSENTIAL).build() private val eventLog2 = EventLog.newBuilder().setPriority(Priority.OPTIONAL).build() diff --git a/testing/src/test/java/org/oppia/android/testing/TestAuthenticationModuleTest.kt b/testing/src/test/java/org/oppia/android/testing/TestAuthenticationModuleTest.kt index 1d006a45b17..e9a9a5f9b23 100644 --- a/testing/src/test/java/org/oppia/android/testing/TestAuthenticationModuleTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/TestAuthenticationModuleTest.kt @@ -4,8 +4,7 @@ import android.app.Application import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth -import dagger.Binds +import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component import dagger.Module @@ -13,7 +12,7 @@ import dagger.Provides import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.oppia.android.domain.auth.AuthenticationWrapper +import org.oppia.android.domain.auth.FirebaseAuthWrapper import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestDispatcherModule import org.oppia.android.util.data.DataProvidersInjector @@ -32,7 +31,7 @@ import javax.inject.Singleton class TestAuthenticationModuleTest { @Inject - lateinit var listener: AuthenticationWrapper + lateinit var firebaseAuthWrapper: FirebaseAuthWrapper @Before fun setUp() { @@ -40,8 +39,8 @@ class TestAuthenticationModuleTest { } @Test - fun testModule_injectsInstanceOfAuthenticationWrapper() { - Truth.assertThat(listener).isInstanceOf(FakeAuthenticationController::class.java) + fun testModule_injectsFakeInstanceOfFirebaseAuthWrapper() { + assertThat(firebaseAuthWrapper).isInstanceOf(FakeFirebaseAuthWrapperImpl::class.java) } private fun setUpTestApplicationComponent() { @@ -55,24 +54,17 @@ class TestAuthenticationModuleTest { class TestModule { @Provides @Singleton - fun provideContext(): Context { - return ApplicationProvider.getApplicationContext() + fun provideContext(application: Application): Context { + return application } } - @Module - interface AuthenticationModule { - @Binds - fun provideAuthenticationController(fakeAuthenticationController: FakeAuthenticationController): - AuthenticationWrapper - } - // TODO(#89): Move this to a common test application component. @Singleton @Component( modules = [ - TestModule::class, TestDispatcherModule::class, TestAuthenticationModule::class, - RobolectricModule::class, DebugLogReportingModule::class + TestModule::class, TestDispatcherModule::class, RobolectricModule::class, + DebugLogReportingModule::class, TestAuthenticationModule::class ] ) interface TestApplicationComponent : DataProvidersInjector { diff --git a/testing/src/test/resources/robolectric.properties b/testing/src/test/resources/robolectric.properties index df7e21b43c0..12d726938f8 100644 --- a/testing/src/test/resources/robolectric.properties +++ b/testing/src/test/resources/robolectric.properties @@ -1,3 +1,3 @@ # testing/src/test/resources/robolectric.properties -# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 31 +# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 33 sdk=30 diff --git a/utility/build.gradle b/utility/build.gradle index 246c6d05559..8df80918b5a 100644 --- a/utility/build.gradle +++ b/utility/build.gradle @@ -4,12 +4,12 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 31 + compileSdkVersion 33 buildToolsVersion "29.0.2" defaultConfig { minSdkVersion 19 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 1 versionName "1.0" javaCompileOptions { @@ -82,7 +82,7 @@ dependencies { 'androidx.appcompat:appcompat:1.0.2', 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03', 'androidx.work:work-runtime-ktx:2.4.0', - 'com.github.oppia:androidsvg:1265eb1087056cf3fc2e10442e5545bc65c109ce', + 'com.github.oppia:androidsvg:5bc9c7553e94c3476e8ea32baea3c77567228fcd', 'com.github.oppia:kotlitex:43139c140833c7120f351d63d74b42c253d2b213', 'com.github.bumptech.glide:glide:4.11.0', 'com.google.dagger:dagger:2.24', diff --git a/utility/src/main/AndroidManifest.xml b/utility/src/main/AndroidManifest.xml index 5a8bce58b67..d06626d50ac 100644 --- a/utility/src/main/AndroidManifest.xml +++ b/utility/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/BUILD.bazel b/utility/src/main/java/org/oppia/android/util/logging/firebase/BUILD.bazel index e848cd81ab5..fe3fe29d546 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/BUILD.bazel +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/BUILD.bazel @@ -86,6 +86,7 @@ kt_android_library( ":debug_event_logger", ":debug_firestore_logger_impl", ":firebase_exception_logger", + ":debug_firestore_wrapper_impl", ], ) @@ -96,6 +97,7 @@ kt_android_library( ], deps = [ ":firestore_logger", + ":firestore_wrapper_impl", "//third_party:androidx_work_work-runtime", "//third_party:androidx_work_work-runtime-ktx", "//third_party:com_google_firebase_firebase-firestore-ktx", @@ -115,27 +117,50 @@ kt_android_library( ) kt_android_library( - name = "debug_firestore_logger", + name = "debug_firestore_logger_impl", srcs = [ - "DebugFirestoreEventLogger.kt", + "DebugFirestoreEventLoggerImpl.kt", + ], + visibility = [ + "//app:__pkg__", ], deps = [ - "//model/src/main/proto:event_logger_java_proto_lite", + ":firestore_logger_impl", + ":firestore_wrapper_impl", + "//third_party:javax_inject_javax_inject", ], ) kt_android_library( - name = "debug_firestore_logger_impl", + name = "firestore_wrapper", srcs = [ - "DebugFirestoreEventLoggerImpl.kt", + "FirestoreInstance.kt", + "FirestoreInstanceWrapper.kt", ], - visibility = [ - "//app:__pkg__", + visibility = ["//:oppia_api_visibility"], + deps = [ + "//third_party:com_google_firebase_firebase-firestore-ktx", + "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-core", + "//utility/src/main/java/org/oppia/android/util/data:async_result", ], +) + +kt_android_library( + name = "firestore_wrapper_impl", + srcs = ["FirestoreInstanceWrapperImpl.kt"], + visibility = ["//:oppia_prod_module_visibility"], deps = [ - ":debug_firestore_logger", - ":firestore_logger_impl", - "//third_party:javax_inject_javax_inject", + ":dagger", + ":firestore_wrapper", + ], +) + +kt_android_library( + name = "debug_firestore_wrapper_impl", + srcs = ["DebugFirestoreInstanceWrapperImpl.kt"], + visibility = ["//app:__pkg__",], + deps = [ + ":firestore_wrapper_impl", ], ) diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLogger.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLogger.kt deleted file mode 100644 index 09e163ddffe..00000000000 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLogger.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.oppia.android.util.logging.firebase - -import org.oppia.android.app.model.EventLog - -/** Logger for debug implementations of Firestore functionality. */ -interface DebugFirestoreEventLogger { - /** - * Converts eventLogs to Firestore documents and uploads or save them on disk. - * - * @param eventLog which contains all the relevant data to be reported. - */ - fun uploadEvent(eventLog: EventLog) - - /** Returns the list of all [EventLog]s logged since the app opened. */ - fun getEventList(): List -} diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLoggerImpl.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLoggerImpl.kt index cee251716e2..f5d39f998cd 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLoggerImpl.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLoggerImpl.kt @@ -6,15 +6,15 @@ import javax.inject.Inject import javax.inject.Singleton /** - * An implementation of [DebugFirestoreEventLogger] used in developer-only builds of the app. + * A debug implementation of [FirestoreEventLogger] used in developer-only builds of the app. * * It forwards events to a production [FirestoreEventLogger] for real logging, but it also records logged * events for later retrieval (e.g. via [getEventList]). */ @Singleton class DebugFirestoreEventLoggerImpl @Inject constructor( - private val realEventLogger: FirestoreEventLogger -) : DebugFirestoreEventLogger { + private val realEventLogger: FirestoreEventLoggerProdImpl +) : FirestoreEventLogger { private val eventList = CopyOnWriteArrayList() override fun uploadEvent(eventLog: EventLog) { @@ -23,7 +23,7 @@ class DebugFirestoreEventLoggerImpl @Inject constructor( } /** Returns the list of all [EventLog]s logged for Firestore. */ - override fun getEventList(): List = eventList + fun getEventList(): List = eventList /** Returns the most recently logged event. */ fun getMostRecentEvent(): EventLog = getEventList().last() diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreInstanceWrapperImpl.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreInstanceWrapperImpl.kt new file mode 100644 index 00000000000..b278c77a281 --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugFirestoreInstanceWrapperImpl.kt @@ -0,0 +1,17 @@ +package org.oppia.android.util.logging.firebase + +import javax.inject.Inject +import javax.inject.Singleton + +/** + * A test and debug fake for the [FirestoreInstanceWrapper]. This is also used in debug environments + * to make the debug implementation testable. [FirebaseFirestore] requires an instance of []FirebaseApp], + * which is difficult to mock or fake hence this implementation always returns null when an instance + * of [FirebaseFirestore] is requested. + */ +@Singleton +class DebugFirestoreInstanceWrapperImpl @Inject constructor() : FirestoreInstanceWrapper { + + override val firestoreInstance: FirestoreInstance? + get() = null +} diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugLogReportingModule.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugLogReportingModule.kt index 304360e2df3..1cc0eb74769 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugLogReportingModule.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/DebugLogReportingModule.kt @@ -31,10 +31,11 @@ class DebugLogReportingModule { @Provides @Singleton fun provideDebugFirestoreLogger(debugFirestoreEventLogger: DebugFirestoreEventLoggerImpl): - DebugFirestoreEventLogger = debugFirestoreEventLogger + FirestoreEventLogger = debugFirestoreEventLogger @Provides @Singleton - fun provideFirestoreLogger(factory: FirestoreEventLoggerProdImpl.Factory): - FirestoreEventLogger = factory.createFirestoreEventLogger() + fun provideFirebaseFirestoreInstanceWrapper( + fakeWrapperImpl: DebugFirestoreInstanceWrapperImpl + ): FirestoreInstanceWrapper = fakeWrapperImpl } diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreEventLoggerProdImpl.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreEventLoggerProdImpl.kt index 09b829e8bbe..7c59d59e955 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreEventLoggerProdImpl.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreEventLoggerProdImpl.kt @@ -1,21 +1,16 @@ package org.oppia.android.util.logging.firebase -import com.google.firebase.firestore.FirebaseFirestore import org.oppia.android.app.model.EventLog import org.oppia.android.util.logging.ConsoleLogger import javax.inject.Inject /** Logger for uploading to Firestore. */ -class FirestoreEventLoggerProdImpl private constructor( - private val firebaseFirestore: FirebaseFirestore, - private val consoleLogger: ConsoleLogger +class FirestoreEventLoggerProdImpl @Inject constructor( + private val consoleLogger: ConsoleLogger, + private val firestoreInstanceWrapper: FirestoreInstanceWrapper ) : FirestoreEventLogger { /** Converts an event to a document and uploads it to Firebase Firestore. */ override fun uploadEvent(eventLog: EventLog) { - uploadOptionalResponseDocument(eventLog) - } - - private fun uploadOptionalResponseDocument(eventLog: EventLog) { val eventContext = eventLog.context.optionalResponse val document = hashMapOf( "survey_id" to eventContext.surveyDetails.surveyId, @@ -23,24 +18,15 @@ class FirestoreEventLoggerProdImpl private constructor( "time_submitted" to eventLog.timestamp ) - firebaseFirestore.collection("nps_survey_open_feedback") - .add(document) - .addOnSuccessListener { + firestoreInstanceWrapper.firestoreInstance?.firebaseFirestore + ?.collection("nps_survey_open_feedback") + ?.add(document) + ?.addOnSuccessListener { + println("upload successful") consoleLogger.i("FirestoreEventLoggerProdImpl", "Upload to Firestore was successful") } - .addOnFailureListener { e -> + ?.addOnFailureListener { e -> consoleLogger.e("FirestoreEventLoggerProdImpl", e.toString(), e) } } - - /** Application-scoped injectable factory for creating a new [FirestoreEventLoggerProdImpl]. */ - class Factory @Inject constructor( - private val consoleLogger: ConsoleLogger - ) { - private val firestoreDatabase by lazy { FirebaseFirestore.getInstance() } - - /** Returns a new [FirestoreEventLoggerProdImpl] for the current application context. */ - fun createFirestoreEventLogger(): FirestoreEventLoggerProdImpl = - FirestoreEventLoggerProdImpl(firestoreDatabase, consoleLogger) - } } diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstance.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstance.kt new file mode 100644 index 00000000000..a2894cee29d --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstance.kt @@ -0,0 +1,8 @@ +package org.oppia.android.util.logging.firebase + +import com.google.firebase.firestore.FirebaseFirestore + +/** Wrapper for [FirebaseFirestore], used to pass an instance of [FirebaseFirestore]. */ +data class FirestoreInstance( + val firebaseFirestore: FirebaseFirestore +) diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapper.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapper.kt new file mode 100644 index 00000000000..246bb505ad4 --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapper.kt @@ -0,0 +1,7 @@ +package org.oppia.android.util.logging.firebase + +/** Interface for providing an implementation of [FirestoreInstance]. */ +interface FirestoreInstanceWrapper { + /** Returns a wrapped instance of FirebaseFirestore. */ + val firestoreInstance: FirestoreInstance? +} diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapperImpl.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapperImpl.kt new file mode 100644 index 00000000000..039c1545509 --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreInstanceWrapperImpl.kt @@ -0,0 +1,13 @@ +package org.oppia.android.util.logging.firebase + +import com.google.firebase.firestore.ktx.firestore +import com.google.firebase.ktx.Firebase +import javax.inject.Inject + +/** Implementation of [FirestoreInstanceWrapper]. */ +class FirestoreInstanceWrapperImpl @Inject constructor() : + FirestoreInstanceWrapper { + + override val firestoreInstance: FirestoreInstance + get() = FirestoreInstance(Firebase.firestore) +} diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/LogReportingModule.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/LogReportingModule.kt index 8425b61bc0b..9ab74a2cbd3 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/LogReportingModule.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/LogReportingModule.kt @@ -30,11 +30,11 @@ class LogReportingModule { @Provides @Singleton - fun provideFirestoreLogger(factory: FirestoreEventLoggerProdImpl.Factory): - FirestoreEventLogger = factory.createFirestoreEventLogger() + fun provideFirestoreLogger(factory: FirestoreEventLoggerProdImpl): + FirestoreEventLogger = factory @Provides @Singleton - fun provideDebugFirestoreLogger(debugFirestoreLogger: DebugFirestoreEventLoggerImpl): - DebugFirestoreEventLogger = debugFirestoreLogger + fun provideFirebaseFirestoreInstanceWrapper(wrapperImpl: FirestoreInstanceWrapperImpl): + FirestoreInstanceWrapper = wrapperImpl } diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt b/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt index ca7b8c0b661..cb37742ff19 100755 --- a/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt @@ -75,16 +75,26 @@ class HtmlParser private constructor( supportsLinks: Boolean = false, supportsConceptCards: Boolean = false ): Spannable { + + var htmlContent = rawString + // Canvas does not support RTL, it always starts from left to right in RTL due to which compound drawables are // not center aligned. To avoid this situation check if RTL is enabled and set the textDirection. if (isRtl) { htmlContentTextView.textDirection = View.TEXT_DIRECTION_RTL + + val regex = Regex("""]*>.*?""") + val modifiedHtmlContent = rawString.replace(regex) { + val oppiaImageTag = it.value + """
$oppiaImageTag
""" + } + htmlContent = modifiedHtmlContent } else { htmlContentTextView.textDirection = View.TEXT_DIRECTION_LTR } + htmlContentTextView.invalidate() - var htmlContent = rawString if ("\n\t" in htmlContent) { htmlContent = htmlContent.replace("\n\t", "") } diff --git a/utility/src/main/java/org/oppia/android/util/parser/image/UrlImageParser.kt b/utility/src/main/java/org/oppia/android/util/parser/image/UrlImageParser.kt index b8dd69868a8..137652bd013 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/image/UrlImageParser.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/image/UrlImageParser.kt @@ -15,6 +15,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.widget.TextView +import androidx.core.view.ViewCompat import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition import org.oppia.android.util.R @@ -234,6 +235,11 @@ class UrlImageParser private constructor( private val autoResizeImage: Boolean ) : AutoAdjustingImageTarget(targetConfiguration) { + private fun isRTLMode(): Boolean { + return ViewCompat.getLayoutDirection(htmlContentTextView) == ViewCompat + .LAYOUT_DIRECTION_RTL + } + override fun computeBounds( context: Context, drawable: D, @@ -262,6 +268,8 @@ class UrlImageParser private constructor( var drawableWidth = drawable.intrinsicWidth.toFloat() var drawableHeight = drawable.intrinsicHeight.toFloat() + val maxContentItemPadding = + context.resources.getDimensionPixelSize(R.dimen.maximum_content_item_padding) if (autoResizeImage) { // Treat the drawable's dimensions as dp so that the image scales for higher density // displays. @@ -285,8 +293,7 @@ class UrlImageParser private constructor( drawableHeight *= multipleFactor drawableWidth *= multipleFactor } - val maxContentItemPadding = - context.resources.getDimensionPixelSize(R.dimen.maximum_content_item_padding) + val maximumImageSize = maxAvailableWidth - maxContentItemPadding if (drawableWidth >= maximumImageSize) { // The multipleFactor value is used to make sure that the aspect ratio of the image @@ -304,11 +311,17 @@ class UrlImageParser private constructor( drawableWidth *= multipleFactor } } - val drawableLeft = if (imageCenterAlign) { + + if (drawableWidth >= (maxAvailableWidth - maxContentItemPadding)) { + drawableWidth -= maxContentItemPadding + } + + val drawableLeft = if (imageCenterAlign && !isRTLMode()) { calculateInitialMargin(maxAvailableWidth, drawableWidth) } else { 0f } + val drawableTop = 0f val drawableRight = drawableLeft + drawableWidth val drawableBottom = drawableTop + drawableHeight diff --git a/utility/src/main/java/org/oppia/android/util/parser/svg/ScalableVectorGraphic.kt b/utility/src/main/java/org/oppia/android/util/parser/svg/ScalableVectorGraphic.kt index 06dc224ad89..6692d90d875 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/svg/ScalableVectorGraphic.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/svg/ScalableVectorGraphic.kt @@ -3,9 +3,9 @@ package org.oppia.android.util.parser.svg import android.graphics.Picture import android.graphics.RectF import android.text.TextPaint -import com.caverock.androidsvg.RenderOptions -import com.caverock.androidsvg.SVG -import com.caverock.androidsvg.utils.RenderOptionsBase +import com.caverock.androidsvg.androidrendering.RenderOptions +import com.caverock.androidsvg.androidrendering.RenderOptionsBase +import com.caverock.androidsvg.androidrendering.SVG import org.oppia.android.util.parser.image.ImageTransformation /** @@ -39,18 +39,14 @@ class ScalableVectorGraphic { } /** - * Returns the [SvgSizeSpecs] corresponding to this SVG, based on the specified [textPaint]. If a - * [TextPaint] is supplied, the returns specs will include text-based adjustments (for in-line - * images). Otherwise, the returned specs will be arranged for rendering the SVG in a standalone - * manner. + * Returns the [SvgSizeSpecs] corresponding to this SVG. + * + * The returned specs will be arranged for rendering the SVG in a standalone manner. */ - fun computeSizeSpecs(textPaint: TextPaint?): SvgSizeSpecs { - val options = RenderOptionsBase().also { if (textPaint != null) it.textPaint(textPaint) } + fun computeSizeSpecs(): SvgSizeSpecs { + val options = RenderOptionsBase() val documentWidth = parsedSvg.value.getDocumentWidthOrNull(options) val documentHeight = parsedSvg.value.getDocumentHeightOrNull(options) - val verticalAlignment = if (textPaint != null) { - adjustAlignmentForAndroid(parsedSvg.value.getVerticalAlignment(options)) - } else 0f val viewBox: RectF? = parsedSvg.value.documentViewBox val viewBoxWidth = viewBox?.width() @@ -75,16 +71,54 @@ class ScalableVectorGraphic { intrinsicHeight, renderedWidth = imageFileNameWidth ?: intrinsicWidth, renderedHeight = imageFileNameHeight ?: intrinsicHeight, + verticalAlignment = 0f + ) + } + + /** + * Returns the [SvgSizeSpecs] corresponding to this SVG, based on the specified [textPaint]. + * Based on the supplied [TextPaint], the returned specs will include text-based adjustments + * (for in-line images). + */ + fun computeSizeSpecsForTextPicture(textPaint: TextPaint?): SvgSizeSpecs { + val options = textPaint?.let { RenderOptionsBase().textPaint(it) } ?: RenderOptionsBase() + val documentWidth = parsedSvg.value.getDocumentWidthOrNull(options) + val documentHeight = parsedSvg.value.getDocumentHeightOrNull(options) + + val imageFileNameWidth = extractedWidth?.toFloat() + val imageFileNameHeight = extractedHeight?.toFloat() + + val fontMetrics = textPaint?.fontMetrics + val fontHeight = fontMetrics?.descent?.minus(fontMetrics.ascent) ?: 0f + + val adjustedWidth = + imageFileNameWidth?.convertExToPx(fontHeight) ?: documentWidth ?: DEFAULT_SIZE_PX + val adjustedHeight = + imageFileNameHeight?.convertExToPx(fontHeight) ?: documentHeight ?: DEFAULT_SIZE_PX + + val verticalAlignment = textPaint?.let { + adjustAlignmentForAndroid(parsedSvg.value.getVerticalAlignment(options)) + } ?: 0f + + return SvgSizeSpecs( + adjustedWidth, + adjustedHeight, + renderedWidth = adjustedWidth, + renderedHeight = adjustedHeight, verticalAlignment ) } + private fun Float.convertExToPx(fontHeight: Float): Float { + return this * fontHeight * 0.5f + } + /** * Returns an Android [Picture] including the draw instructions for rendering this SVG within a * line of text whose size and style is configured by the provided [textPaint]. */ fun renderToTextPicture(textPaint: TextPaint): Picture { - return computeSizeSpecs(textPaint).let { (width, height, _) -> + return computeSizeSpecsForTextPicture(textPaint).let { (width, height, _) -> val options = RenderOptions().textPaint(textPaint).viewPort(0f, 0f, width, height) as RenderOptions parsedSvg.value.renderToPicture(options) diff --git a/utility/src/main/java/org/oppia/android/util/parser/svg/SvgPictureDrawable.kt b/utility/src/main/java/org/oppia/android/util/parser/svg/SvgPictureDrawable.kt index 8178e745dca..2197ba013aa 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/svg/SvgPictureDrawable.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/svg/SvgPictureDrawable.kt @@ -100,11 +100,17 @@ abstract class SvgPictureDrawable( * when [textPaint] is null and text rendering when otherwise. */ protected fun reinitialize(textPaint: TextPaint?) { - picture = textPaint?.let { - scalableVectorGraphic.renderToTextPicture(it) - } ?: scalableVectorGraphic.renderToBlockPicture() + val newPicture = if (textPaint != null) { + intrinsicSize = scalableVectorGraphic.computeSizeSpecsForTextPicture(textPaint) + scalableVectorGraphic.renderToTextPicture(textPaint) + } else { + intrinsicSize = scalableVectorGraphic.computeSizeSpecs() + scalableVectorGraphic.renderToBlockPicture() + } + + picture = newPicture + // TODO(#4246): Fix both SVG rendering performance and upscaling to ensure images aren't blurry. - intrinsicSize = scalableVectorGraphic.computeSizeSpecs(textPaint) if (scalableVectorGraphic.shouldBeRenderedAsBitmap()) { recomputeBitmap() } diff --git a/utility/src/main/java/org/oppia/android/util/platformparameter/FeatureFlagConstants.kt b/utility/src/main/java/org/oppia/android/util/platformparameter/FeatureFlagConstants.kt new file mode 100644 index 00000000000..dd45a5a3ae2 --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/platformparameter/FeatureFlagConstants.kt @@ -0,0 +1,157 @@ +package org.oppia.android.util.platformparameter + +import javax.inject.Qualifier + +/** + * This file contains all the constants that are associated with individual Feature Flags. + * These constants are: + * - Qualifier Annotation + * - Feature Flag Name - The name begins with Enable_ + * - Feature Flag Default Value + * - Feature Flag Status - A boolean that keeps track of whether the feature flag + * has been synced with Oppia Web. + */ + +/** + * Qualifier for the feature flag that controls whether the user has support for manually + * downloading topics. + */ +@Qualifier annotation class EnableDownloadsSupport + +/** Name of the feature flag that controls whether to enable downloads support. */ +const val DOWNLOADS_SUPPORT = "downloads_support" + +/** Default value for feature flag corresponding to [EnableDownloadsSupport]. */ +const val ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE = false + +/** Qualifier for the feature flag corresponding to enabling the language selection UI. */ +@Qualifier +annotation class EnableLanguageSelectionUi + +/** Default value for the feature flag corresponding to [EnableLanguageSelectionUi]. */ +const val ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE = true + +/** + * Qualifier for the feature flag corresponding to enabling the extra topic tabs: practice and info. + */ +@Qualifier +annotation class EnableExtraTopicTabsUi + +/** Name of the feature flag that controls whether to enable the extra topics tab UI. */ +const val EXTRA_TOPIC_TABS_UI = "extra_topic_tabs_ui" + +/** Default value for the feature flag corresponding to [EnableExtraTopicTabsUi]. */ +const val ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE = false + +/** + * Qualifier for the feature flag that controls the visibility of [ProfileAndDeviceIdActivity] + * and working of learner study related analytics logging. + */ +@Qualifier +annotation class EnableLearnerStudyAnalytics + +/** + * Name of the feature flag that controls the visibility of [ProfileAndDeviceIdActivity] + * and working of learner study related analytics logging. + */ +const val LEARNER_STUDY_ANALYTICS = "learner_study_analytics" + +/** + * Default value of the feature flag that controls the visibility of [ProfileAndDeviceIdActivity] + * and working of learner study related analytics logging. + */ +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 feature flag 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 feature flag 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 feature flag corresponding to enabling the edit accounts options. */ +@Qualifier +annotation class EnableEditAccountsOptionsUi + +/** Name of the feature flag that controls whether to enable the edit account options UI. */ +const val EDIT_ACCOUNTS_OPTIONS_UI = "edit_accounts_options_ui" + +/** Default value for the feature flag corresponding to [EnableEditAccountsOptionsUi]. */ +const val ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE = false + +/** Qualifier for the feature flag that controls whether to record performance metrics. */ +@Qualifier +annotation class EnablePerformanceMetricsCollection + +/** Name of the feature flag that controls whether to record performance metrics. */ +const val ENABLE_PERFORMANCE_METRICS_COLLECTION = "enable_performance_metrics_collection" + +/** Default value for whether to record performance metrics. */ +const val ENABLE_PERFORMANCE_METRICS_COLLECTION_DEFAULT_VALUE = false + +/** Qualifier for the feature flag corresponding to enabling the spotlight UI. */ +@Qualifier +annotation class EnableSpotlightUi + +/** Name of the feature flag that controls whether to enable the spotlight UI. */ +const val SPOTLIGHT_UI = "spotlight_ui" + +/** Default value for the feature flag corresponding to [EnableSpotlightUi]. */ +const val ENABLE_SPOTLIGHT_UI_DEFAULT_VALUE = false + +/** + * Qualifier for the feature flag that controls whether input interaction state is correctly + * retained across configuration changes. + */ +@Qualifier +annotation class EnableInteractionConfigChangeStateRetention + +/** + * Name of the feature flag that controls whether input interaction state is correctly retained + * across configuration changes. + */ +const val INTERACTION_CONFIG_CHANGE_STATE_RETENTION = "interaction_config_change_state_retention" + +/** + * Default value for feature flag corresponding to [EnableInteractionConfigChangeStateRetention]. + */ +const val ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE = false + +/** + * Qualifier for the [EnableAppAndOsDeprecation] feature flag that controls whether to enable + * app and OS deprecation or not. + */ +@Qualifier +annotation class EnableAppAndOsDeprecation + +/** Name of the feature flag that controls whether to enable app and os deprecation. */ +const val APP_AND_OS_DEPRECATION = "app_and_os_deprecation" + +/** + * Default value for the feature flag corresponding to [EnableAppAndOsDeprecation]. + */ +const val ENABLE_APP_AND_OS_DEPRECATION_DEFAULT_VALUE = false 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 8cb1e1b4297..46121ebf7d9 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 @@ -10,15 +10,6 @@ import javax.inject.Qualifier * - Platform Parameter Default Value */ -/** - * Qualifier for the platform parameter that controls whether the user has support for manually - * downloading topics. - */ -@Qualifier annotation class EnableDownloadsSupport - -/** Default value for feature flag corresponding to [EnableDownloadsSupport]. */ -const val ENABLE_DOWNLOADS_SUPPORT_DEFAULT_VALUE = false - /** * Name of the platform parameter that automatically updates topics when a user toggles the * switch in the [AdministratorControlsFragmentPresenter]. @@ -75,72 +66,6 @@ const val SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS = "sync_up_worker_time_period" */ const val SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS_DEFAULT_VALUE = 12 -/** Qualifier for the feature flag corresponding to enabling the language selection UI. */ -@Qualifier -annotation class EnableLanguageSelectionUi - -/** Default value for the feature flag corresponding to [EnableLanguageSelectionUi]. */ -const val ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE = true - -/** - * Qualifier for the feature flag corresponding to enabling the extra topic tabs: practice and info. - */ -@Qualifier -annotation class EnableExtraTopicTabsUi - -/** Default value for the feature flag corresponding to [EnableExtraTopicTabsUi]. */ -const val ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE = false - -/** - * Qualifier for the platform parameter that controls the visibility of [ProfileAndDeviceIdActivity] - * and working of learner study related analytics logging. - */ -@Qualifier -annotation class EnableLearnerStudyAnalytics - -/** - * Name of the platform parameter that controls the visibility of [ProfileAndDeviceIdActivity] - * and working of learner study related analytics logging. - */ -const val LEARNER_STUDY_ANALYTICS = "learner_study_analytics" - -/** - * Default value of the platform parameter that controls the visibility of [ProfileAndDeviceIdActivity] - * and working of learner study related analytics logging. - */ -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. */ @@ -153,34 +78,6 @@ const val CACHE_LATEX_RENDERING = "cache_latex_rendering" /** Default value for whether to cache LaTeX rendering using Glide. */ const val CACHE_LATEX_RENDERING_DEFAULT_VALUE = true -/** Qualifier for the feature flag corresponding to enabling the edit accounts options. */ -@Qualifier -annotation class EnableEditAccountsOptionsUi - -/** Default value for the feature flag corresponding to [EnableEditAccountsOptionsUi]. */ -const val ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_DEFAULT_VALUE = false - -/** Qualifier for the platform parameter that controls whether to record performance metrics. */ -@Qualifier -annotation class EnablePerformanceMetricsCollection - -/** Name of the platform parameter that controls whether to record performance metrics. */ -const val ENABLE_PERFORMANCE_METRICS_COLLECTION = "enable_performance_metrics_collection" - -/** Default value for whether to record performance metrics. */ -const val ENABLE_PERFORMANCE_METRICS_COLLECTION_DEFAULT_VALUE = false - -/** - * Qualifier for the platform parameter that controls whether to animate the continue button - * interaction and navigation items. This is used to disable the animation during testing because - * Espresso has known problems while testing views that contain animations. - */ -@Qualifier -annotation class EnableContinueButtonAnimation - -/** Default value for whether to enable continue button animation. */ -const val ENABLE_CONTINUE_BUTTON_ANIMATION_DEFAULT_VALUE = true - /** * Qualifier for the platform parameter that controls the time interval in minutes of uploading * previously recorded performance metrics to the remote service. @@ -243,37 +140,6 @@ const val PERFORMANCE_METRICS_COLLECTION_LOW_FREQUENCY_TIME_INTERVAL_IN_MINUTES const val PERFORMANCE_METRICS_COLLECTION_LOW_FREQUENCY_TIME_INTERVAL_IN_MINUTES_DEFAULT_VAL = 1440 -/** Qualifier for the feature flag corresponding to enabling the spotlight UI. */ -@Qualifier -annotation class EnableSpotlightUi - -/** Default value for the feature flag corresponding to [EnableSpotlightUi]. */ -const val ENABLE_SPOTLIGHT_UI_DEFAULT_VALUE = false - -/** - * Qualifier for the platform parameter that controls whether input interaction state is correctly - * retained across configuration changes. - */ -@Qualifier -annotation class EnableInteractionConfigChangeStateRetention - -/** - * Default value for feature flag corresponding to [EnableInteractionConfigChangeStateRetention]. - */ -const val ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE = false - -/** - * Qualifier for the [EnableAppAndOsDeprecation] feature flag that controls whether to enable - * app and OS deprecation or not. - */ -@Qualifier -annotation class EnableAppAndOsDeprecation - -/** - * Default value for the feature flag corresponding to [EnableAppAndOsDeprecation]. - */ -const val ENABLE_APP_AND_OS_DEPRECATION_DEFAULT_VALUE = false - /** * Qualifier for the platform parameter that contains the version code of the latest available * optional app update, which is used to notify the app that a soft update is available. diff --git a/utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterValue.kt b/utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterValue.kt index 309574d9660..beefdee92bb 100644 --- a/utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterValue.kt +++ b/utility/src/main/java/org/oppia/android/util/platformparameter/PlatformParameterValue.kt @@ -1,6 +1,7 @@ package org.oppia.android.util.platformparameter import org.oppia.android.app.model.PlatformParameter +import org.oppia.android.app.model.PlatformParameter.SyncStatus /** * Generic interface that is used to provide platform parameter values corresponding to the @@ -9,16 +10,20 @@ import org.oppia.android.app.model.PlatformParameter */ interface PlatformParameterValue { val value: T + val syncStatus: SyncStatus companion object { /** * Creates a Platform Parameter Implementation containing the default value for a particular * Platform Parameter */ - fun createDefaultParameter(defaultValue: T): PlatformParameterValue { + fun createDefaultParameter( + defaultValue: T, + defaultSyncStatus: SyncStatus = SyncStatus.NOT_SYNCED_FROM_SERVER + ): PlatformParameterValue { return object : PlatformParameterValue { - override val value: T - get() = defaultValue + override val value = defaultValue + override val syncStatus = defaultSyncStatus } } } 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 d2062e9097f..db424118e1f 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 @@ -2414,9 +2414,9 @@ class EventBundleCreatorTest { fun provideLoggingLearnerStudyIds(): PlatformParameterValue { // Snapshot the value so that it doesn't change between injection and use. val enableFeature = enableLoggingLearnerStudyIds - return object : PlatformParameterValue { - override val value: Boolean = enableFeature - } + return PlatformParameterValue.createDefaultParameter( + defaultValue = enableFeature + ) } } 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 562f16b4337..3ec4c2f5169 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 @@ -1581,9 +1581,9 @@ class KenyaAlphaEventBundleCreatorTest { fun provideLoggingLearnerStudyIds(): PlatformParameterValue { // Snapshot the value so that it doesn't change between injection and use. val enableFeature = enableLoggingLearnerStudyIds - return object : PlatformParameterValue { - override val value: Boolean = enableFeature - } + return PlatformParameterValue.createDefaultParameter( + defaultValue = enableFeature + ) } } diff --git a/utility/src/test/java/org/oppia/android/util/logging/firebase/BUILD.bazel b/utility/src/test/java/org/oppia/android/util/logging/firebase/BUILD.bazel index 350471767e3..3ae5970b336 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/firebase/BUILD.bazel +++ b/utility/src/test/java/org/oppia/android/util/logging/firebase/BUILD.bazel @@ -46,7 +46,7 @@ oppia_android_test( "//third_party:com_google_truth_truth", "//third_party:org_robolectric_robolectric", "//third_party:robolectric_android-all", - "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/locale/testing:test_module", "//utility/src/main/java/org/oppia/android/util/logging:standard_event_logging_configuration_module", "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", "//utility/src/main/java/org/oppia/android/util/logging/firebase:prod_module", diff --git a/utility/src/test/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLoggerImplTest.kt b/utility/src/test/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLoggerImplTest.kt index dfc2b93ecf0..87def387d56 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLoggerImplTest.kt +++ b/utility/src/test/java/org/oppia/android/util/logging/firebase/DebugFirestoreEventLoggerImplTest.kt @@ -13,11 +13,11 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.oppia.android.app.model.EventLog -import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.assertThrows import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestDispatcherModule import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.locale.testing.LocaleTestModule import org.oppia.android.util.logging.EnableConsoleLog import org.oppia.android.util.logging.EnableFileLog import org.oppia.android.util.logging.GlobalLogLevel @@ -37,6 +37,9 @@ class DebugFirestoreEventLoggerImplTest { @Inject lateinit var debugFirestoreLoggerImpl: DebugFirestoreEventLoggerImpl + @Inject + lateinit var eventLogger: FirestoreEventLogger + private val eventLog1 = EventLog.newBuilder().setPriority(EventLog.Priority.ESSENTIAL).build() private val eventLog2 = EventLog.newBuilder().setPriority(EventLog.Priority.ESSENTIAL).build() @@ -47,7 +50,7 @@ class DebugFirestoreEventLoggerImplTest { @Test fun testDebugFirestoreEventLogger_logEvent_returnsEvent() { - debugFirestoreLoggerImpl.uploadEvent(eventLog1) + eventLogger.uploadEvent(eventLog1) val event = debugFirestoreLoggerImpl.getMostRecentEvent() Truth.assertThat(event).isEqualTo(eventLog1) @@ -56,8 +59,8 @@ class DebugFirestoreEventLoggerImplTest { @Test fun testDebugFirestoreEventLogger_logEventTwice_returnsLatestEvent() { - debugFirestoreLoggerImpl.uploadEvent(eventLog1) - debugFirestoreLoggerImpl.uploadEvent(eventLog2) + eventLogger.uploadEvent(eventLog1) + eventLogger.uploadEvent(eventLog2) val event = debugFirestoreLoggerImpl.getMostRecentEvent() Truth.assertThat(event).isEqualTo(eventLog2) @@ -65,7 +68,7 @@ class DebugFirestoreEventLoggerImplTest { @Test fun testDebugFirestoreEventLogger_logEvent_clearAllEvents_logEventAgain_returnsLatestEvent() { - debugFirestoreLoggerImpl.uploadEvent(eventLog1) + eventLogger.uploadEvent(eventLog1) debugFirestoreLoggerImpl.clearAllEvents() debugFirestoreLoggerImpl.uploadEvent(eventLog2) val event = debugFirestoreLoggerImpl.getMostRecentEvent() @@ -80,7 +83,7 @@ class DebugFirestoreEventLoggerImplTest { @Test fun testDebugFirestoreEventLogger_logEvent_clearAllEvents_getMostRecent_returnsFailure() { - debugFirestoreLoggerImpl.uploadEvent(eventLog1) + eventLogger.uploadEvent(eventLog1) debugFirestoreLoggerImpl.clearAllEvents() val eventException = assertThrows(NoSuchElementException::class) { @@ -100,7 +103,7 @@ class DebugFirestoreEventLoggerImplTest { @Test fun testDebugFirestoreEventLogger_logEvent_clearAllEvents_returnsEmptyList() { - debugFirestoreLoggerImpl.uploadEvent(eventLog1) + eventLogger.uploadEvent(eventLog1) debugFirestoreLoggerImpl.clearAllEvents() val isListEmpty = debugFirestoreLoggerImpl.getEventList().isEmpty() @@ -109,8 +112,8 @@ class DebugFirestoreEventLoggerImplTest { @Test fun testDebugFirestoreEventLogger_logMultipleEvents_clearAllEvents_returnsEmptyList() { - debugFirestoreLoggerImpl.uploadEvent(eventLog1) - debugFirestoreLoggerImpl.uploadEvent(eventLog2) + eventLogger.uploadEvent(eventLog1) + eventLogger.uploadEvent(eventLog2) debugFirestoreLoggerImpl.clearAllEvents() val isListEmpty = debugFirestoreLoggerImpl.getEventList().isEmpty() @@ -119,7 +122,7 @@ class DebugFirestoreEventLoggerImplTest { @Test fun testDebugFirestoreEventLogger_logEvent_returnsNonEmptyList() { - debugFirestoreLoggerImpl.uploadEvent(eventLog1) + eventLogger.uploadEvent(eventLog1) val isListEmpty = debugFirestoreLoggerImpl.getEventList().isEmpty() Truth.assertThat(isListEmpty).isFalse() @@ -160,8 +163,8 @@ class DebugFirestoreEventLoggerImplTest { @Singleton @Component( modules = [ - TestModule::class, TestLogReportingModule::class, RobolectricModule::class, - TestDispatcherModule::class, FakeOppiaClockModule::class, + TestModule::class, RobolectricModule::class, DebugLogReportingModule::class, + TestDispatcherModule::class, FakeOppiaClockModule::class, LocaleTestModule::class ] ) diff --git a/utility/src/test/resources/robolectric.properties b/utility/src/test/resources/robolectric.properties index 467b28a73b9..7b9532ffcbf 100644 --- a/utility/src/test/resources/robolectric.properties +++ b/utility/src/test/resources/robolectric.properties @@ -1,3 +1,3 @@ # utility/src/test/resources/robolectric.properties -# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 31 +# TODO(#4748): Remove the need for this file after upgrading Robolectric tests to API 33 sdk=30 diff --git a/wiki/Installing-Oppia-Android.md b/wiki/Installing-Oppia-Android.md index 309a9f3b274..8e37749c5f6 100644 --- a/wiki/Installing-Oppia-Android.md +++ b/wiki/Installing-Oppia-Android.md @@ -8,13 +8,15 @@ This wiki page explains how to install Oppia Android on your local machine. If y - [Install oppia-android](#install-oppia-android) - [Run the app from Android Studio](#run-the-app-from-android-studio) - [Run the tests](#set-up-and-run-tests) + - [Step-by-Step guidance for setting up and running app modules robolectric test](#step-by-step-guidance-for-setting-up-and-running-app-modules-robolectric-test) + - [For tests that are in non-app modules, such as **domain** or **utility**:](#for-tests-that-are-in-non-app-modules-such-as-domain-or-utility) ## Prepare developer environment -1. Download/Install [Android Studio](https://developer.android.com/studio/?gclid=EAIaIQobChMI8fX3n5Lb6AIVmH8rCh24JQsxEAAYASAAEgL4L_D_BwE&gclsrc=aw.ds#downloads). +1. Download/Install [Android Studio Bumblebee | Patch 3](https://developer.android.com/studio/archive). - **Note**: We recommend installing **Android Studio Bumblebee** because newer versions of Android Studio[ do not support running tests where shared source sets are used](https://issuetracker.google.com/issues/232007221#comment18), a configuration we use at Oppia. + **Note**: We recommend installing **Android Studio Bumblebee | 2021.1.1 Patch 3** because newer versions of Android Studio[ do not support running tests where shared source sets are used](https://issuetracker.google.com/issues/232007221#comment18), a configuration we use at Oppia. **Direct download Url**: [Windows](https://redirector.gvt1.com/edgedl/android/studio/install/2021.1.1.23/android-studio-2021.1.1.23-windows.exe) | [Linux](https://redirector.gvt1.com/edgedl/android/studio/ide-zips/2021.1.1.23/android-studio-2021.1.1.23-linux.tar.gz) | [Intel Mac](https://redirector.gvt1.com/edgedl/android/studio/install/2021.1.1.23/android-studio-2021.1.1.23-mac.dmg) | [Apple Silicon Mac](https://redirector.gvt1.com/edgedl/android/studio/install/2021.1.1.23/android-studio-2021.1.1.23-mac_arm.dmg) @@ -22,10 +24,13 @@ This wiki page explains how to install Oppia Android on your local machine. If y 2. Configure your Android Studio - In Android Studio, open Tools > SDK Manager. - - In the "SDK Platforms" tab (which is the default), select `API Level 28` and also `API Level 31` (for Bazel support). + - In the "SDK Platforms" tab (which is the default), select `API Level 28` and also `API Level 30` (for Bazel support). - Also, navigate to the "SDK Tools" tab, click the "Show Package Details" checkbox at the bottom right, then click on "Android SDK Build-Tools 34-rc1" and select 29.0.2 (this is needed for Bazel support). - Then, click "Apply" to download and install these two SDKs/Tools. + + - Must have **JDK 11** selected: + - In Android Studio, open Settings > Build, Execution, Deployment > Build Tools > Gradle and edit the Gradle JDK field. ## Install oppia-android @@ -81,38 +86,65 @@ Please follow these steps to set up Oppia Android on your local machine. ## Set up and run tests Testing the app is an integral part of our development process. You will need to test all code changes to ensure that the app works correctly, therefore it is important to ensure that your test configuration works. -We run tests in either Espresso(`app` module tests) or Robolectric(non-app module tests), meaning tests will run in either the emulator or on Gradle/Bazel via the terminal. +We strongly recommend running tests on Robolectric which is faster because it does not require a physical device or emulator setup. + +### Configure Robolectric Tests + +#### Step-by-Step guidance for setting up and running app modules robolectric test: + +1. Go to **Edit Configuration** in Android Studio (Bumblebee | 2021.1.1 Patch 3) + ![](https://user-images.githubusercontent.com/9396084/79109714-83525980-7d96-11ea-99d7-f83ea81a8a50.png) + +2. Click on Add(+) -> **JUnit** + ![](https://github.com/oppia/oppia-android/assets/76530270/87caf3fc-37d9-472d-92fd-b8ec49fb6b49) + +3. Enter following information: + - a) Name of test. Example: In my case "SplashActivityTest" + - b) Make sure select "java 11" and oppia-android.app + - c) Class path of Test class. Example: In my case "org.oppia.android.app.splash. + SplashActivityTest" + - d) Press `OK` to select the test. + ![](https://github.com/oppia/oppia-android/assets/76530270/5901624a-df76-4b27-8f31-6077a68fcb89) + +4. Click on "Run" button to run robolectric test. (In my case "SplashActivityTest") + ![](https://github.com/oppia/oppia-android/assets/76530270/75a6b998-90c5-4f0a-8886-78f96970be90) + +#### For tests that are in non-app modules, such as **domain** or **utility**:: + +1. In Android Studio, open the desired test file, e.g., `AnalyticsControllerTest`. +2. In the test file, to the left of the class name, click on the orange and green arrow, and select **Run 'AnalyticsControllerTest'**. + - You will notice that the emulator is greyed out, but the run window will open to show the running tests: + ![](https://user-images.githubusercontent.com/59600948/272657015-158117e5-47d2-40fc-a38b-5dee6c347556.png) ### Configure Emulator Tests + +**Espresso is slower for running tests, so we recommend using Robolectric.** + 1. In Android Studio, open the desired test file, e.g., `HomeActivityTest`. 2. In the Android Studio toolbar, click on the `Available Devices` option. Select an emulator that has between API 28-30. **Note**: If you don't have any available devices in this list, please follow [these instructions](#run-the-app-from-android-studio) to create one. + 3. In the test file, to the left of the class name, click on the orange and green arrow, and select **Run 'HomeActivityTest'**. -![](https://user-images.githubusercontent.com/59600948/272657131-96e5354b-13a9-4709-969a-b9494a65c30f.png) + ![](https://user-images.githubusercontent.com/59600948/272657131-96e5354b-13a9-4709-969a-b9494a65c30f.png) + 4. An "**Edit Configuration**" dialog will show up, and you should add the following settings under the general tab: - For module, select **oppia-android.app** - For Test, select **Class** - For Instrumentation class, **org.oppia.android.testing.OppiaTestRunner**, will be selected by default. - For target, select the **Use the device/snapshot dropdown** option. - Verify that your setup looks like below: - -![](https://user-images.githubusercontent.com/59600948/272657260-2e654891-61be-467a-8ebd-c997aa2abda6.png) + + ![](https://user-images.githubusercontent.com/59600948/272657260-2e654891-61be-467a-8ebd-c997aa2abda6.png) - Finally, Click the "Apply" and "Okay" buttons. - You may need to repeat step (3) above to run the test with the new configuration. - Subsequent runs of any app module tests will not require editing the configuration. - This configuration will run all the tests in that class. 5. To run only a specific test in a file: - Search or scroll down to the desired test name, to the left of the test name, click on the run icon and select **Run '`test name`''**. - -### Configure Robolectric Tests -These are tests that are in non-app modules, such as **domain** or **utility**. -1. In Android Studio, open the desired test file, e.g., `AnalyticsControllerTest`. -2. In the test file, to the left of the class name, click on the orange and green arrow, and select **Run 'AnalyticsControllerTest'**. - - You will notice that the emulator is greyed out, but the run window will open to show the running tests: - - ![](https://user-images.githubusercontent.com/59600948/272657015-158117e5-47d2-40fc-a38b-5dee6c347556.png) - + ### Next Steps -- Congratulations, you are ready to work on your first issue! Take a look at our [good first issues](https://github.com/oppia/oppia-android/wiki/Oppia-Android-Testing) and leave a comment with your suggested fix. A maintainer will assign you the issue and provide any necessary guidance. +- Congratulations, you are ready to work on your first issue! Take a look at our [good first issues](https://github.com/oppia/oppia-android/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+no%3Aassignee) and leave a comment with your suggested fix. A maintainer will assign you the issue and provide any necessary guidance. - When you are ready to submit a PR, please follow [these instructions](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR) on submitting a PR. diff --git a/wiki/Oppia-Android-Testing.md b/wiki/Oppia-Android-Testing.md index de1519730a7..757d83975bf 100644 --- a/wiki/Oppia-Android-Testing.md +++ b/wiki/Oppia-Android-Testing.md @@ -112,17 +112,22 @@ public class MyActivityTest { ``` ### Running Robolectric tests -1. Go to **Edit Configuration** in Android Studio -Screenshot 2020-04-13 at 2 51 02 PM - -2. Add **Android JUnit** -Screenshot 2020-04-13 at 2 51 31 PM - -3. Enter following information - (a.) **Name** (Normally class name) (b.)**Use classpath of module** (c.) **Class** -Screenshot 2020-04-13 at 3 18 39 PM - - -4. Press `OK` to run the test cases in robolectric. +1. Go to **Edit Configuration** in Android Studio (Bumblebee | 2021.1.1 Patch 3) + ![](https://user-images.githubusercontent.com/9396084/79109714-83525980-7d96-11ea-99d7-f83ea81a8a50.png) + +2. Click on Add(+) -> **JUnit** + ![](https://github.com/oppia/oppia-android/assets/76530270/87caf3fc-37d9-472d-92fd-b8ec49fb6b49) + +3. Enter following information: + - a) Name of test. Example: In my case "SplashActivityTest" + - b) Make sure select "java 11" and oppia-android.app [**Note:** For "app module test" select `oppia-android.app` similarly for "utility tests" select `oppia-android.utility`, for "domain test" select `oppia-android.domain`, for "model test" select `oppia-android.model`, for "testing" select `oppia-android.testing`] + - c) Class path of Test class. Example: In my case "org.oppia.android.app.splash. + SplashActivityTest" + - d) Press `OK` to select the test. + ![](https://github.com/oppia/oppia-android/assets/76530270/5901624a-df76-4b27-8f31-6077a68fcb89) + +4. Click on "Run" button to run robolectric test. (In my case "SplashActivityTest") + ![](https://github.com/oppia/oppia-android/assets/76530270/75a6b998-90c5-4f0a-8886-78f96970be90) ## Espresso