Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cb3ec1b
wip
DenBond7 Jun 5, 2024
8f1d476
wip
DenBond7 Jun 5, 2024
cc31ff3
wip
DenBond7 Jun 5, 2024
0d15070
wip
DenBond7 Jun 6, 2024
4a49d9e
wip
DenBond7 Jun 6, 2024
ddab634
wip
DenBond7 Jun 6, 2024
a877fb5
wip
DenBond7 Jun 6, 2024
fe99928
wip
DenBond7 Jun 6, 2024
9f43e56
wip
DenBond7 Jun 6, 2024
2c0ce0f
wip
DenBond7 Jun 6, 2024
a1bc62f
wip
DenBond7 Jun 6, 2024
3238b87
wip
DenBond7 Jun 6, 2024
404fb79
wip
DenBond7 Jun 6, 2024
3e9a35d
wip
DenBond7 Jun 10, 2024
bfd8810
Merge branch 'master' into issue_2531_Migrate_to_Credential_Manager
DenBond7 Jun 10, 2024
4b9d5d4
wip
DenBond7 Jun 11, 2024
de43638
wip
DenBond7 Jun 11, 2024
dce8289
Merge branch 'issue_2531_Migrate_to_Credential_Manager' of github.com…
DenBond7 Jun 11, 2024
6cad720
wip
DenBond7 Jun 11, 2024
bf450af
wip
DenBond7 Jun 11, 2024
31ba3e6
Merge branch 'master' into issue_2531_Migrate_to_Credential_Manager
DenBond7 Jun 12, 2024
49fce88
Disabled some tests.| #2531
DenBond7 Jun 12, 2024
b0a9d51
Merge branch 'master' into issue_2531_Migrate_to_Credential_Manager
DenBond7 Jun 12, 2024
6ed590c
wip
DenBond7 Jun 13, 2024
3410655
Merge branch 'master' into issue_2531_Migrate_to_Credential_Manager
DenBond7 Sep 10, 2026
4f24e7a
Migrated to Credential Manager and play-services-auth 22.0.0. A draft…
DenBond7 Sep 10, 2026
1e68e9a
Fix and re-enable UI tests after Credential Manager migration | #2531
DenBond7 Sep 10, 2026
a578c6a
Remove unused SignInWithGoogleViewModel and revert related Result cha…
DenBond7 Sep 10, 2026
cfa4142
Fix CredentialManagerMisuse lint error by handling NoCredentialException
DenBond7 Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion FlowCrypt/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -510,11 +510,15 @@ dependencies {
implementation("androidx.navigation:navigation-ui-ktx:2.10.0")
implementation("androidx.navigation:navigation-runtime-ktx:2.10.0")
implementation("androidx.webkit:webkit:1.16.0")
implementation("androidx.credentials:credentials:1.6.0")
implementation("androidx.credentials:credentials-play-services-auth:1.6.0")

implementation("com.google.android.gms:play-services-base:18.10.1")
implementation("com.google.android.gms:play-services-auth:21.6.0")
implementation("com.google.android.gms:play-services-auth:22.0.0")
implementation("com.google.android.material:material:1.14.0")
implementation("com.google.android.flexbox:flexbox:3.0.0")
implementation("com.google.android.libraries.identity.googleid:googleid:1.2.0")

implementation("com.google.code.gson:gson:2.14.0")
implementation("com.google.api-client:google-api-client-android:2.9.1")
implementation("com.google.apis:google-api-services-gmail:v1-rev20251201-2.0.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ package com.flowcrypt.email.ui.activity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.toPackage
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
Expand All @@ -25,6 +23,11 @@ import com.flowcrypt.email.rules.GrantPermissionRuleChooser
import com.flowcrypt.email.rules.RetryRule
import com.flowcrypt.email.rules.ScreenshotTestRule
import org.hamcrest.Matchers.allOf
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
Expand Down Expand Up @@ -65,7 +68,9 @@ class SignInScreenFlowTest : BaseTest() {
.check(matches(isDisplayed()))
.perform(click())
//check that the Google Sign-in screen displayed
intended(toPackage("com.google.android.gms"))
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
assertTrue(device.wait(Until.hasObject(By.pkg("com.google.android.gms")), 5000))
device.pressBack()
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,50 @@

package com.flowcrypt.email.ui.base

import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
import android.net.Uri
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import com.flowcrypt.email.R
import com.flowcrypt.email.base.BaseTest
import com.flowcrypt.email.util.google.GoogleApiClientHelper
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.flowcrypt.email.util.FlavorSettings
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import okhttp3.mockwebserver.RecordedRequest
import org.jose4j.jwa.AlgorithmConstraints
import org.jose4j.jws.AlgorithmIdentifiers
import org.jose4j.jws.JsonWebSignature
import org.jose4j.jwt.JwtClaims
import org.jose4j.jwt.consumer.JwtConsumerBuilder
import org.json.JSONObject
import org.junit.After


/**
* @author Denys Bondarenko
*/
abstract class BaseSignTest : BaseTest() {

protected fun setupAndClickSignInButton(signInAccountJson: String) {
val intent = Intent()
intent.putExtra("googleSignInAccount", GoogleSignInAccount.zaa(signInAccountJson))

val signInIntent = GoogleSignIn.getClient(
getTargetContext(),
GoogleApiClientHelper.generateGoogleSignInOptions()
).signInIntent
@After
fun resetGoogleIdTokenCredential() {
FlavorSettings.setGoogleIdTokenCredential(null)
}

intending(hasComponent(signInIntent.component))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, intent))
protected fun setupAndClickSignInButton(signInAccountJson: String) {
val jsonObject = JSONObject(signInAccountJson)
FlavorSettings.setGoogleIdTokenCredential(
GoogleIdTokenCredential(
id = jsonObject.getString("email"),
idToken = jsonObject.getString("tokenId"),
displayName = jsonObject.optString("displayName").ifEmpty { null },
familyName = jsonObject.optString("familyName").ifEmpty { null },
givenName = jsonObject.optString("givenName").ifEmpty { null },
profilePictureUri = jsonObject.optString("photoUrl").takeIf { it.isNotEmpty() }
?.let(Uri::parse),
phoneNumber = null
)
)

onView(withId(R.id.buttonSignInWithGmail))
.check(matches(isDisplayed()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,26 @@ package com.flowcrypt.email.util

import android.content.Context
import androidx.test.espresso.idling.CountingIdlingResource
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import java.util.UUID

/**
* @author Denys Bondarenko
*/
object FlavorSettings : EnvironmentSettings {
private var cachedGoogleIdTokenCredential: GoogleIdTokenCredential? = null

private val countingIdlingResource: CountingIdlingResource = CountingIdlingResource(
GeneralUtil.genIdlingResourcesName(this::class.java),
GeneralUtil.isDebugBuild()
)
override fun configure(context: Context) {}
override fun getCountingIdlingResource() = countingIdlingResource
override fun getGoogleIdToken(): String = UUID.randomUUID().toString()
override fun getGoogleIdTokenCredential(): GoogleIdTokenCredential? = cachedGoogleIdTokenCredential
fun setGoogleIdTokenCredential(credential: GoogleIdTokenCredential?) {
cachedGoogleIdTokenCredential = credential
}
override fun getGmailAPIRootUrl() = "https://flowcrypt.test/"
override fun isGMailAPIHttpRequestInitializerEnabled(): Boolean = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ package com.flowcrypt.email.util

import android.content.Context
import androidx.test.espresso.idling.CountingIdlingResource
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import java.util.UUID

/**
* @author Denys Bondarenko
*/
object FlavorSettings : EnvironmentSettings {
private var cachedGoogleIdTokenCredential: GoogleIdTokenCredential? = null

private val countingIdlingResource: CountingIdlingResource = CountingIdlingResource(
GeneralUtil.genIdlingResourcesName(this::class.java),
GeneralUtil.isDebugBuild()
Expand All @@ -21,6 +24,10 @@ object FlavorSettings : EnvironmentSettings {
override fun configure(context: Context) {}
override fun getCountingIdlingResource() = countingIdlingResource
override fun getGoogleIdToken(): String = UUID.randomUUID().toString()
override fun getGoogleIdTokenCredential(): GoogleIdTokenCredential? = cachedGoogleIdTokenCredential
fun setGoogleIdTokenCredential(credential: GoogleIdTokenCredential?) {
cachedGoogleIdTokenCredential = credential
}
override fun getGmailAPIRootUrl() = "https://flowcrypt.test/"
override fun isGMailAPIHttpRequestInitializerEnabled(): Boolean = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ class FoldersManager constructor(val accountEntity: AccountEntity) {
*/
fun getSortedServerFolders(): Collection<LocalFolder> {
val localFolders = serverFolders.toMutableList()
if (localFolders.size <= 1) {
return localFolders
}
val sortedList = arrayOfNulls<LocalFolder>(localFolders.size)

val inbox = folderInbox?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import com.flowcrypt.email.api.email.model.AuthCredentials
import com.flowcrypt.email.api.email.model.SecurityType
import com.flowcrypt.email.api.retrofit.response.model.ClientConfiguration
import com.flowcrypt.email.security.KeyStoreCryptoManager
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.GoogleAuthUtil
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.IgnoredOnParcel
Expand Down Expand Up @@ -103,22 +104,20 @@ data class AccountEntity(
get() = JavaEmailConstants.AUTH_MECHANISMS_XOAUTH2 == imapAuthMechanisms

constructor(
googleSignInAccount: GoogleSignInAccount,
googleIdTokenCredential: GoogleIdTokenCredential,
clientConfiguration: ClientConfiguration? = null,
useCustomerFesUrl: Boolean,
useStartTlsForSmtp: Boolean = false,
) : this(
email = requireNotNull(googleSignInAccount.email).lowercase(),
accountType = googleSignInAccount.account?.type?.lowercase() ?: EmailUtil.getDomain(
requireNotNull(googleSignInAccount.email)
).ifEmpty { ACCOUNT_TYPE_UNKNOWN },
displayName = googleSignInAccount.displayName,
givenName = googleSignInAccount.givenName,
familyName = googleSignInAccount.familyName,
photoUrl = googleSignInAccount.photoUrl?.toString(),
email = googleIdTokenCredential.id.lowercase(),
accountType = GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE,
displayName = googleIdTokenCredential.displayName,
givenName = googleIdTokenCredential.givenName,
familyName = googleIdTokenCredential.familyName,
photoUrl = googleIdTokenCredential.profilePictureUri?.toString(),
isEnabled = true,
isActive = false,
username = requireNotNull(googleSignInAccount.email),
username = googleIdTokenCredential.id,
password = "",
imapServer = GmailConstants.GMAIL_IMAP_SERVER,
imapPort = GmailConstants.GMAIL_IMAP_PORT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

package com.flowcrypt.email.extensions.java.lang

import androidx.fragment.app.Fragment
import com.flowcrypt.email.R
import com.flowcrypt.email.extensions.androidx.fragment.app.showInfoDialog
import com.flowcrypt.email.util.GeneralUtil

/**
Expand All @@ -15,3 +18,13 @@ fun Exception.printStackTraceIfDebugOnly() {
printStackTrace()
}
}

fun Exception.showDialogWithErrorDetails(fragment: Fragment) {
fragment.showInfoDialog(
dialogTitle = "",
dialogMsg = fragment.getString(
R.string.error_occurred_with_details_please_try_again,
localizedMessage
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.GravityCompat
import androidx.core.view.get
import androidx.core.view.size
import androidx.credentials.ClearCredentialStateRequest
import androidx.credentials.CredentialManager
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
Expand Down Expand Up @@ -65,6 +67,7 @@ import com.flowcrypt.email.extensions.android.content.getParcelableExtraViaExt
import com.flowcrypt.email.extensions.decrementSafely
import com.flowcrypt.email.extensions.exceptionMsg
import com.flowcrypt.email.extensions.incrementSafely
import com.flowcrypt.email.extensions.java.lang.printStackTraceIfDebugOnly
import com.flowcrypt.email.extensions.kotlin.parseAsColorBasedOnDefaultSettings
import com.flowcrypt.email.extensions.showFeedbackFragment
import com.flowcrypt.email.extensions.showInfoDialog
Expand All @@ -87,16 +90,12 @@ import com.flowcrypt.email.util.GeneralUtil
import com.flowcrypt.email.util.SharedPreferencesHelper
import com.flowcrypt.email.util.exception.CommonConnectionException
import com.flowcrypt.email.util.exception.EmptyPassphraseException
import com.flowcrypt.email.util.google.GoogleApiClientHelper
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import kotlinx.coroutines.launch

/**
* @author Denys Bondarenko
*/
class MainActivity : BaseActivity<ActivityMainBinding>() {
private lateinit var client: GoogleSignInClient
private var navigationViewManager: NavigationViewManager? = null

private val launcherViewModel: LauncherViewModel by viewModels()
Expand Down Expand Up @@ -161,8 +160,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
super.onCreate(savedInstanceState)
observeMovingToBackground()

client = GoogleSignIn.getClient(this, GoogleApiClientHelper.generateGoogleSignInOptions())

IdleService.start(this)
IdleService.bind(this, idleServiceConnection)

Expand Down Expand Up @@ -434,7 +431,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun logout() {
lifecycleScope.launch {
activeAccount?.let { accountEntity ->
if (accountEntity.accountType == AccountEntity.ACCOUNT_TYPE_GOOGLE) client.signOut()
if (accountEntity.accountType == AccountEntity.ACCOUNT_TYPE_GOOGLE) {
runCatching {
CredentialManager.create(this@MainActivity)
.clearCredentialState(ClearCredentialStateRequest())
}
}

FlavorSettings.getCountingIdlingResource().incrementSafely(this@MainActivity)
WorkManager.getInstance(applicationContext).cancelAllWorkByTag(BaseSyncWorker.TAG_SYNC)
Expand Down
Loading
Loading