@@ -18,8 +18,12 @@ import android.app.Activity
1818import android.content.Context
1919import android.net.Uri
2020import android.util.Log
21+ import androidx.annotation.RestrictTo
22+ import androidx.annotation.VisibleForTesting
2123import androidx.compose.ui.graphics.Color
24+ import androidx.core.net.toUri
2225import androidx.datastore.preferences.core.stringPreferencesKey
26+ import com.facebook.AccessToken
2327import com.firebase.ui.auth.R
2428import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration
2529import com.firebase.ui.auth.compose.configuration.AuthUIConfigurationDsl
@@ -44,8 +48,8 @@ import com.google.firebase.auth.PhoneAuthProvider
4448import com.google.firebase.auth.TwitterAuthProvider
4549import com.google.firebase.auth.UserProfileChangeRequest
4650import com.google.firebase.auth.actionCodeSettings
51+ import kotlinx.coroutines.suspendCancellableCoroutine
4752import kotlinx.coroutines.tasks.await
48- import kotlinx.serialization.Serializable
4953import java.util.concurrent.TimeUnit
5054import kotlin.coroutines.resume
5155import kotlin.coroutines.resumeWithException
@@ -89,14 +93,16 @@ internal enum class Provider(val id: String, val isSocialProvider: Boolean = fal
8993 */
9094abstract class OAuthProvider (
9195 override val providerId : String ,
96+
97+ override val name : String ,
9298 open val scopes : List <String > = emptyList(),
9399 open val customParameters : Map <String , String > = emptyMap(),
94- ) : AuthProvider(providerId)
100+ ) : AuthProvider(providerId = providerId, name = name )
95101
96102/* *
97103 * Base abstract class for authentication providers.
98104 */
99- abstract class AuthProvider (open val providerId : String ) {
105+ abstract class AuthProvider (open val providerId : String , open val name : String ) {
100106
101107 companion object {
102108 internal fun canUpgradeAnonymous (config : AuthUIConfiguration , auth : FirebaseAuth ): Boolean {
@@ -201,7 +207,7 @@ abstract class AuthProvider(open val providerId: String) {
201207 * A list of custom password validation rules.
202208 */
203209 val passwordValidationRules : List <PasswordRule >,
204- ) : AuthProvider(providerId = Provider .EMAIL .id) {
210+ ) : AuthProvider(providerId = Provider .EMAIL .id, name = " Email " ) {
205211 companion object {
206212 const val SESSION_ID_LENGTH = 10
207213 val KEY_EMAIL = stringPreferencesKey(" com.firebase.ui.auth.data.client.email" )
@@ -327,7 +333,7 @@ abstract class AuthProvider(open val providerId: String) {
327333 * Enables instant verification of the phone number. Defaults to true.
328334 */
329335 val isInstantVerificationEnabled : Boolean = true ,
330- ) : AuthProvider(providerId = Provider .PHONE .id) {
336+ ) : AuthProvider(providerId = Provider .PHONE .id, name = " Phone " ) {
331337 /* *
332338 * Sealed class representing the result of phone number verification.
333339 *
@@ -550,6 +556,7 @@ abstract class AuthProvider(open val providerId: String) {
550556 override val customParameters : Map <String , String > = emptyMap(),
551557 ) : OAuthProvider(
552558 providerId = Provider .GOOGLE .id,
559+ name = " Google" ,
553560 scopes = scopes,
554561 customParameters = customParameters
555562 ) {
@@ -591,17 +598,13 @@ abstract class AuthProvider(open val providerId: String) {
591598 */
592599 override val scopes : List <String > = listOf("email", "public_profile"),
593600
594- /* *
595- * if true, enable limited login mode. Defaults to false.
596- */
597- val limitedLogin : Boolean = false ,
598-
599601 /* *
600602 * A map of custom OAuth parameters.
601603 */
602604 override val customParameters : Map <String , String > = emptyMap(),
603605 ) : OAuthProvider(
604606 providerId = Provider .FACEBOOK .id,
607+ name = " Facebook" ,
605608 scopes = scopes,
606609 customParameters = customParameters
607610 ) {
@@ -627,6 +630,113 @@ abstract class AuthProvider(open val providerId: String) {
627630 }
628631 }
629632 }
633+
634+ /* *
635+ * An interface to wrap the static `FacebookAuthProvider.getCredential` method to make it testable.
636+ * @suppress
637+ */
638+ @VisibleForTesting(otherwise = VisibleForTesting .PROTECTED )
639+ interface CredentialProvider {
640+ fun getCredential (token : String ): AuthCredential
641+ }
642+
643+ /* *
644+ * The default implementation of [CredentialProvider] that calls the static method.
645+ * @suppress
646+ */
647+ internal class DefaultCredentialProvider : CredentialProvider {
648+ override fun getCredential (token : String ): AuthCredential {
649+ return FacebookAuthProvider .getCredential(token)
650+ }
651+ }
652+
653+ /* *
654+ * Internal data class to hold Facebook profile information.
655+ */
656+ internal class FacebookProfileData (
657+ val displayName : String? ,
658+ val email : String? ,
659+ val photoUrl : Uri ? ,
660+ )
661+
662+ /* *
663+ * Fetches user profile data from Facebook Graph API.
664+ *
665+ * @param accessToken The Facebook access token
666+ * @return FacebookProfileData containing user's display name, email, and photo URL
667+ */
668+ internal suspend fun fetchFacebookProfile (accessToken : AccessToken ): FacebookProfileData ? {
669+ return suspendCancellableCoroutine { continuation ->
670+ val request =
671+ com.facebook.GraphRequest .newMeRequest(accessToken) { jsonObject, response ->
672+ try {
673+ val error = response?.error
674+ if (error != null ) {
675+ Log .e(
676+ " FirebaseAuthUI.signInWithFacebook" ,
677+ " Graph API error: ${error.errorMessage} "
678+ )
679+ continuation.resume(null )
680+ return @newMeRequest
681+ }
682+
683+ if (jsonObject == null ) {
684+ Log .e(
685+ " FirebaseAuthUI.signInWithFacebook" ,
686+ " Graph API returned null response"
687+ )
688+ continuation.resume(null )
689+ return @newMeRequest
690+ }
691+
692+ val name = jsonObject.optString(" name" )
693+ val email = jsonObject.optString(" email" )
694+
695+ // Extract photo URL from picture object
696+ val photoUrl = try {
697+ jsonObject.optJSONObject(" picture" )
698+ ?.optJSONObject(" data" )
699+ ?.optString(" url" )
700+ ?.takeIf { it.isNotEmpty() }?.toUri()
701+ } catch (e: Exception ) {
702+ Log .w(
703+ " FirebaseAuthUI.signInWithFacebook" ,
704+ " Error parsing photo URL" ,
705+ e
706+ )
707+ null
708+ }
709+
710+ Log .d(
711+ " FirebaseAuthUI.signInWithFacebook" ,
712+ " Profile fetched: name=$name , email=$email , hasPhoto=${photoUrl != null } "
713+ )
714+
715+ continuation.resume(
716+ FacebookProfileData (
717+ displayName = name,
718+ email = email,
719+ photoUrl = photoUrl
720+ )
721+ )
722+ } catch (e: Exception ) {
723+ Log .e(
724+ " FirebaseAuthUI.signInWithFacebook" ,
725+ " Error processing Graph API response" ,
726+ e
727+ )
728+ continuation.resume(null )
729+ }
730+ }
731+
732+ // Request specific fields: id, name, email, and picture
733+ val parameters = android.os.Bundle ().apply {
734+ putString(" fields" , " id,name,email,picture" )
735+ }
736+ request.parameters = parameters
737+ request.executeAsync()
738+ }
739+ }
630740 }
631741
632742 /* *
@@ -639,6 +749,7 @@ abstract class AuthProvider(open val providerId: String) {
639749 override val customParameters : Map <String , String >,
640750 ) : OAuthProvider(
641751 providerId = Provider .TWITTER .id,
752+ name = " Twitter" ,
642753 customParameters = customParameters
643754 )
644755
@@ -657,6 +768,7 @@ abstract class AuthProvider(open val providerId: String) {
657768 override val customParameters : Map <String , String >,
658769 ) : OAuthProvider(
659770 providerId = Provider .GITHUB .id,
771+ name = " Github" ,
660772 scopes = scopes,
661773 customParameters = customParameters
662774 )
@@ -681,6 +793,7 @@ abstract class AuthProvider(open val providerId: String) {
681793 override val customParameters : Map <String , String >,
682794 ) : OAuthProvider(
683795 providerId = Provider .MICROSOFT .id,
796+ name = " Microsoft" ,
684797 scopes = scopes,
685798 customParameters = customParameters
686799 )
@@ -700,6 +813,7 @@ abstract class AuthProvider(open val providerId: String) {
700813 override val customParameters : Map <String , String >,
701814 ) : OAuthProvider(
702815 providerId = Provider .YAHOO .id,
816+ name = " Yahoo" ,
703817 scopes = scopes,
704818 customParameters = customParameters
705819 )
@@ -724,14 +838,18 @@ abstract class AuthProvider(open val providerId: String) {
724838 override val customParameters : Map <String , String >,
725839 ) : OAuthProvider(
726840 providerId = Provider .APPLE .id,
841+ name = " Apple" ,
727842 scopes = scopes,
728843 customParameters = customParameters
729844 )
730845
731846 /* *
732847 * Anonymous authentication provider. It has no configurable properties.
733848 */
734- object Anonymous : AuthProvider(providerId = Provider .ANONYMOUS .id) {
849+ object Anonymous : AuthProvider(
850+ providerId = Provider .ANONYMOUS .id,
851+ name = " Anonymous"
852+ ) {
735853 internal fun validate (providers : List <AuthProvider >) {
736854 if (providers.size == 1 && providers.first() is Anonymous ) {
737855 throw IllegalStateException (
@@ -746,6 +864,11 @@ abstract class AuthProvider(open val providerId: String) {
746864 * A generic OAuth provider for any unsupported provider.
747865 */
748866 class GenericOAuth (
867+ /* *
868+ * The provider name.
869+ */
870+ override val name : String ,
871+
749872 /* *
750873 * The provider ID as configured in the Firebase console.
751874 */
@@ -782,6 +905,7 @@ abstract class AuthProvider(open val providerId: String) {
782905 val contentColor : Color ? ,
783906 ) : OAuthProvider(
784907 providerId = providerId,
908+ name = name,
785909 scopes = scopes,
786910 customParameters = customParameters
787911 ) {
0 commit comments