Unverified Commit ae33779b authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #1334 from RocketChat/ux/smart-lock

[IMPROVEMENT] Google Smart Lock feature refactor
parents f6ad8e18 0802e41d
...@@ -55,7 +55,6 @@ class LoginPresenter @Inject constructor( ...@@ -55,7 +55,6 @@ class LoginPresenter @Inject constructor(
private lateinit var credentialSecret: String private lateinit var credentialSecret: String
private lateinit var deepLinkUserId: String private lateinit var deepLinkUserId: String
private lateinit var deepLinkToken: String private lateinit var deepLinkToken: String
private var loginCredentials: Credential? = null
fun setupView() { fun setupView() {
setupConnectionInfo(currentServer) setupConnectionInfo(currentServer)
...@@ -330,10 +329,7 @@ class LoginPresenter @Inject constructor( ...@@ -330,10 +329,7 @@ class LoginPresenter @Inject constructor(
saveToken(token) saveToken(token)
registerPushToken() registerPushToken()
if (loginType == TYPE_LOGIN_USER_EMAIL) { if (loginType == TYPE_LOGIN_USER_EMAIL) {
loginCredentials = Credential.Builder(usernameOrEmail) view.saveSmartLockCredentials(usernameOrEmail, password)
.setPassword(password)
.build()
view.saveSmartLockCredentials(loginCredentials)
} }
navigator.toChatList() navigator.toChatList()
} else if (loginType == TYPE_LOGIN_OAUTH) { } else if (loginType == TYPE_LOGIN_OAUTH) {
......
...@@ -226,7 +226,7 @@ interface LoginView : LoadingView, MessageView { ...@@ -226,7 +226,7 @@ interface LoginView : LoadingView, MessageView {
fun alertWrongPassword() fun alertWrongPassword()
/** /**
* Save credentials via google smart lock * Saves Google Smart Lock credentials.
*/ */
fun saveSmartLockCredentials(loginCredential: Credential?) fun saveSmartLockCredentials(id: String, password: String)
} }
\ No newline at end of file
...@@ -2,28 +2,27 @@ package chat.rocket.android.authentication.login.ui ...@@ -2,28 +2,27 @@ package chat.rocket.android.authentication.login.ui
import DrawableHelper import DrawableHelper
import android.app.Activity import android.app.Activity
import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.content.IntentSender
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v4.app.FragmentActivity
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.* import android.widget.Button
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.ScrollView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.postDelayed import androidx.core.view.postDelayed
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.login.presentation.LoginPresenter import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.*
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import chat.rocket.android.webview.cas.ui.INTENT_CAS_TOKEN import chat.rocket.android.webview.cas.ui.INTENT_CAS_TOKEN
import chat.rocket.android.webview.cas.ui.casWebViewIntent import chat.rocket.android.webview.cas.ui.casWebViewIntent
...@@ -31,37 +30,24 @@ import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_SECRET ...@@ -31,37 +30,24 @@ import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_SECRET
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import com.google.android.gms.auth.api.Auth
import com.google.android.gms.auth.api.credentials.* import com.google.android.gms.auth.api.credentials.*
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.common.api.ResolvingResultCallbacks
import com.google.android.gms.common.api.Status
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.* import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal const val REQUEST_CODE_FOR_CAS = 4
internal const val REQUEST_CODE_FOR_OAUTH = 5
internal const val REQUEST_CODE_FOR_CAS = 1 class LoginFragment : Fragment(), LoginView {
internal const val REQUEST_CODE_FOR_OAUTH = 2
internal const val MULTIPLE_CREDENTIALS_READ = 3
internal const val NO_CREDENTIALS_EXIST = 4
internal const val SAVE_CREDENTIALS = 5
lateinit var googleApiClient: GoogleApiClient
class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks {
@Inject @Inject
lateinit var presenter: LoginPresenter lateinit var presenter: LoginPresenter
private var isOauthViewEnable = false private var isOauthViewEnable = false
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
areLoginOptionsNeeded() areLoginOptionsNeeded()
} }
private var isOauthSuccessful = false
private var isGlobalLayoutListenerSetUp = false private var isGlobalLayoutListenerSetUp = false
private var deepLinkInfo: LoginDeepLinkInfo? = null private var deepLinkInfo: LoginDeepLinkInfo? = null
private var credentialsToBeSaved: Credential? = null private val credentialsClient by lazy { Credentials.getClient(requireActivity()) }
companion object { companion object {
private const val DEEP_LINK_INFO = "DeepLinkInfo" private const val DEEP_LINK_INFO = "DeepLinkInfo"
...@@ -73,17 +59,9 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks ...@@ -73,17 +59,9 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
} }
} }
override fun onConnected(bundle: Bundle?) {
saveSmartLockCredentials(credentialsToBeSaved)
}
override fun onConnectionSuspended(errorCode: Int) {
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
buildGoogleApiClient()
deepLinkInfo = arguments?.getParcelable(DEEP_LINK_INFO) deepLinkInfo = arguments?.getParcelable(DEEP_LINK_INFO)
} }
...@@ -120,153 +98,37 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks ...@@ -120,153 +98,37 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
if (data != null) { if (data != null) {
when (requestCode) { when (requestCode) {
REQUEST_CODE_FOR_CAS -> data.apply { REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION -> {
presenter.authenticateWithCas(getStringExtra(INTENT_CAS_TOKEN)) onCredentialRetrieved(data.getParcelableExtra(Credential.EXTRA_KEY))
} }
REQUEST_CODE_FOR_OAUTH -> { REQUEST_CODE_FOR_SIGN_IN_REQUIRED -> {
isOauthSuccessful = true //use the hints to autofill sign in forms to reduce the info to be filled.
data.apply { val credential: Credential = data.getParcelableExtra(Credential.EXTRA_KEY)
presenter.authenticateWithOauth( text_username_or_email.setText(credential.id)
getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN), text_password.setText(credential.password)
getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)
)
}
} }
MULTIPLE_CREDENTIALS_READ -> { REQUEST_CODE_FOR_SAVE_RESOLUTION -> {
val loginCredentials: Credential = showMessage(getString(R.string.message_credentials_saved_successfully))
data.getParcelableExtra(Credential.EXTRA_KEY)
handleCredential(loginCredentials)
} }
NO_CREDENTIALS_EXIST -> { REQUEST_CODE_FOR_CAS -> {
//use the hints to autofill sign in forms to reduce the info to be filled presenter.authenticateWithCas(data.getStringExtra(INTENT_CAS_TOKEN))
val loginCredentials: Credential =
data.getParcelableExtra(Credential.EXTRA_KEY)
val email = loginCredentials.id
val password = loginCredentials.password
text_username_or_email.setText(email)
text_password.setText(password)
} }
SAVE_CREDENTIALS -> Toast.makeText( REQUEST_CODE_FOR_OAUTH -> {
context, presenter.authenticateWithOauth(
getString(R.string.message_credentials_saved_successfully), data.getStringExtra(INTENT_OAUTH_CREDENTIAL_TOKEN),
Toast.LENGTH_SHORT data.getStringExtra(INTENT_OAUTH_CREDENTIAL_SECRET)
).show() )
}
}
}
//cancel button pressed by the user in case of reading from smart lock
else if (resultCode == Activity.RESULT_CANCELED && requestCode == REQUEST_CODE_FOR_OAUTH) {
Timber.d("Returned from oauth")
}
}
override fun onDestroy() {
super.onDestroy()
googleApiClient.let {
activity?.let { it1 -> it.stopAutoManage(it1) }
it.disconnect()
}
}
private fun buildGoogleApiClient() {
googleApiClient = GoogleApiClient.Builder(context!!)
.enableAutoManage(activity as FragmentActivity, {
Timber.e("ERROR: Connection to client failed")
})
.addConnectionCallbacks(this)
.addApi(Auth.CREDENTIALS_API)
.build()
}
override fun onStart() {
super.onStart()
if (!isOauthSuccessful) {
requestCredentials()
}
}
private fun requestCredentials() {
val request: CredentialRequest = CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.build()
Auth.CredentialsApi.request(googleApiClient, request)
.setResultCallback { credentialRequestResult ->
val status = credentialRequestResult.status
when {
status.isSuccess -> handleCredential(credentialRequestResult.credential)
(status.statusCode == CommonStatusCodes.RESOLUTION_REQUIRED) -> resolveResult(
status,
MULTIPLE_CREDENTIALS_READ
)
(status.statusCode == CommonStatusCodes.SIGN_IN_REQUIRED) -> {
val hintRequest: HintRequest = HintRequest.Builder()
.setHintPickerConfig(
CredentialPickerConfig.Builder()
.setShowCancelButton(true)
.build()
)
.setEmailAddressIdentifierSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE)
.build()
val intent: PendingIntent =
Auth.CredentialsApi.getHintPickerIntent(googleApiClient, hintRequest)
try {
startIntentSenderForResult(
intent.intentSender,
NO_CREDENTIALS_EXIST,
null,
0,
0,
0,
null
)
} catch (e: IntentSender.SendIntentException) {
Timber.e("ERROR: Could not start hint picker Intent")
}
} }
else -> Timber.d("ERROR: nothing happening")
} }
} }
}
private fun handleCredential(loginCredentials: Credential) {
if (loginCredentials.accountType == null) {
presenter.authenticateWithUserAndPassword(
loginCredentials.id,
loginCredentials.password.toString()
)
} }
} }
private fun resolveResult(status: Status, requestCode: Int) { override fun onResume() {
try { super.onResume()
status.startResolutionForResult(activity, requestCode) image_key.setOnClickListener {
} catch (e: IntentSender.SendIntentException) { requestStoredCredentials()
Timber.e("Failed to send Credentials intent") image_key.isVisible = false
}
}
override fun saveSmartLockCredentials(loginCredential: Credential?) {
credentialsToBeSaved = loginCredential
if (credentialsToBeSaved == null) {
return
}
activity?.let {
Auth.CredentialsApi.save(googleApiClient, credentialsToBeSaved).setResultCallback(
object : ResolvingResultCallbacks<Status>(it, SAVE_CREDENTIALS) {
override fun onSuccess(status: Status) {
Timber.d("credentials save:SUCCESS:$status")
credentialsToBeSaved = null
}
override fun onUnresolvableFailure(status: Status) {
Timber.e("credentials save:FAILURE:$status")
credentialsToBeSaved = null
}
})
} }
} }
...@@ -286,6 +148,24 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks ...@@ -286,6 +148,24 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
} }
} }
private fun requestStoredCredentials() {
activity?.let {
SmartLockHelper.requestStoredCredentials(credentialsClient, it)?.let {
onCredentialRetrieved(it)
}
}
}
private fun onCredentialRetrieved(credential: Credential) {
presenter.authenticateWithUserAndPassword(credential.id, credential.password.toString())
}
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.let {
SmartLockHelper.save(credentialsClient, it, id, password)
}
}
override fun showLoading() { override fun showLoading() {
ui { ui {
view_loading.isVisible = true view_loading.isVisible = true
...@@ -318,6 +198,7 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks ...@@ -318,6 +198,7 @@ class LoginFragment : Fragment(), LoginView, GoogleApiClient.ConnectionCallbacks
ui { ui {
text_username_or_email.isVisible = true text_username_or_email.isVisible = true
text_password.isVisible = true text_password.isVisible = true
image_key.isVisible = true
} }
} }
......
...@@ -15,7 +15,6 @@ import chat.rocket.core.internal.rest.login ...@@ -15,7 +15,6 @@ import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.signup import chat.rocket.core.internal.rest.signup
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import com.google.android.gms.auth.api.credentials.Credential
import javax.inject.Inject import javax.inject.Inject
class SignupPresenter @Inject constructor( class SignupPresenter @Inject constructor(
...@@ -64,10 +63,7 @@ class SignupPresenter @Inject constructor( ...@@ -64,10 +63,7 @@ class SignupPresenter @Inject constructor(
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
saveAccount(me) saveAccount(me)
registerPushToken() registerPushToken()
val loginCredentials = Credential.Builder(email) view.saveSmartLockCredentials(username, password)
.setPassword(password)
.build()
view.saveSmartLockCredentials(loginCredentials)
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { exception.message?.let {
......
...@@ -27,7 +27,7 @@ interface SignupView : LoadingView, MessageView { ...@@ -27,7 +27,7 @@ interface SignupView : LoadingView, MessageView {
fun alertBlankEmail() fun alertBlankEmail()
/** /**
* Save credentials via google smart lock * Saves Google Smart Lock credentials.
*/ */
fun saveSmartLockCredentials(loginCredential: Credential) fun saveSmartLockCredentials(id: String, password: String)
} }
\ No newline at end of file
package chat.rocket.android.authentication.signup.ui package chat.rocket.android.authentication.signup.ui
import DrawableHelper import DrawableHelper
import android.app.Activity.RESULT_OK import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
...@@ -11,30 +11,24 @@ import android.view.LayoutInflater ...@@ -11,30 +11,24 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.login.ui.googleApiClient import chat.rocket.android.R.string.message_credentials_saved_successfully
import chat.rocket.android.authentication.signup.presentation.SignupPresenter import chat.rocket.android.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.SmartLockHelper
import chat.rocket.android.helper.TextHelper import chat.rocket.android.helper.TextHelper
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import com.google.android.gms.auth.api.Auth import com.google.android.gms.auth.api.credentials.Credentials
import com.google.android.gms.auth.api.credentials.Credential
import com.google.android.gms.common.api.ResolvingResultCallbacks
import com.google.android.gms.common.api.Status
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.* import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal const val SAVE_CREDENTIALS = 1 internal const val SAVE_CREDENTIALS = 1
class SignupFragment : Fragment(), SignupView { class SignupFragment : Fragment(), SignupView {
@Inject @Inject
lateinit var presenter: SignupPresenter lateinit var presenter: SignupPresenter
private lateinit var credentialsToBeSaved: Credential
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(relative_layout.rootView)) { if (KeyboardHelper.isSoftKeyboardShown(relative_layout.rootView)) {
bottom_container.setVisible(false) bottom_container.setVisible(false)
...@@ -120,44 +114,16 @@ class SignupFragment : Fragment(), SignupView { ...@@ -120,44 +114,16 @@ class SignupFragment : Fragment(), SignupView {
} }
} }
override fun saveSmartLockCredentials(loginCredential: Credential) {
credentialsToBeSaved = loginCredential
googleApiClient.let {
if (it.isConnected) {
saveCredentials()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == SAVE_CREDENTIALS) { if (resultCode == Activity.RESULT_OK) {
if (resultCode == RESULT_OK) { if (data != null) {
Toast.makeText( if (requestCode == SAVE_CREDENTIALS) {
context, showMessage(getString(message_credentials_saved_successfully))
getString(R.string.message_credentials_saved_successfully), }
Toast.LENGTH_SHORT
).show()
} else {
Timber.e("ERROR: Cancelled by user")
} }
} }
} }
private fun saveCredentials() {
activity?.let {
Auth.CredentialsApi.save(googleApiClient, credentialsToBeSaved).setResultCallback(
object : ResolvingResultCallbacks<Status>(it, SAVE_CREDENTIALS) {
override fun onSuccess(status: Status) {
Timber.d("save:SUCCESS:$status")
}
override fun onUnresolvableFailure(status: Status) {
Timber.e("save:FAILURE:$status")
}
})
}
}
override fun showLoading() { override fun showLoading() {
ui { ui {
enableUserInput(false) enableUserInput(false)
...@@ -188,6 +154,12 @@ class SignupFragment : Fragment(), SignupView { ...@@ -188,6 +154,12 @@ class SignupFragment : Fragment(), SignupView {
showMessage(getString(R.string.msg_generic_error)) showMessage(getString(R.string.msg_generic_error))
} }
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.let {
SmartLockHelper.save(Credentials.getClient(it), it, id, password)
}
}
private fun tintEditTextDrawableStart() { private fun tintEditTextDrawableStart() {
ui { ui {
val personDrawable = val personDrawable =
......
package chat.rocket.android.helper
import android.app.Activity
import android.content.IntentSender
import android.support.v4.app.FragmentActivity
import com.google.android.gms.auth.api.credentials.*
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.ResolvableApiException
import timber.log.Timber
const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1
const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2
const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3
/**
* This class handles some cases of Google Smart Lock for passwords like the request to retrieve
* credentials, to retrieve sign-in hints and to store the credentials.
*
* See https://developers.google.com/identity/smartlock-passwords/android/overview for futher
* information.
*/
object SmartLockHelper {
/**
* Requests for stored Google Smart Lock credentials.
* Note that in case of exception it will try to start a sign in
* ([REQUEST_CODE_FOR_SIGN_IN_REQUIRED]) or "multiple account"
* ([REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION]) resolution.
*
* @param credentialsClient The credential client.
* @param activity The activity.
* @return null or the [Credential] result.
*/
fun requestStoredCredentials(
credentialsClient: CredentialsClient,
activity: Activity
): Credential? {
var credential: Credential? = null
val credentialRequest = CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.build()
credentialsClient.request(credentialRequest)
.addOnCompleteListener {
when {
it.isSuccessful -> {
credential = it.result.credential
}
it.exception is ResolvableApiException -> {
val resolvableApiException = (it.exception as ResolvableApiException)
if (resolvableApiException.statusCode == CommonStatusCodes.SIGN_IN_REQUIRED) {
provideSignInHint(credentialsClient, activity)
} else {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one. This requires showing UI to
// resolve the read request.
resolveResult(
resolvableApiException,
REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION,
activity
)
}
}
}
}
return credential
}
/**
* Saves a user credential to Google Smart Lock.
* Note that in case of exception it will try to start a save resolution,
* so the activity/fragment should expected for a request code
* ([REQUEST_CODE_FOR_SAVE_RESOLUTION]) on onActivityResult call.
*
* @param credentialsClient The credential client.
* @param activity The activity.
* @param id The user id credential.
* @param password The user password credential.
*/
fun save(
credentialsClient: CredentialsClient,
activity: FragmentActivity,
id: String,
password: String
) {
val credential = Credential.Builder(id)
.setPassword(password)
.build()
credentialsClient.save(credential)
.addOnCompleteListener {
val exception = it.exception
if (exception is ResolvableApiException) {
// Try to resolve the save request. This will prompt the user if
// the credential is new.
try {
exception.startResolutionForResult(
activity,
REQUEST_CODE_FOR_SAVE_RESOLUTION
)
} catch (e: IntentSender.SendIntentException) {
Timber.e("Failed to send resolution. Exception is: $e")
}
}
}
}
private fun provideSignInHint(credentialsClient: CredentialsClient, activity: Activity) {
val hintRequest = HintRequest.Builder()
.setHintPickerConfig(
CredentialPickerConfig.Builder()
.setShowCancelButton(true)
.build()
)
.setEmailAddressIdentifierSupported(true)
.build()
try {
val intent = credentialsClient.getHintPickerIntent(hintRequest)
activity.startIntentSenderForResult(
intent.intentSender,
REQUEST_CODE_FOR_SIGN_IN_REQUIRED,
null,
0,
0,
0,
null
)
} catch (e: IntentSender.SendIntentException) {
Timber.e("Could not start hint picker Intent. Exception is: $e")
}
}
private fun resolveResult(
exception: ResolvableApiException,
requestCode: Int,
activity: Activity
) {
try {
exception.startResolutionForResult(activity, requestCode)
} catch (e: IntentSender.SendIntentException) {
Timber.e("Failed to send resolution. Exception is: $e")
}
}
}
\ No newline at end of file
...@@ -32,6 +32,18 @@ ...@@ -32,6 +32,18 @@
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_headline" /> app:layout_constraintTop_toBottomOf="@+id/text_headline" />
<ImageView
android:id="@+id/image_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_vpn_key_black_24dp"
android:tint="@color/colorDrawableTintGrey"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/text_username_or_email"
app:layout_constraintEnd_toEndOf="@+id/text_username_or_email"
app:layout_constraintTop_toTopOf="@+id/text_username_or_email" />
<EditText <EditText
android:id="@+id/text_password" android:id="@+id/text_password"
style="@style/Authentication.EditText" style="@style/Authentication.EditText"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment