CredentialProviderPlayServicesImpl.kt
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.credentials.playservices
import android.app.Activity
import android.content.Context
import android.os.CancellationSignal
import android.util.Log
import androidx.credentials.ClearCredentialStateRequest
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import androidx.credentials.CreateCredentialRequest
import androidx.credentials.CreateCredentialResponse
import androidx.credentials.CreatePasswordRequest
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.CredentialManagerCallback
import androidx.credentials.CredentialProvider
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import androidx.credentials.exceptions.ClearCredentialException
import androidx.credentials.exceptions.ClearCredentialUnknownException
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.GetCredentialException
import androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController
import androidx.credentials.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController
import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.CredentialProviderCreatePublicKeyCredentialController
import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import java.util.concurrent.Executor
/**
* Entry point of all credential manager requests to the play-services-auth
* module.
*
* @hide
*/
@Suppress("deprecation")
class CredentialProviderPlayServicesImpl(private val context: Context) : CredentialProvider {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@set:RestrictTo(RestrictTo.Scope.TESTS)
var googleApiAvailability = GoogleApiAvailability.getInstance()
override fun onGetCredential(
request: GetCredentialRequest,
activity: Activity,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>
) {
if (cancellationReviewer(cancellationSignal)) { return }
CredentialProviderBeginSignInController(activity).invokePlayServices(
request, callback, executor, cancellationSignal)
}
@SuppressWarnings("deprecated")
override fun onCreateCredential(
request: CreateCredentialRequest,
activity: Activity,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>
) {
if (cancellationReviewer(cancellationSignal)) { return }
when (request) {
is CreatePasswordRequest -> {
CredentialProviderCreatePasswordController.getInstance(
activity).invokePlayServices(
request,
callback,
executor,
cancellationSignal)
}
is CreatePublicKeyCredentialRequest -> {
CredentialProviderCreatePublicKeyCredentialController.getInstance(
activity).invokePlayServices(
request,
callback,
executor,
cancellationSignal)
}
else -> {
throw UnsupportedOperationException(
"Create Credential request is unsupported, not password or " +
"publickeycredential")
}
}
}
override fun isAvailableOnDevice(): Boolean {
val resultCode = isGooglePlayServicesAvailable(context)
return resultCode == ConnectionResult.SUCCESS
}
// https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult
// TODO(Most codes indicate failure, but two indicate retry-ability - look into handling.)
private fun isGooglePlayServicesAvailable(context: Context): Int {
return googleApiAvailability.isGooglePlayServicesAvailable(context)
}
override fun onClearCredential(
request: ClearCredentialStateRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<Void?, ClearCredentialException>
) {
if (cancellationReviewer(cancellationSignal)) {
return
}
Identity.getSignInClient(context)
.signOut()
.addOnSuccessListener {
var isCanceled = false
cancellationSignal?.let {
isCanceled = cancellationSignal.isCanceled
}
if (!isCanceled) {
Log.i(TAG, "During clear credential, signed out successfully!")
executor.execute { callback.onResult(null) }
}
}
.addOnFailureListener { e ->
run {
var isCanceled = false
cancellationSignal?.let {
isCanceled = cancellationSignal.isCanceled
}
if (!isCanceled) {
Log.w(TAG, "During clear credential sign out failed with $e")
executor.execute {
callback.onError(ClearCredentialUnknownException(e.message))
}
}
}
}
}
companion object {
private val TAG = CredentialProviderPlayServicesImpl::class.java.name
internal fun cancellationReviewer(
cancellationSignal: CancellationSignal?
): Boolean {
if (cancellationSignal != null) {
if (cancellationSignal.isCanceled) {
Log.i(TAG, "the flow has been canceled")
// TODO("See if there's a better way to message pass to avoid if statements")
// TODO("And to use a single listener instead")
return true
}
} else {
Log.i(TAG, "No cancellationSignal found")
}
return false
}
}
}