구글 로그인 회피 로직을 강화한다
승인 계정 우선 조회 후 전체 계정 재시도를 추가한다. 다른 계정 로그인 진입을 위해 구글 전용 옵션 경로를 제공한다. Android 14 이상에서 Play 서비스 버전을 점검하고 업데이트를 유도한다.
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package kr.co.vividnext.sodalive.user.login
|
package kr.co.vividnext.sodalive.user.login
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
@@ -13,12 +15,15 @@ import androidx.credentials.Credential
|
|||||||
import androidx.credentials.CredentialManager
|
import androidx.credentials.CredentialManager
|
||||||
import androidx.credentials.CustomCredential
|
import androidx.credentials.CustomCredential
|
||||||
import androidx.credentials.GetCredentialRequest
|
import androidx.credentials.GetCredentialRequest
|
||||||
|
import androidx.credentials.GetCredentialResponse
|
||||||
import androidx.credentials.exceptions.GetCredentialException
|
import androidx.credentials.exceptions.GetCredentialException
|
||||||
|
import androidx.credentials.exceptions.NoCredentialException
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.google.android.gms.common.ConnectionResult
|
import com.google.android.gms.common.ConnectionResult
|
||||||
import com.google.android.gms.common.GoogleApiAvailability
|
import com.google.android.gms.common.GoogleApiAvailability
|
||||||
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
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
|
||||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential.Companion.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
|
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential.Companion.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
|
||||||
import com.jakewharton.rxbinding4.widget.textChanges
|
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 kr.co.vividnext.sodalive.user.signup.SignUpActivity
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::inflate) {
|
class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::inflate) {
|
||||||
@@ -57,6 +63,8 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
|||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
private var lineLoginNonce: String? = null
|
private var lineLoginNonce: String? = null
|
||||||
|
private val minGooglePlayServicesMajor = 24
|
||||||
|
private val minGooglePlayServicesMinor = 40
|
||||||
|
|
||||||
private val lineLoginLauncher =
|
private val lineLoginLauncher =
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
@@ -127,43 +135,11 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
|||||||
binding.ivSignUpEmail.setOnClickListener { startSignUp() }
|
binding.ivSignUpEmail.setOnClickListener { startSignUp() }
|
||||||
|
|
||||||
binding.ivLoginGoogle.setOnClickListener {
|
binding.ivLoginGoogle.setOnClickListener {
|
||||||
if (!isGoogleLoginAvailable()) {
|
startGoogleLogin(forceUseAllAccounts = false)
|
||||||
return@setOnClickListener
|
}
|
||||||
}
|
binding.ivLoginGoogle.setOnLongClickListener {
|
||||||
loadingDialog.show(width = screenWidth)
|
startGoogleLogin(forceUseAllAccounts = true)
|
||||||
val credentialManager = CredentialManager.create(this)
|
true
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.ivLoginKakao.setOnClickListener { loginKakao() }
|
binding.ivLoginKakao.setOnClickListener { loginKakao() }
|
||||||
@@ -309,6 +285,83 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
|||||||
startActivity(nextIntent)
|
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 {
|
private fun isGoogleLoginAvailable(): Boolean {
|
||||||
if (BuildConfig.GOOGLE_CLIENT_ID.isBlank()) {
|
if (BuildConfig.GOOGLE_CLIENT_ID.isBlank()) {
|
||||||
Logger.e("Google login blocked: GOOGLE_CLIENT_ID is blank.")
|
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)
|
val status = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this)
|
||||||
if (status == ConnectionResult.SUCCESS) {
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,6 +393,46 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::i
|
|||||||
return false
|
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() {
|
private fun navigateToMain() {
|
||||||
finishAffinity()
|
finishAffinity()
|
||||||
val nextIntent = Intent(applicationContext, MainActivity::class.java)
|
val nextIntent = Intent(applicationContext, MainActivity::class.java)
|
||||||
|
|||||||
Reference in New Issue
Block a user