From 25e9ea457341b0943368fde7e6980bae88002947 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Tue, 24 Oct 2023 17:53:27 -0400 Subject: [PATCH 01/16] [WIP Hacks] Use local emulator --- ground/src/main/AndroidManifest.xml | 3 ++- .../remote/RemotePersistenceModule.kt | 2 ++ .../ground/repository/UserRepository.kt | 2 +- .../auth/GoogleAuthenticationManager.kt | 20 +++++++++++++++---- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ground/src/main/AndroidManifest.xml b/ground/src/main/AndroidManifest.xml index 46e32caae6..b7edeed78e 100644 --- a/ground/src/main/AndroidManifest.xml +++ b/ground/src/main/AndroidManifest.xml @@ -35,7 +35,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/AppTheme.Launcher"> + android:theme="@style/AppTheme.Launcher" + android:usesCleartextTraffic="true"> Date: Wed, 25 Oct 2023 11:20:04 -0400 Subject: [PATCH 02/16] Fix reauth --- .../ground/system/auth/GoogleAuthenticationManager.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt index 40abd652ee..7884ad735a 100644 --- a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt @@ -43,6 +43,8 @@ import timber.log.Timber private val SIGN_IN_REQUEST_CODE = AuthenticationManager::class.java.hashCode() and 0xffff +val NOBODY = User("nobody", "nobody", "Test User") + class GoogleAuthenticationManager @Inject constructor( @@ -95,7 +97,7 @@ constructor( externalScope.launch { getFirebaseAuth().signInAnonymously().await() signInState.onNext( - SignInState.signedIn(User("nobody", "nobody", "Test User")) + SignInState.signedIn(NOBODY) ) } // activityStreams.withActivity { @@ -147,7 +149,7 @@ constructor( private fun getFirebaseAuthCredential(googleAccount: GoogleSignInAccount): AuthCredential = GoogleAuthProvider.getCredential(googleAccount.idToken, null) - private suspend fun getFirebaseUser(): User? = getFirebaseAuth().currentUser?.toUser() + private suspend fun getFirebaseUser(): User? = NOBODY //getFirebaseAuth().currentUser?.toUser() private fun FirebaseUser.toUser(): User = User(uid, email.orEmpty(), displayName.orEmpty(), photoUrl.toString()) From 29b129528dc28d3c63ae02316b13bd4af72b7a0c Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Wed, 25 Oct 2023 22:27:03 -0400 Subject: [PATCH 03/16] Make emu configurable --- ground/build.gradle | 26 ++++++++++++++ .../java/com/google/android/ground/Config.kt | 1 - .../remote/RemotePersistenceModule.kt | 20 ++++++----- .../auth/GoogleAuthenticationManager.kt | 34 +++++++++++++------ sharedTest/build.gradle | 9 +++++ 5 files changed, 71 insertions(+), 19 deletions(-) diff --git a/ground/build.gradle b/ground/build.gradle index e76637e519..34a4772d6a 100644 --- a/ground/build.gradle +++ b/ground/build.gradle @@ -71,6 +71,9 @@ android { multiDexEnabled true // For rendering vector map markers. vectorDrawables.useSupportLibrary = true + buildConfigField("String", "EMULATOR_HOST", "\"10.0.2.2\"") + buildConfigField("int", "FIRESTORE_EMULATOR_PORT", "8080") + buildConfigField("int", "AUTH_EMULATOR_PORT", "9099") } // Use flag -PtestBuildType with desired variant to change default behavior. @@ -101,6 +104,15 @@ android { instrumentationEnabled false } } +// local { +// ext.enableCrashlytics = false +// ext.alwaysUpdateBuildId = false +// resValue("string", "GOOGLE_MAPS_API_KEY", "") +// buildConfigField("String", "emulatorHost", "\"10.0.2.2\"") +// FirebasePerformance { +// instrumentationEnabled false +// } +// } staging { ext.enableCrashlytics = false ext.alwaysUpdateBuildId = false @@ -112,6 +124,20 @@ android { } } + flavorDimensions "backend" + productFlavors { + local { + dimension "backend" + versionNameSuffix "-local" + buildConfigField("boolean", "USE_EMULATORS", "true") + } + dev { + dimension "backend" + versionNameSuffix "-prod" + buildConfigField("boolean", "USE_EMULATORS", "false") + } + } + buildFeatures { dataBinding true viewBinding true diff --git a/ground/src/main/java/com/google/android/ground/Config.kt b/ground/src/main/java/com/google/android/ground/Config.kt index bc36fa6f01..6af2383c43 100644 --- a/ground/src/main/java/com/google/android/ground/Config.kt +++ b/ground/src/main/java/com/google/android/ground/Config.kt @@ -30,7 +30,6 @@ object Config { const val DB_NAME = "ground.db" // Firebase Cloud Firestore settings. - const val FIRESTORE_PERSISTENCE_ENABLED = false const val FIRESTORE_LOGGING_ENABLED = true // Photos diff --git a/ground/src/main/java/com/google/android/ground/persistence/remote/RemotePersistenceModule.kt b/ground/src/main/java/com/google/android/ground/persistence/remote/RemotePersistenceModule.kt index ae31633311..94aa3c84db 100644 --- a/ground/src/main/java/com/google/android/ground/persistence/remote/RemotePersistenceModule.kt +++ b/ground/src/main/java/com/google/android/ground/persistence/remote/RemotePersistenceModule.kt @@ -15,7 +15,9 @@ */ package com.google.android.ground.persistence.remote -import com.google.android.ground.Config +import com.google.android.ground.BuildConfig.EMULATOR_HOST +import com.google.android.ground.BuildConfig.FIRESTORE_EMULATOR_PORT +import com.google.android.ground.BuildConfig.USE_EMULATORS import com.google.android.ground.persistence.remote.firebase.FirebaseStorageManager import com.google.android.ground.persistence.remote.firebase.FirestoreDataStore import com.google.android.ground.persistence.remote.firebase.FirestoreUuidGenerator @@ -51,13 +53,15 @@ abstract class RemotePersistenceModule { companion object { @Provides - fun firebaseFirestoreSettings(): FirebaseFirestoreSettings { - return FirebaseFirestoreSettings.Builder() - .setHost("10.0.2.2:8080") - .setSslEnabled(false) - .setPersistenceEnabled(Config.FIRESTORE_PERSISTENCE_ENABLED) - .build() - } + fun firebaseFirestoreSettings(): FirebaseFirestoreSettings = + with(FirebaseFirestoreSettings.Builder()) { + if (USE_EMULATORS) { + host = "$EMULATOR_HOST:$FIRESTORE_EMULATOR_PORT" + isSslEnabled = false + } + isPersistenceEnabled = false + build() + } /** Returns a reference to the default Storage bucket. */ @Provides diff --git a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt index 7884ad735a..aff74aba6c 100644 --- a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt @@ -22,6 +22,9 @@ import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInClient import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.gms.common.api.ApiException +import com.google.android.ground.BuildConfig.AUTH_EMULATOR_PORT +import com.google.android.ground.BuildConfig.EMULATOR_HOST +import com.google.android.ground.BuildConfig.USE_EMULATORS import com.google.android.ground.R import com.google.android.ground.coroutines.ApplicationScope import com.google.android.ground.coroutines.IoDispatcher @@ -94,17 +97,24 @@ constructor( override fun signIn() { signInState.onNext(SignInState.signingIn()) + if (USE_EMULATORS) { + signInAnonymously() + } else { + showSignInDialog() + } + } + + private fun signInAnonymously() = externalScope.launch { getFirebaseAuth().signInAnonymously().await() - signInState.onNext( - SignInState.signedIn(NOBODY) - ) + signInState.onNext(SignInState.signedIn(NOBODY)) + } + + private fun showSignInDialog() = + activityStreams.withActivity { + val signInIntent = getGoogleSignInClient(it).signInIntent + it.startActivityForResult(signInIntent, SIGN_IN_REQUEST_CODE) } - // activityStreams.withActivity { - // val signInIntent = getGoogleSignInClient(it).signInIntent - // it.startActivityForResult(signInIntent, SIGN_IN_REQUEST_CODE) - // } - } override fun signOut() { externalScope.launch { @@ -117,7 +127,10 @@ constructor( private suspend fun getFirebaseAuth() = withContext(ioDispatcher) { val auth = FirebaseAuth.getInstance() - auth.useEmulator("10.0.2.2", 9099) + if (USE_EMULATORS) { + // Use the auth emulator so we can sign-in anonymously during dev. + auth.useEmulator(EMULATOR_HOST, AUTH_EMULATOR_PORT) + } auth } @@ -149,7 +162,8 @@ constructor( private fun getFirebaseAuthCredential(googleAccount: GoogleSignInAccount): AuthCredential = GoogleAuthProvider.getCredential(googleAccount.idToken, null) - private suspend fun getFirebaseUser(): User? = NOBODY //getFirebaseAuth().currentUser?.toUser() + private suspend fun getFirebaseUser(): User? = + if (USE_EMULATORS) NOBODY else getFirebaseAuth().currentUser?.toUser() private fun FirebaseUser.toUser(): User = User(uid, email.orEmpty(), displayName.orEmpty(), photoUrl.toString()) diff --git a/sharedTest/build.gradle b/sharedTest/build.gradle index 7c4f55117f..1453241e2a 100644 --- a/sharedTest/build.gradle +++ b/sharedTest/build.gradle @@ -34,6 +34,15 @@ android { staging { } } + flavorDimensions "backend" + productFlavors { + local { + dimension "backend" + } + dev { + dimension "backend" + } + } } dependencies { From c9b85ee146318275575bd3895107a3bce0e10eb4 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Wed, 25 Oct 2023 22:31:09 -0400 Subject: [PATCH 04/16] Additional emu config --- ground/build.gradle | 23 +++++++------------ ground/src/main/AndroidManifest.xml | 2 +- .../ground/repository/UserRepository.kt | 2 +- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/ground/build.gradle b/ground/build.gradle index 34a4772d6a..bfaa36a097 100644 --- a/ground/build.gradle +++ b/ground/build.gradle @@ -71,9 +71,9 @@ android { multiDexEnabled true // For rendering vector map markers. vectorDrawables.useSupportLibrary = true - buildConfigField("String", "EMULATOR_HOST", "\"10.0.2.2\"") - buildConfigField("int", "FIRESTORE_EMULATOR_PORT", "8080") - buildConfigField("int", "AUTH_EMULATOR_PORT", "9099") + buildConfigField "String", "EMULATOR_HOST", "\"10.0.2.2\"" + buildConfigField "int", "FIRESTORE_EMULATOR_PORT", "8080" + buildConfigField "int", "AUTH_EMULATOR_PORT", "9099" } // Use flag -PtestBuildType with desired variant to change default behavior. @@ -104,15 +104,6 @@ android { instrumentationEnabled false } } -// local { -// ext.enableCrashlytics = false -// ext.alwaysUpdateBuildId = false -// resValue("string", "GOOGLE_MAPS_API_KEY", "") -// buildConfigField("String", "emulatorHost", "\"10.0.2.2\"") -// FirebasePerformance { -// instrumentationEnabled false -// } -// } staging { ext.enableCrashlytics = false ext.alwaysUpdateBuildId = false @@ -129,12 +120,14 @@ android { local { dimension "backend" versionNameSuffix "-local" - buildConfigField("boolean", "USE_EMULATORS", "true") + buildConfigField "boolean", "USE_EMULATORS", "true" + buildConfigField "boolean", "ENABLE_CLEARTXT_TRAFFIC", "true" } dev { dimension "backend" - versionNameSuffix "-prod" - buildConfigField("boolean", "USE_EMULATORS", "false") + versionNameSuffix "-dev" + buildConfigField "boolean", "USE_EMULATORS", "false" + buildConfigField "boolean", "ENABLE_CLEARTXT_TRAFFIC", "false" } } diff --git a/ground/src/main/AndroidManifest.xml b/ground/src/main/AndroidManifest.xml index b7edeed78e..f5605f59f1 100644 --- a/ground/src/main/AndroidManifest.xml +++ b/ground/src/main/AndroidManifest.xml @@ -36,7 +36,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme.Launcher" - android:usesCleartextTraffic="true"> + android:usesCleartextTraffic="${ENABLE_CLEARTXT_TRAFFIC}"> Date: Wed, 25 Oct 2023 22:33:25 -0400 Subject: [PATCH 05/16] Tweak config --- .../system/auth/GoogleAuthenticationManager.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt index aff74aba6c..6e09f649b3 100644 --- a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt @@ -44,9 +44,10 @@ import kotlinx.coroutines.tasks.await import kotlinx.coroutines.withContext import timber.log.Timber -private val SIGN_IN_REQUEST_CODE = AuthenticationManager::class.java.hashCode() and 0xffff +private val signInRequestCode = AuthenticationManager::class.java.hashCode() and 0xffff -val NOBODY = User("nobody", "nobody", "Test User") +/** Used when running against local Firebase Emulator Suite. */ +private val anonymousUser = User("nobody", "nobody", "Test User") class GoogleAuthenticationManager @Inject @@ -68,7 +69,7 @@ constructor( .build() externalScope.launch { - activityStreams.getActivityResults(SIGN_IN_REQUEST_CODE).asFlow().collect { + activityStreams.getActivityResults(signInRequestCode).asFlow().collect { onActivityResult(it) } } @@ -107,13 +108,13 @@ constructor( private fun signInAnonymously() = externalScope.launch { getFirebaseAuth().signInAnonymously().await() - signInState.onNext(SignInState.signedIn(NOBODY)) + signInState.onNext(SignInState.signedIn(anonymousUser)) } private fun showSignInDialog() = activityStreams.withActivity { val signInIntent = getGoogleSignInClient(it).signInIntent - it.startActivityForResult(signInIntent, SIGN_IN_REQUEST_CODE) + it.startActivityForResult(signInIntent, signInRequestCode) } override fun signOut() { @@ -163,7 +164,7 @@ constructor( GoogleAuthProvider.getCredential(googleAccount.idToken, null) private suspend fun getFirebaseUser(): User? = - if (USE_EMULATORS) NOBODY else getFirebaseAuth().currentUser?.toUser() + if (USE_EMULATORS) anonymousUser else getFirebaseAuth().currentUser?.toUser() private fun FirebaseUser.toUser(): User = User(uid, email.orEmpty(), displayName.orEmpty(), photoUrl.toString()) From 881484f69f2fb2da3ad2362b8c45de8c8d6ad098 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Wed, 25 Oct 2023 22:34:20 -0400 Subject: [PATCH 06/16] More emu logic --- .../com/google/android/ground/repository/UserRepository.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ground/src/main/java/com/google/android/ground/repository/UserRepository.kt b/ground/src/main/java/com/google/android/ground/repository/UserRepository.kt index 51de00af2b..707439374b 100644 --- a/ground/src/main/java/com/google/android/ground/repository/UserRepository.kt +++ b/ground/src/main/java/com/google/android/ground/repository/UserRepository.kt @@ -15,6 +15,7 @@ */ package com.google.android.ground.repository +import com.google.android.ground.BuildConfig.USE_EMULATORS import com.google.android.ground.model.Role import com.google.android.ground.model.User import com.google.android.ground.persistence.local.LocalValueStore @@ -54,7 +55,9 @@ constructor( Timber.d("Skipped refreshing user profile as device is offline.") return } - // remoteDataStore.refreshUserProfile() + if (!USE_EMULATORS) { + remoteDataStore.refreshUserProfile() + } } suspend fun getUser(userId: String): User = localUserStore.getUser(userId) From 4821677ede0086f745e8dc9e5f3654e9c90fa1f3 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Wed, 25 Oct 2023 22:50:24 -0400 Subject: [PATCH 07/16] Correctly configure cleartext traffic placeholder --- build.gradle | 2 +- config/lint/lint.gradle | 2 +- config/lint/lint.xml | 3 +++ ground/build.gradle | 4 ++-- ground/src/main/AndroidManifest.xml | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index cb09d9cd22..8c551b1692 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ detekt { } task checkCode(type: GradleBuild) { - tasks = ['checkstyle', 'lintDebug', 'ktfmtCheck', 'detekt'] + tasks = ['checkstyle', 'lintLocalDebug', 'ktfmtCheck', 'detekt'] } task clean(type: Delete) { diff --git a/config/lint/lint.gradle b/config/lint/lint.gradle index 1d6b9cc913..7440eca88e 100644 --- a/config/lint/lint.gradle +++ b/config/lint/lint.gradle @@ -20,7 +20,7 @@ * Runs automatically with every build on Google Cloud Build. * * To run manually: - * $ ./gradlew lintDebug + * $ ./gradlew lintLocalDebug * * Reports (both html and xml formats) are stored under: ground/build/reports/lint/ */ diff --git a/config/lint/lint.xml b/config/lint/lint.xml index ec40bce184..508fabb0b0 100644 --- a/config/lint/lint.xml +++ b/config/lint/lint.xml @@ -60,4 +60,7 @@ + + + diff --git a/ground/build.gradle b/ground/build.gradle index bfaa36a097..33a49a0f8e 100644 --- a/ground/build.gradle +++ b/ground/build.gradle @@ -121,13 +121,13 @@ android { dimension "backend" versionNameSuffix "-local" buildConfigField "boolean", "USE_EMULATORS", "true" - buildConfigField "boolean", "ENABLE_CLEARTXT_TRAFFIC", "true" + manifestPlaceholders.usesCleartextTraffic = true } dev { dimension "backend" versionNameSuffix "-dev" buildConfigField "boolean", "USE_EMULATORS", "false" - buildConfigField "boolean", "ENABLE_CLEARTXT_TRAFFIC", "false" + manifestPlaceholders.usesCleartextTraffic = false } } diff --git a/ground/src/main/AndroidManifest.xml b/ground/src/main/AndroidManifest.xml index f5605f59f1..6e7a617094 100644 --- a/ground/src/main/AndroidManifest.xml +++ b/ground/src/main/AndroidManifest.xml @@ -36,7 +36,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme.Launcher" - android:usesCleartextTraffic="${ENABLE_CLEARTXT_TRAFFIC}"> + android:usesCleartextTraffic="${usesCleartextTraffic}"> Date: Wed, 25 Oct 2023 23:03:47 -0400 Subject: [PATCH 08/16] Update build targets --- cloudbuild.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index def2fe3f0f..09847b027e 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -85,11 +85,11 @@ steps: args: - '-c' - | - ./gradlew -PdisablePreDex assembleStaging assembleStagingUnitTest -PtestBuildType=staging + ./gradlew -PdisablePreDex assembleDevStaging assembleDevStagingUnitTest -PtestBuildType=devStaging # TODO(#1547): Re-enable once instrumentation tests are fixed. # if [[ "${_PUSH_TO_MASTER}" ]]; then - # ./gradlew -PdisablePreDex assembleStagingAndroidTest -PtestBuildType=staging + # ./gradlew -PdisablePreDex assembleDevStagingAndroidTest -PtestBuildType=devStaging # fi # Run code quality checks @@ -114,11 +114,11 @@ steps: args: - '-c' - | - ./gradlew -PdisablePreDex testStagingUnitTest 2> unit-test-logs.txt || echo "fail" > build-status.txt + ./gradlew -PdisablePreDex testDevStagingUnitTest 2> unit-test-logs.txt || echo "fail" > build-status.txt cat unit-test-logs.txt # TODO: Add a check for _PUSH_TO_MASTER - ./gradlew jacocoTestStagingUnitTestReport + ./gradlew jacocoTestDevStagingUnitTestReport - name: 'gcr.io/$PROJECT_ID/android:34' id: &authenticate_gcloud 'Authorize gcloud' From 7727e60819ad5de07a4a25d4ba2c75af49ae5e06 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Wed, 25 Oct 2023 23:13:51 -0400 Subject: [PATCH 09/16] Fix Cloud Build --- cloudbuild.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 09847b027e..855e706b61 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -85,11 +85,11 @@ steps: args: - '-c' - | - ./gradlew -PdisablePreDex assembleDevStaging assembleDevStagingUnitTest -PtestBuildType=devStaging + ./gradlew -PdisablePreDex assembleDevStaging assembleDevStagingUnitTest -PtestBuildType=staging # TODO(#1547): Re-enable once instrumentation tests are fixed. # if [[ "${_PUSH_TO_MASTER}" ]]; then - # ./gradlew -PdisablePreDex assembleDevStagingAndroidTest -PtestBuildType=devStaging + # ./gradlew -PdisablePreDex assembleDevStagingAndroidTest -PtestBuildType=staging # fi # Run code quality checks From b0bed37097c395cfe1b935807bf43a82bc461a88 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Thu, 26 Oct 2023 12:17:14 -0400 Subject: [PATCH 10/16] Abstract handling of anonymous user --- ground/src/main/java/com/google/android/ground/model/User.kt | 1 + .../com/google/android/ground/repository/UserRepository.kt | 3 +-- .../android/ground/system/auth/GoogleAuthenticationManager.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/model/User.kt b/ground/src/main/java/com/google/android/ground/model/User.kt index dfb8f186d7..fab5dc5a04 100644 --- a/ground/src/main/java/com/google/android/ground/model/User.kt +++ b/ground/src/main/java/com/google/android/ground/model/User.kt @@ -23,4 +23,5 @@ constructor( val email: String, val displayName: String, val photoUrl: String? = null, + val isAnonymous: Boolean = false ) diff --git a/ground/src/main/java/com/google/android/ground/repository/UserRepository.kt b/ground/src/main/java/com/google/android/ground/repository/UserRepository.kt index 707439374b..9942b7fdd9 100644 --- a/ground/src/main/java/com/google/android/ground/repository/UserRepository.kt +++ b/ground/src/main/java/com/google/android/ground/repository/UserRepository.kt @@ -15,7 +15,6 @@ */ package com.google.android.ground.repository -import com.google.android.ground.BuildConfig.USE_EMULATORS import com.google.android.ground.model.Role import com.google.android.ground.model.User import com.google.android.ground.persistence.local.LocalValueStore @@ -55,7 +54,7 @@ constructor( Timber.d("Skipped refreshing user profile as device is offline.") return } - if (!USE_EMULATORS) { + if (!authenticationManager.currentUser.isAnonymous) { remoteDataStore.refreshUserProfile() } } diff --git a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt index 6e09f649b3..aaf0708de3 100644 --- a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt @@ -167,5 +167,5 @@ constructor( if (USE_EMULATORS) anonymousUser else getFirebaseAuth().currentUser?.toUser() private fun FirebaseUser.toUser(): User = - User(uid, email.orEmpty(), displayName.orEmpty(), photoUrl.toString()) + User(uid, email.orEmpty(), displayName.orEmpty(), photoUrl.toString(), isAnonymous) } From 5b75594f354a42626fe2babadb22a90cb25178d9 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Thu, 26 Oct 2023 12:36:05 -0400 Subject: [PATCH 11/16] Refactor anon handling --- .../auth/AnonymousAuthenticationManager.kt | 77 +++++++++++++++++++ .../system/auth/AuthenticationModule.kt | 27 +++++-- .../auth/GoogleAuthenticationManager.kt | 47 ++--------- 3 files changed, 107 insertions(+), 44 deletions(-) create mode 100644 ground/src/main/java/com/google/android/ground/system/auth/AnonymousAuthenticationManager.kt diff --git a/ground/src/main/java/com/google/android/ground/system/auth/AnonymousAuthenticationManager.kt b/ground/src/main/java/com/google/android/ground/system/auth/AnonymousAuthenticationManager.kt new file mode 100644 index 0000000000..59753df844 --- /dev/null +++ b/ground/src/main/java/com/google/android/ground/system/auth/AnonymousAuthenticationManager.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.ground.system.auth + +import com.google.android.ground.coroutines.ApplicationScope +import com.google.android.ground.model.User +import com.google.android.ground.rx.annotations.Hot +import com.google.firebase.auth.* +import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.Subject +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.rx2.await +import kotlinx.coroutines.tasks.await + +private val anonymousUser = User("nobody", "nobody", "Anonymous user ", null, true) + +/** + * Forces anonymous sign in with test user used when running against local Firebase Emulator Suite. + */ +class AnonymousAuthenticationManager +@Inject +constructor( + private val firebaseAuth: FirebaseAuth, + @ApplicationScope private val externalScope: CoroutineScope +) : AuthenticationManager { + override val signInState: @Hot(replays = true) Subject = BehaviorSubject.create() + + /** + * Returns the current user, blocking until a user logs in. Only call from code where user is + * guaranteed to be authenticated. + */ + override val currentUser: User + get() = + signInState + .filter { it.state == SignInState.State.SIGNED_IN } + .map { it.result.getOrNull()!! } + .blockingFirst() // TODO: Should this be blocking? + + override fun init() { + externalScope.launch { + signInState.onNext( + if (firebaseAuth.currentUser == null) SignInState.signedOut() + else SignInState.signedIn(anonymousUser) + ) + } + } + + override fun signIn() { + signInState.onNext(SignInState.signingIn()) + externalScope.launch { + firebaseAuth.signInAnonymously().await() + signInState.onNext(SignInState.signedIn(anonymousUser)) + } + } + + override fun signOut() { + externalScope.launch { + firebaseAuth.signOut() + signInState.onNext(SignInState.signedOut()) + } + } +} diff --git a/ground/src/main/java/com/google/android/ground/system/auth/AuthenticationModule.kt b/ground/src/main/java/com/google/android/ground/system/auth/AuthenticationModule.kt index 9980a05566..c68bda7863 100644 --- a/ground/src/main/java/com/google/android/ground/system/auth/AuthenticationModule.kt +++ b/ground/src/main/java/com/google/android/ground/system/auth/AuthenticationModule.kt @@ -15,17 +15,34 @@ */ package com.google.android.ground.system.auth -import dagger.Binds +import com.google.android.ground.BuildConfig.AUTH_EMULATOR_PORT +import com.google.android.ground.BuildConfig.EMULATOR_HOST +import com.google.android.ground.BuildConfig.USE_EMULATORS +import com.google.firebase.auth.FirebaseAuth import dagger.Module +import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @InstallIn(SingletonComponent::class) @Module -abstract class AuthenticationModule { - /** Provides the Google implementation of authentication manager. */ - @Binds +class AuthenticationModule { + /** Provides appropriate implementation of authentication manager. */ + @Provides @Singleton - abstract fun googleAuthenticationManager(gam: GoogleAuthenticationManager): AuthenticationManager + fun authenticationManager( + googleAuthMgr: GoogleAuthenticationManager, + anonymousAuthMgr: AnonymousAuthenticationManager + ): AuthenticationManager = if (USE_EMULATORS) anonymousAuthMgr else googleAuthMgr + + @Provides + fun firebaseAuth(): FirebaseAuth { + val auth = FirebaseAuth.getInstance() + if (USE_EMULATORS) { + // Use the auth emulator so we can sign-in anonymously during dev. + auth.useEmulator(EMULATOR_HOST, AUTH_EMULATOR_PORT) + } + return auth + } } diff --git a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt index aaf0708de3..a5a0a16e44 100644 --- a/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/auth/GoogleAuthenticationManager.kt @@ -22,12 +22,8 @@ import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInClient import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.gms.common.api.ApiException -import com.google.android.ground.BuildConfig.AUTH_EMULATOR_PORT -import com.google.android.ground.BuildConfig.EMULATOR_HOST -import com.google.android.ground.BuildConfig.USE_EMULATORS import com.google.android.ground.R import com.google.android.ground.coroutines.ApplicationScope -import com.google.android.ground.coroutines.IoDispatcher import com.google.android.ground.model.User import com.google.android.ground.rx.annotations.Hot import com.google.android.ground.system.ActivityResult @@ -36,25 +32,19 @@ import com.google.firebase.auth.* import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.asFlow -import kotlinx.coroutines.tasks.await -import kotlinx.coroutines.withContext import timber.log.Timber private val signInRequestCode = AuthenticationManager::class.java.hashCode() and 0xffff -/** Used when running against local Firebase Emulator Suite. */ -private val anonymousUser = User("nobody", "nobody", "Test User") - class GoogleAuthenticationManager @Inject constructor( resources: Resources, private val activityStreams: ActivityStreams, - @IoDispatcher private val ioDispatcher: CoroutineDispatcher, + private val firebaseAuth: FirebaseAuth, @ApplicationScope private val externalScope: CoroutineScope ) : AuthenticationManager { @@ -98,19 +88,9 @@ constructor( override fun signIn() { signInState.onNext(SignInState.signingIn()) - if (USE_EMULATORS) { - signInAnonymously() - } else { - showSignInDialog() - } + showSignInDialog() } - private fun signInAnonymously() = - externalScope.launch { - getFirebaseAuth().signInAnonymously().await() - signInState.onNext(SignInState.signedIn(anonymousUser)) - } - private fun showSignInDialog() = activityStreams.withActivity { val signInIntent = getGoogleSignInClient(it).signInIntent @@ -119,27 +99,17 @@ constructor( override fun signOut() { externalScope.launch { - getFirebaseAuth().signOut() + firebaseAuth.signOut() signInState.onNext(SignInState.signedOut()) activityStreams.withActivity { getGoogleSignInClient(it).signOut() } } } - private suspend fun getFirebaseAuth() = - withContext(ioDispatcher) { - val auth = FirebaseAuth.getInstance() - if (USE_EMULATORS) { - // Use the auth emulator so we can sign-in anonymously during dev. - auth.useEmulator(EMULATOR_HOST, AUTH_EMULATOR_PORT) - } - auth - } - private fun getGoogleSignInClient(activity: Activity): GoogleSignInClient = // TODO: Use app context instead of activity? GoogleSignIn.getClient(activity, googleSignInOptions) - private suspend fun onActivityResult(activityResult: ActivityResult) { + private fun onActivityResult(activityResult: ActivityResult) { // The Task returned from getSignedInAccountFromIntent is always completed, so no need to // attach a listener. try { @@ -151,8 +121,8 @@ constructor( } } - private suspend fun onGoogleSignIn(googleAccount: GoogleSignInAccount) = - getFirebaseAuth() + private fun onGoogleSignIn(googleAccount: GoogleSignInAccount) = + firebaseAuth .signInWithCredential(getFirebaseAuthCredential(googleAccount)) .addOnSuccessListener { authResult: AuthResult -> onFirebaseAuthSuccess(authResult) } .addOnFailureListener { signInState.onNext(SignInState.error(it)) } @@ -163,9 +133,8 @@ constructor( private fun getFirebaseAuthCredential(googleAccount: GoogleSignInAccount): AuthCredential = GoogleAuthProvider.getCredential(googleAccount.idToken, null) - private suspend fun getFirebaseUser(): User? = - if (USE_EMULATORS) anonymousUser else getFirebaseAuth().currentUser?.toUser() + private fun getFirebaseUser(): User? = firebaseAuth.currentUser?.toUser() private fun FirebaseUser.toUser(): User = - User(uid, email.orEmpty(), displayName.orEmpty(), photoUrl.toString(), isAnonymous) + User(uid, email.orEmpty(), displayName.orEmpty(), photoUrl.toString()) } From bea85829ead762786b8149d7341af54e91f8c51a Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Thu, 26 Oct 2023 12:40:57 -0400 Subject: [PATCH 12/16] Order an naming --- .../android/ground/system/auth/AuthenticationModule.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/system/auth/AuthenticationModule.kt b/ground/src/main/java/com/google/android/ground/system/auth/AuthenticationModule.kt index c68bda7863..734cfdf244 100644 --- a/ground/src/main/java/com/google/android/ground/system/auth/AuthenticationModule.kt +++ b/ground/src/main/java/com/google/android/ground/system/auth/AuthenticationModule.kt @@ -32,9 +32,10 @@ class AuthenticationModule { @Provides @Singleton fun authenticationManager( - googleAuthMgr: GoogleAuthenticationManager, - anonymousAuthMgr: AnonymousAuthenticationManager - ): AuthenticationManager = if (USE_EMULATORS) anonymousAuthMgr else googleAuthMgr + anonymousAuthenticationManager: AnonymousAuthenticationManager, + googleAuthenticationManager: GoogleAuthenticationManager + ): AuthenticationManager = + if (USE_EMULATORS) anonymousAuthenticationManager else googleAuthenticationManager @Provides fun firebaseAuth(): FirebaseAuth { From c3ee40880952acfc4157373988a623707d46500b Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Thu, 26 Oct 2023 12:44:13 -0400 Subject: [PATCH 13/16] Remove unneccessary coroutine launches --- .../auth/AnonymousAuthenticationManager.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/system/auth/AnonymousAuthenticationManager.kt b/ground/src/main/java/com/google/android/ground/system/auth/AnonymousAuthenticationManager.kt index 59753df844..acd19988fe 100644 --- a/ground/src/main/java/com/google/android/ground/system/auth/AnonymousAuthenticationManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/auth/AnonymousAuthenticationManager.kt @@ -52,12 +52,10 @@ constructor( .blockingFirst() // TODO: Should this be blocking? override fun init() { - externalScope.launch { - signInState.onNext( - if (firebaseAuth.currentUser == null) SignInState.signedOut() - else SignInState.signedIn(anonymousUser) - ) - } + signInState.onNext( + if (firebaseAuth.currentUser == null) SignInState.signedOut() + else SignInState.signedIn(anonymousUser) + ) } override fun signIn() { @@ -69,9 +67,7 @@ constructor( } override fun signOut() { - externalScope.launch { - firebaseAuth.signOut() - signInState.onNext(SignInState.signedOut()) - } + firebaseAuth.signOut() + signInState.onNext(SignInState.signedOut()) } } From 45a457f45fffae6741c2356fa688894ec9bc44c1 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Thu, 26 Oct 2023 14:03:09 -0400 Subject: [PATCH 14/16] Fix GCB --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 855e706b61..2ab6d7da7c 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -118,7 +118,7 @@ steps: cat unit-test-logs.txt # TODO: Add a check for _PUSH_TO_MASTER - ./gradlew jacocoTestDevStagingUnitTestReport + ./gradlew jacocoTestStagingUnitTestReport - name: 'gcr.io/$PROJECT_ID/android:34' id: &authenticate_gcloud 'Authorize gcloud' From 4056eb5b5032a058412346046dc2f9e4ea475530 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Thu, 26 Oct 2023 14:26:07 -0400 Subject: [PATCH 15/16] Fix GCB --- config/jacoco/jacoco.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/jacoco/jacoco.gradle b/config/jacoco/jacoco.gradle index 4282bffcac..337e48a472 100644 --- a/config/jacoco/jacoco.gradle +++ b/config/jacoco/jacoco.gradle @@ -26,7 +26,7 @@ tasks.withType(Test).configureEach { } tasks.register('jacocoTestStagingUnitTestReport', JacocoReport) { - dependsOn 'testDebugUnitTest' + dependsOn 'testDevDebugUnitTest' group = "Reporting" description = "Run tests and generate coverage reports" reports { From 04a6d01f137e67e9f2e7d0fc6827381c8dbbc095 Mon Sep 17 00:00:00 2001 From: Gino Miceli Date: Thu, 26 Oct 2023 14:26:25 -0400 Subject: [PATCH 16/16] Fix GCB --- config/jacoco/jacoco.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/jacoco/jacoco.gradle b/config/jacoco/jacoco.gradle index 337e48a472..51a29db16e 100644 --- a/config/jacoco/jacoco.gradle +++ b/config/jacoco/jacoco.gradle @@ -48,5 +48,5 @@ tasks.register('jacocoTestStagingUnitTestReport', JacocoReport) { ] ) sourceDirectories.from = files('src/main/java/com/google/android/ground') - executionData.from = files('build/jacoco/testDebugUnitTest.exec') + executionData.from = files('build/jacoco/testDevDebugUnitTest.exec') }