diff --git a/app/src/main/java/org/oppia/android/app/drawer/ExitProfileDialogFragment.kt b/app/src/main/java/org/oppia/android/app/drawer/ExitProfileDialogFragment.kt index ed53fac27d8..a1e67ba9da6 100644 --- a/app/src/main/java/org/oppia/android/app/drawer/ExitProfileDialogFragment.kt +++ b/app/src/main/java/org/oppia/android/app/drawer/ExitProfileDialogFragment.kt @@ -74,7 +74,7 @@ class ExitProfileDialogFragment : InjectableDialogFragment() { } .setPositiveButton(R.string.home_activity_back_dialog_exit) { _, _ -> if (soleLearnerProfile) { - requireActivity().finishAffinity() + requireActivity().finish() } else { // TODO(#3641): Investigate on using finish instead of intent. val intent = ProfileChooserActivity.createProfileChooserActivity(requireActivity()) @@ -82,6 +82,7 @@ class ExitProfileDialogFragment : InjectableDialogFragment() { intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } requireActivity().startActivity(intent) + requireActivity().finish() } } .create() diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt index c20f40f696a..3b3a027d663 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt @@ -21,6 +21,7 @@ import org.oppia.android.app.model.IntroActivityParams import org.oppia.android.app.model.Profile import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.ProfileOnboardingMode +import org.oppia.android.app.model.ProfileType import org.oppia.android.app.notice.AutomaticAppDeprecationNoticeDialogFragment import org.oppia.android.app.notice.BetaNoticeDialogFragment import org.oppia.android.app.notice.DeprecationNoticeActionResponse @@ -278,7 +279,7 @@ class SplashActivityPresenter @Inject constructor( OsDeprecationNoticeDialogFragment::newInstance ) } - StartupMode.USER_NOT_YET_ONBOARDED -> fetchProfiles() + StartupMode.USER_NOT_YET_ONBOARDED -> fetchProfile() else -> { // In all other cases (including errors when the startup state fails to load or is // defaulted), assume the user needs to be onboarded. @@ -297,7 +298,7 @@ class SplashActivityPresenter @Inject constructor( AutomaticAppDeprecationNoticeDialogFragment::newInstance ) } - StartupMode.USER_NOT_YET_ONBOARDED -> fetchProfiles() + StartupMode.USER_NOT_YET_ONBOARDED -> fetchProfile() else -> { // In all other cases (including errors when the startup state fails to load or is // defaulted), assume the user needs to be onboarded. @@ -316,7 +317,7 @@ class SplashActivityPresenter @Inject constructor( } private fun getProfileOnboardingState() { - profileManagementController.getProfileOnboardingState().toLiveData().observe( + profileManagementController.getProfileOnboardingMode().toLiveData().observe( activity, { result -> when (result) { @@ -337,7 +338,7 @@ class SplashActivityPresenter @Inject constructor( ProfileOnboardingMode.NEW_INSTALL -> { launchOnboardingActivity() } - ProfileOnboardingMode.SOLE_LEARNER_PROFILE -> fetchProfiles() + ProfileOnboardingMode.SOLE_LEARNER_PROFILE_ONLY -> fetchProfile() else -> { activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) activity.finish() @@ -345,7 +346,7 @@ class SplashActivityPresenter @Inject constructor( } } - private fun fetchProfiles() { + private fun fetchProfile() { profileManagementController.getProfiles().toLiveData().observe(activity) { result -> when (result) { is AsyncResult.Success -> handleProfiles(result.value) @@ -358,7 +359,7 @@ class SplashActivityPresenter @Inject constructor( } private fun handleProfiles(profiles: List) { - val soleLearnerProfile = profiles.find { it.isAdmin && it.pin.isNullOrBlank() } + val soleLearnerProfile = profiles.find { it.profileType == ProfileType.SOLE_LEARNER } if (soleLearnerProfile != null) { proceedBasedOnProfileState(soleLearnerProfile) } else { diff --git a/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt index cf924af23ef..1d466db6d7b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt @@ -54,6 +54,7 @@ import org.oppia.android.app.model.OppiaLanguage.NIGERIAN_PIDGIN import org.oppia.android.app.model.OppiaLocaleContext import org.oppia.android.app.model.OppiaRegion import org.oppia.android.app.model.ProfileId +import org.oppia.android.app.model.ProfileType import org.oppia.android.app.model.ScreenName import org.oppia.android.app.onboarding.IntroActivity import org.oppia.android.app.onboarding.OnboardingActivity @@ -1072,6 +1073,7 @@ class SplashActivityTest { initializeTestApplication(onboardingV2Enabled = true) profileTestHelper.addOnlyAdminProfileWithoutPin() val profileId = ProfileId.newBuilder().setInternalId(0).build() + profileTestHelper.updateProfileType(profileId, ProfileType.SOLE_LEARNER) profileTestHelper.markProfileOnboardingStarted(profileId) val params = IntroActivityParams.newBuilder() .setProfileNickname("Admin") diff --git a/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt b/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt index d66d4affb50..cd458f3ef50 100644 --- a/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt +++ b/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt @@ -405,17 +405,22 @@ class ProfileManagementController @Inject constructor( } /** Returns the state of the app based on the number and type of existing profiles. */ - fun getProfileOnboardingState(): DataProvider { + fun getProfileOnboardingMode(): DataProvider { return getProfiles().transform(PROFILE_ONBOARDING_MODE_PROVIDER_ID) { profileList -> val profileCount = profileList.size when { profileCount > 1 -> ProfileOnboardingMode.MULTIPLE_PROFILES profileCount == 1 -> { - val profileType = profileList.first().profileType - if (profileType == ProfileType.SUPERVISOR) { - ProfileOnboardingMode.ADMIN_PROFILE_ONLY - } else { - ProfileOnboardingMode.SOLE_LEARNER_PROFILE + when (profileList.first().profileType) { + ProfileType.SUPERVISOR -> { + ProfileOnboardingMode.SUPERVISOR_PROFILE_ONLY + } + ProfileType.SOLE_LEARNER -> { + ProfileOnboardingMode.SOLE_LEARNER_PROFILE_ONLY + } + else -> { + ProfileOnboardingMode.UNKNOWN_PROFILE_TYPE + } } } else -> ProfileOnboardingMode.NEW_INSTALL 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 e8845d74b20..c6f80c137c3 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 @@ -85,17 +85,28 @@ import javax.inject.Singleton @LooperMode(LooperMode.Mode.PAUSED) @Config(application = ProfileManagementControllerTest.TestApplication::class) class ProfileManagementControllerTest { - @get:Rule val oppiaTestRule = OppiaTestRule() - @Inject lateinit var context: Context - @Inject lateinit var profileTestHelper: ProfileTestHelper - @Inject lateinit var profileManagementController: ProfileManagementController - @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory - @Inject lateinit var machineLocale: OppiaLocale.MachineLocale - @field:[BackgroundDispatcher Inject] lateinit var backgroundDispatcher: CoroutineDispatcher - @Inject lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger - @Inject lateinit var loggingIdentifierController: LoggingIdentifierController - @Inject lateinit var oppiaClock: FakeOppiaClock + @get:Rule + val oppiaTestRule = OppiaTestRule() + @Inject + lateinit var context: Context + @Inject + lateinit var profileTestHelper: ProfileTestHelper + @Inject + lateinit var profileManagementController: ProfileManagementController + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject + lateinit var monitorFactory: DataProviderTestMonitor.Factory + @Inject + lateinit var machineLocale: OppiaLocale.MachineLocale + @field:[BackgroundDispatcher Inject] + lateinit var backgroundDispatcher: CoroutineDispatcher + @Inject + lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger + @Inject + lateinit var loggingIdentifierController: LoggingIdentifierController + @Inject + lateinit var oppiaClock: FakeOppiaClock private companion object { private val PROFILES_LIST = listOf( @@ -1726,16 +1737,21 @@ class ProfileManagementControllerTest { } @Test - fun testProfileOnboardingState_oneAdminProfileWithoutPassword_returnsSoleLeanerState() { + fun testProfileOnboardingState_oneAdminProfileWithoutPassword_returnsSoleLeanerTypeMode() { setUpTestWithOnboardingV2Enabled(true) addAdminProfileAndWait(name = "James", pin = "") - val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingState() + val updateProfileProvider = + profileManagementController.updateProfileType(ADMIN_PROFILE_ID_0, ProfileType.SOLE_LEARNER) + monitorFactory.ensureDataProviderExecutes(updateProfileProvider) + val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingMode() val profileOnboardingModeResult = monitorFactory.waitForNextSuccessfulResult(profileOnboardingModeProvider) - assertThat(profileOnboardingModeResult).isEqualTo(ProfileOnboardingMode.SOLE_LEARNER_PROFILE) + assertThat(profileOnboardingModeResult).isEqualTo( + ProfileOnboardingMode.SOLE_LEARNER_PROFILE_ONLY + ) } @Test @@ -1745,26 +1761,23 @@ class ProfileManagementControllerTest { val updateProfileProvider = profileManagementController.updateProfileType(ADMIN_PROFILE_ID_0, ProfileType.SUPERVISOR) - monitorFactory.ensureDataProviderExecutes(updateProfileProvider) - val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingState() - + val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingMode() val profileOnboardingModeResult = monitorFactory.waitForNextSuccessfulResult(profileOnboardingModeProvider) - assertThat(profileOnboardingModeResult).isEqualTo(ProfileOnboardingMode.ADMIN_PROFILE_ONLY) + assertThat(profileOnboardingModeResult).isEqualTo(ProfileOnboardingMode.SUPERVISOR_PROFILE_ONLY) } @Test - fun testProfileOnboardingState_multipleProfiles_returnsMultipleProfilesState() { + fun testProfileOnboardingState_multipleProfiles_returnsMultipleProfilesTypeMode() { setUpTestWithOnboardingV2Enabled(true) addAdminProfileAndWait(name = "James") addNonAdminProfileAndWait(name = "Rajat", pin = "01234") addNonAdminProfileAndWait(name = "Rohit", pin = "") - val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingState() - + val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingMode() val profileOnboardingModeResult = monitorFactory.waitForNextSuccessfulResult(profileOnboardingModeProvider) @@ -1772,17 +1785,28 @@ class ProfileManagementControllerTest { } @Test - fun testProfileOnboardingState_noProfilesFound_returnsNewInstallState() { + fun testProfileOnboardingState_noProfilesFound_returnsNewInstallTypeMode() { setUpTestWithOnboardingV2Enabled(true) - val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingState() - + val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingMode() val profileOnboardingModeResult = monitorFactory.waitForNextSuccessfulResult(profileOnboardingModeProvider) assertThat(profileOnboardingModeResult).isEqualTo(ProfileOnboardingMode.NEW_INSTALL) } + @Test + fun testProfileOnboardingState_existingProfilesV1_returnsUnknownProfileTypeMode() { + setUpTestWithOnboardingV2Enabled(true) + addAdminProfileAndWait(name = "James") + + val profileOnboardingModeProvider = profileManagementController.getProfileOnboardingMode() + val profileOnboardingModeResult = + monitorFactory.waitForNextSuccessfulResult(profileOnboardingModeProvider) + + assertThat(profileOnboardingModeResult).isEqualTo(ProfileOnboardingMode.UNKNOWN_PROFILE_TYPE) + } + @Test fun testGetProfile_createAdmin_returnsSupervisorType() { setUpTestWithOnboardingV2Enabled(true) diff --git a/model/src/main/proto/profile.proto b/model/src/main/proto/profile.proto index a6fab1360ad..e58fd585e0b 100644 --- a/model/src/main/proto/profile.proto +++ b/model/src/main/proto/profile.proto @@ -179,11 +179,15 @@ enum ProfileOnboardingMode { NEW_INSTALL = 1; // Indicates that there is only one profile and it is a sole learner profile. - SOLE_LEARNER_PROFILE = 2; + SOLE_LEARNER_PROFILE_ONLY = 2; // Indicates that there is only one profile and it is an admin profile. - ADMIN_PROFILE_ONLY = 3; + SUPERVISOR_PROFILE_ONLY = 3; // Indicates that there are multiple profiles on the device. MULTIPLE_PROFILES = 4; + + // Indicates that there is only one profile and the profile type is unknown, indicating that + // migration is required. + UNKNOWN_PROFILE_TYPE = 5; } diff --git a/testing/src/main/java/org/oppia/android/testing/profile/ProfileTestHelper.kt b/testing/src/main/java/org/oppia/android/testing/profile/ProfileTestHelper.kt index 35e1b0632c4..74076d12168 100644 --- a/testing/src/main/java/org/oppia/android/testing/profile/ProfileTestHelper.kt +++ b/testing/src/main/java/org/oppia/android/testing/profile/ProfileTestHelper.kt @@ -1,6 +1,7 @@ package org.oppia.android.testing.profile import org.oppia.android.app.model.ProfileId +import org.oppia.android.app.model.ProfileType import org.oppia.android.domain.profile.ProfileManagementController import org.oppia.android.testing.data.DataProviderTestMonitor import org.oppia.android.util.data.AsyncResult @@ -129,6 +130,11 @@ class ProfileTestHelper @Inject constructor( return profileManagementController.markProfileOnboardingStarted(profileId) } + /** Updates the [ProfileType] of an existing profile. */ + fun updateProfileType(profileId: ProfileId, profileType: ProfileType): DataProvider { + return profileManagementController.updateProfileType(profileId, profileType) + } + /** Returns the continue button animation seen for profile. */ fun getContinueButtonAnimationSeenStatus(profileId: ProfileId): Boolean { return monitorFactory.waitForNextSuccessfulResult( diff --git a/testing/src/test/java/org/oppia/android/testing/profile/ProfileTestHelperTest.kt b/testing/src/test/java/org/oppia/android/testing/profile/ProfileTestHelperTest.kt index 9a63b3682de..3683a0016af 100644 --- a/testing/src/test/java/org/oppia/android/testing/profile/ProfileTestHelperTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/profile/ProfileTestHelperTest.kt @@ -12,6 +12,7 @@ import dagger.Provides import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.oppia.android.app.model.ProfileType import org.oppia.android.domain.oppialogger.LogStorageModule import org.oppia.android.domain.oppialogger.LoggingIdentifierModule import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule @@ -147,13 +148,38 @@ class ProfileTestHelperTest { } @Test - fun testProfileOnboarding_markOnboardingCompleted_chekIsSuccessful() { + fun testProfileOnboarding_markOnboardingStarted_checkIsSuccessful() { + profileTestHelper.addOnlyAdminProfile() + val profileId = profileManagementController.getCurrentProfileId() + val onboardingProvider = profileTestHelper.markProfileOnboardingStarted(profileId!!) + monitorFactory.waitForNextSuccessfulResult(onboardingProvider) + } + + @Test + fun testProfileOnboarding_markOnboardingCompleted_checkIsSuccessful() { profileTestHelper.addOnlyAdminProfile() val profileId = profileManagementController.getCurrentProfileId() val onboardingProvider = profileTestHelper.markProfileOnboardingEnded(profileId!!) monitorFactory.waitForNextSuccessfulResult(onboardingProvider) } + @Test + fun testUpdateProfile_updateProfileType_checkIsSuccessful() { + profileTestHelper.addOnlyAdminProfile() + val profileId = profileManagementController.getCurrentProfileId() + val updateProvider = profileTestHelper.updateProfileType(profileId!!, ProfileType.SUPERVISOR) + monitorFactory.ensureDataProviderExecutes(updateProvider) + + val profilesProvider = profileManagementController.getProfiles() + testCoroutineDispatchers.runCurrent() + + val profiles = monitorFactory.waitForNextSuccessfulResult(profilesProvider) + assertThat(profiles.size).isEqualTo(1) + assertThat(profiles[0].name).isEqualTo("Admin") + assertThat(profiles[0].isAdmin).isTrue() + assertThat(profiles[0].profileType).isEqualTo(ProfileType.SUPERVISOR) + } + // TODO(#89): Move this to a common test application component. @Module class TestModule {