구글 로그인 회피 로직을 강화한다
승인 계정 우선 조회 후 전체 계정 재시도를 추가한다. 다른 계정 로그인 진입을 위해 구글 전용 옵션 경로를 제공한다. Android 14 이상에서 Play 서비스 버전을 점검하고 업데이트를 유도한다.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.user.login
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -13,12 +15,15 @@ import androidx.credentials.Credential
|
||||
import androidx.credentials.CredentialManager
|
||||
import androidx.credentials.CustomCredential
|
||||
import androidx.credentials.GetCredentialRequest
|
||||
import androidx.credentials.GetCredentialResponse
|
||||
import androidx.credentials.exceptions.GetCredentialException
|
||||
import androidx.credentials.exceptions.NoCredentialException
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
||||
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential.Companion.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
|
||||
import com.jakewharton.rxbinding4.widget.textChanges
|
||||
@@ -45,6 +50,7 @@ import kr.co.vividnext.sodalive.user.find_password.FindPasswordActivity
|
||||
import kr.co.vividnext.sodalive.user.signup.SignUpActivity
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.UUID
|
||||
import androidx.core.net.toUri
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::inflate) {
|
||||
@@ -57,6 +63,8 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
private var lineLoginNonce: String? = null
|
||||
private val minGooglePlayServicesMajor = 24
|
||||
private val minGooglePlayServicesMinor = 40
|
||||
|
||||
private val lineLoginLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
@@ -127,43 +135,11 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
||||
binding.ivSignUpEmail.setOnClickListener { startSignUp() }
|
||||
|
||||
binding.ivLoginGoogle.setOnClickListener {
|
||||
if (!isGoogleLoginAvailable()) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
loadingDialog.show(width = screenWidth)
|
||||
val credentialManager = CredentialManager.create(this)
|
||||
|
||||
val googleIdOption = GetGoogleIdOption.Builder()
|
||||
.setServerClientId(BuildConfig.GOOGLE_CLIENT_ID)
|
||||
.setFilterByAuthorizedAccounts(false)
|
||||
.setAutoSelectEnabled(false)
|
||||
.build()
|
||||
|
||||
// Create the Credential Manager request
|
||||
val request = GetCredentialRequest.Builder()
|
||||
.addCredentialOption(googleIdOption)
|
||||
.build()
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
// Launch Credential Manager UI
|
||||
val result = credentialManager.getCredential(
|
||||
context = this@LoginActivity,
|
||||
request = request
|
||||
)
|
||||
loadingDialog.dismiss()
|
||||
|
||||
// Extract credential from the result returned by Credential Manager
|
||||
handleSignIn(result.credential)
|
||||
} catch (e: GetCredentialException) {
|
||||
showToast(getString(R.string.login_google_failed))
|
||||
Logger.e(
|
||||
"Couldn't retrieve user's credentials: " +
|
||||
"${e.javaClass.simpleName}, ${e.localizedMessage}"
|
||||
)
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
startGoogleLogin(forceUseAllAccounts = false)
|
||||
}
|
||||
binding.ivLoginGoogle.setOnLongClickListener {
|
||||
startGoogleLogin(forceUseAllAccounts = true)
|
||||
true
|
||||
}
|
||||
|
||||
binding.ivLoginKakao.setOnClickListener { loginKakao() }
|
||||
@@ -309,6 +285,83 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
||||
startActivity(nextIntent)
|
||||
}
|
||||
|
||||
private fun startGoogleLogin(forceUseAllAccounts: Boolean) {
|
||||
if (!isGoogleLoginAvailable()) {
|
||||
return
|
||||
}
|
||||
loadingDialog.show(width = screenWidth)
|
||||
val credentialManager = CredentialManager.create(this)
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = if (forceUseAllAccounts) {
|
||||
getGoogleCredentialWithSignInOption(credentialManager)
|
||||
} else {
|
||||
getGoogleCredentialWithAuthorizedFirst(credentialManager)
|
||||
}
|
||||
handleSignIn(result.credential)
|
||||
} catch (e: GetCredentialException) {
|
||||
showToast(getString(R.string.login_google_failed))
|
||||
Logger.e(
|
||||
"Couldn't retrieve user's credentials: " +
|
||||
"${e.javaClass.simpleName}, ${e.localizedMessage}"
|
||||
)
|
||||
} finally {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGoogleCredentialWithAuthorizedFirst(
|
||||
credentialManager: CredentialManager
|
||||
): GetCredentialResponse {
|
||||
return try {
|
||||
credentialManager.getCredential(
|
||||
context = this@LoginActivity,
|
||||
request = buildGoogleIdRequest(filterByAuthorizedAccounts = true)
|
||||
)
|
||||
} catch (e: NoCredentialException) {
|
||||
Logger.i(
|
||||
"No authorized account. Retry with all accounts: ${e.localizedMessage}"
|
||||
)
|
||||
credentialManager.getCredential(
|
||||
context = this@LoginActivity,
|
||||
request = buildGoogleIdRequest(filterByAuthorizedAccounts = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGoogleCredentialWithSignInOption(
|
||||
credentialManager: CredentialManager
|
||||
): GetCredentialResponse {
|
||||
return credentialManager.getCredential(
|
||||
context = this@LoginActivity,
|
||||
request = buildSignInWithGoogleRequest()
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildGoogleIdRequest(filterByAuthorizedAccounts: Boolean): GetCredentialRequest {
|
||||
val googleIdOption = GetGoogleIdOption.Builder()
|
||||
.setServerClientId(BuildConfig.GOOGLE_CLIENT_ID)
|
||||
.setFilterByAuthorizedAccounts(filterByAuthorizedAccounts)
|
||||
.setAutoSelectEnabled(false)
|
||||
.build()
|
||||
|
||||
return GetCredentialRequest.Builder()
|
||||
.addCredentialOption(googleIdOption)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun buildSignInWithGoogleRequest(): GetCredentialRequest {
|
||||
val signInWithGoogleOption = GetSignInWithGoogleOption.Builder(
|
||||
BuildConfig.GOOGLE_CLIENT_ID
|
||||
).build()
|
||||
|
||||
return GetCredentialRequest.Builder()
|
||||
.addCredentialOption(signInWithGoogleOption)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun isGoogleLoginAvailable(): Boolean {
|
||||
if (BuildConfig.GOOGLE_CLIENT_ID.isBlank()) {
|
||||
Logger.e("Google login blocked: GOOGLE_CLIENT_ID is blank.")
|
||||
@@ -318,6 +371,16 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
||||
|
||||
val status = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this)
|
||||
if (status == ConnectionResult.SUCCESS) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
|
||||
isGooglePlayServicesVersionOutdated()
|
||||
) {
|
||||
Logger.e(
|
||||
"Google login blocked: Google Play services outdated for Android 14+."
|
||||
)
|
||||
promptGooglePlayServicesUpdate()
|
||||
showToast(getString(R.string.login_google_failed))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -330,6 +393,46 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isGooglePlayServicesVersionOutdated(): Boolean {
|
||||
return try {
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackageInfo(
|
||||
"com.google.android.gms",
|
||||
android.content.pm.PackageManager.PackageInfoFlags.of(0)
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
packageManager.getPackageInfo("com.google.android.gms", 0)
|
||||
}
|
||||
val versionName = packageInfo.versionName ?: return false
|
||||
val match = Regex("""^(\d+)\.(\d+)""").find(versionName) ?: return false
|
||||
val major = match.groupValues[1].toIntOrNull() ?: return false
|
||||
val minor = match.groupValues[2].toIntOrNull() ?: return false
|
||||
major < minGooglePlayServicesMajor ||
|
||||
(major == minGooglePlayServicesMajor && minor < minGooglePlayServicesMinor)
|
||||
} catch (e: Exception) {
|
||||
Logger.e("Failed to read Google Play services version: ${e.localizedMessage}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun promptGooglePlayServicesUpdate() {
|
||||
val marketIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
"market://details?id=com.google.android.gms".toUri()
|
||||
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
val webIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
"https://play.google.com/store/apps/details?id=com.google.android.gms".toUri()
|
||||
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
try {
|
||||
startActivity(marketIntent)
|
||||
} catch (_: Exception) {
|
||||
startActivity(webIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToMain() {
|
||||
finishAffinity()
|
||||
val nextIntent = Intent(applicationContext, MainActivity::class.java)
|
||||
|
||||
Reference in New Issue
Block a user