Google sign-in fallback chain: Credential Manager -> legacy API (microG) -> browser
Some checks failed
build / apk (push) Has been cancelled
Some checks failed
build / apk (push) Has been cancelled
- try Credential Manager first; on any failure fall back to the legacy GoogleSignIn API, which microG implements - only when no Google provider is available (isGooglePlayServicesAvailable fails) use the browser flow with the nikogpt:// hand-back - README: document the order and the Android OAuth client requirements - version 1.0.4
This commit is contained in:
parent
147b17dc44
commit
b2914f685a
3 changed files with 73 additions and 10 deletions
17
README.md
17
README.md
|
|
@ -51,6 +51,23 @@ apksigner sign --ks nikogpt.jks --out nikogpt-release.apk \
|
|||
app/build/outputs/apk/release/app-release-unsigned.apk
|
||||
```
|
||||
|
||||
## Google sign-in
|
||||
|
||||
Native "Continue with Google" is attempted in this order:
|
||||
|
||||
1. **Credential Manager** (modern GMS) — the standard account sheet;
|
||||
2. **legacy Google Sign-In API** — this is what **microG** implements, so the
|
||||
native picker works on de-Googled ROMs with microG and a Google account
|
||||
added there;
|
||||
3. **browser flow** — used when neither provider is available (no GMS, no
|
||||
microG, or cancelled); the browser returns to the app through the
|
||||
`nikogpt://auth` deep link and the session lands in the WebView.
|
||||
|
||||
Native sign-in needs an **Android-type OAuth client** in the same Google Cloud
|
||||
project, with the app's package name (`dev.n1ko.nikogpt`) and the SHA-1 of the
|
||||
signing certificate. The web client id (from the server) is passed as
|
||||
`serverClientId`.
|
||||
|
||||
## Structure
|
||||
|
||||
| Path | Purpose |
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ android {
|
|||
applicationId = "dev.n1ko.nikogpt"
|
||||
minSdk = 29 // Android 10
|
||||
targetSdk = 35
|
||||
versionCode = 3
|
||||
versionName = "1.0.3"
|
||||
versionCode = 4
|
||||
versionName = "1.0.4"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -51,4 +51,7 @@ dependencies {
|
|||
implementation("androidx.credentials:credentials:1.3.0")
|
||||
implementation("androidx.credentials:credentials-play-services-auth:1.3.0")
|
||||
implementation("com.google.android.libraries.identity.googleid:googleid:1.1.0")
|
||||
// Legacy Google Sign-In: microG implements this API, Credential Manager is
|
||||
// GMS-only. Both are tried before the browser fallback.
|
||||
implementation("com.google.android.gms:play-services-auth:21.2.0")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ import androidx.credentials.GetCredentialRequest
|
|||
import androidx.credentials.exceptions.GetCredentialException
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -72,6 +77,23 @@ class MainActivity : AppCompatActivity() {
|
|||
fileCallback = null
|
||||
}
|
||||
|
||||
// microG path: the legacy Google Sign-In API, which microG implements.
|
||||
private var legacyLinkMode = false
|
||||
private val legacySignIn: ActivityResultLauncher<Intent> =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
val link = legacyLinkMode
|
||||
try {
|
||||
val account = GoogleSignIn.getSignedInAccountFromIntent(result.data)
|
||||
.getResult(ApiException::class.java)
|
||||
val token = account?.idToken
|
||||
if (!token.isNullOrBlank()) deliverToken(token, link) else openGoogleInBrowser(link)
|
||||
} catch (e: ApiException) {
|
||||
openGoogleInBrowser(link)
|
||||
} catch (e: Exception) {
|
||||
openGoogleInBrowser(link)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -218,12 +240,10 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
// openGooglePicker requests an ID token from Google Play services
|
||||
// (Credential Manager). Without a usable provider — e.g. microG, no
|
||||
// accounts, user cancelled — it falls back to the browser flow with the
|
||||
// nikogpt:// hand-back.
|
||||
// openGooglePicker tries the native pickers in order: Credential Manager
|
||||
// (modern GMS) → legacy Google Sign-In (microG) → browser flow.
|
||||
private fun openGooglePicker(clientId: String, link: Boolean) {
|
||||
if (clientId.isBlank()) {
|
||||
if (clientId.isBlank() || !googleServicesAvailable()) {
|
||||
openGoogleInBrowser(link)
|
||||
return
|
||||
}
|
||||
|
|
@ -241,14 +261,37 @@ class MainActivity : AppCompatActivity() {
|
|||
.getCredential(this@MainActivity, request)
|
||||
val idToken = GoogleIdTokenCredential.createFrom(response.credential.data).idToken
|
||||
deliverToken(idToken, link)
|
||||
} catch (e: GetCredentialException) {
|
||||
openGoogleInBrowser(link)
|
||||
} catch (e: Exception) {
|
||||
openGoogleInBrowser(link)
|
||||
// No Credential Manager provider (microG) — try the legacy API.
|
||||
openLegacyGooglePicker(clientId, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// googleServicesAvailable is true for GMS and for microG (which reports
|
||||
// itself as Play services when signature spoofing is enabled).
|
||||
private fun googleServicesAvailable(): Boolean {
|
||||
return try {
|
||||
GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("microG only implements the legacy Google Sign-In API")
|
||||
private fun openLegacyGooglePicker(clientId: String, link: Boolean) {
|
||||
legacyLinkMode = link
|
||||
try {
|
||||
val options = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
.requestIdToken(clientId)
|
||||
.requestEmail()
|
||||
.build()
|
||||
legacySignIn.launch(GoogleSignIn.getClient(this, options).signInIntent)
|
||||
} catch (e: Exception) {
|
||||
openGoogleInBrowser(link)
|
||||
}
|
||||
}
|
||||
|
||||
// deliverToken hands the ID token back to the page, which signs in
|
||||
// through the API inside this WebView (so the cookie stays in the app).
|
||||
private fun deliverToken(idToken: String, link: Boolean) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue