Merge "Initial Design for Play Auth Module Connection" into androidx-main
diff --git a/credentials/credentials-play-services-auth/OWNERS b/credentials/credentials-play-services-auth/OWNERS
new file mode 100644
index 0000000..7b0e47f
--- /dev/null
+++ b/credentials/credentials-play-services-auth/OWNERS
@@ -0,0 +1,8 @@
+# Bug Component: 1218609
+sgjerry@google.com
+helenqin@google.com
+reemabajwa@google.com
+duqinmei@google.com
+beccahughes@google.com
+leecam@google.com
+akaphle@google.com
diff --git a/credentials/credentials-play-services-auth/build.gradle b/credentials/credentials-play-services-auth/build.gradle
index 725338d..430b672 100644
--- a/credentials/credentials-play-services-auth/build.gradle
+++ b/credentials/credentials-play-services-auth/build.gradle
@@ -26,7 +26,22 @@
     api(libs.kotlinStdlib)
     api project(":credentials:credentials")
 
-    // Add dependencies here
+    implementation project(path: ':activity:activity')
+    // Closed source dependencies
+    implementation(libs.playServicesAuth) {
+        exclude group: "androidx.loader"
+        exclude group: "androidx.fragment"
+    }
+    implementation(libs.playServicesFido)
+
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.truth)
+    androidTestImplementation(project(":internal-testutils-truth"))
+    androidTestImplementation(libs.kotlinCoroutinesAndroid)
 }
 
 android {
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
index 59ad679..6e59651 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
@@ -18,12 +18,16 @@
 
 import android.app.Activity
 import android.os.CancellationSignal
+import android.util.Log
 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.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController
 import java.util.concurrent.Executor
 
 /**
@@ -32,6 +36,7 @@
  *
  * @hide
  */
+@Suppress("deprecation")
 class CredentialProviderPlayServicesImpl : CredentialProvider {
     override fun onGetCredential(
         request: GetCredentialRequest,
@@ -40,6 +45,10 @@
         executor: Executor,
         callback: CredentialManagerCallback<GetCredentialResponse>
     ) {
+        if (cancellationSignal != null) {
+            Log.i(TAG, "onCreateCredential cancellationSignal not used")
+            TODO("Use Cancel Operations Properly")
+        }
         TODO("Not yet implemented")
     }
 
@@ -50,10 +59,31 @@
         executor: Executor,
         callback: CredentialManagerCallback<CreateCredentialResponse>
     ) {
-        TODO("Not yet implemented")
+        if (cancellationSignal != null) {
+            Log.i(TAG, "onCreateCredential cancellationSignal not used")
+            TODO("Use Cancel Operations Properly")
+        }
+        val fragmentManager: android.app.FragmentManager = activity!!.fragmentManager
+        // TODO("Manage Fragment Lifecycle and Fragment Manager Properly")
+        if (request is CreatePasswordRequest) {
+            CredentialProviderCreatePasswordController.getInstance(
+                fragmentManager).invokePlayServices(
+                request,
+                callback,
+                executor)
+        } else if (request is CreatePublicKeyCredentialRequest) {
+            TODO("Not yet implemented")
+        } else {
+            throw UnsupportedOperationException(
+                "Unsupported request; not password or publickeycredential")
+        }
     }
 
     override fun isAvailableOnDevice(): Boolean {
         TODO("Not yet implemented")
     }
+
+    companion object {
+        private val TAG = CredentialProviderPlayServicesImpl::class.java.name
+    }
 }
\ No newline at end of file
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
new file mode 100644
index 0000000..994429a
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.controllers.BeginSignIn
+
+import android.content.Intent
+import android.util.Log
+import androidx.credentials.CredentialManagerCallback
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.GetCredentialResponse
+import androidx.credentials.playservices.controllers.CredentialProviderController
+import com.google.android.gms.auth.api.identity.BeginSignInRequest
+import com.google.android.gms.auth.api.identity.SignInCredential
+import java.util.concurrent.Executor
+
+/**
+ * A controller to handle the BeginSignIn flow with play services.
+ *
+ * @hide
+ */
+@Suppress("deprecation")
+class CredentialProviderBeginSignInController : CredentialProviderController<
+    GetCredentialRequest,
+    BeginSignInRequest,
+    SignInCredential,
+    GetCredentialResponse>() {
+
+    /**
+     * The callback object state, used in the protected handleResponse method.
+     */
+    private lateinit var callback: CredentialManagerCallback<GetCredentialResponse>
+    /**
+     * The callback requires an executor to invoke it.
+     */
+    private lateinit var executor: Executor
+
+    override fun invokePlayServices(
+        request: GetCredentialRequest,
+        callback: CredentialManagerCallback<GetCredentialResponse>,
+        executor: Executor
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        handleResponse(requestCode, resultCode, data)
+    }
+
+    private fun handleResponse(uniqueRequestCode: Int, resultCode: Int, data: Intent?) {
+        Log.i(TAG, "$uniqueRequestCode $resultCode $data")
+        TODO("Not yet implemented")
+    }
+
+    override fun convertToPlayServices(request: GetCredentialRequest): BeginSignInRequest {
+        TODO("Not yet implemented")
+    }
+
+    override fun convertToCredentialProvider(response: SignInCredential): GetCredentialResponse {
+        TODO("Not yet implemented")
+    }
+
+    companion object {
+        private val TAG = CredentialProviderBeginSignInController::class.java.name
+        private const val REQUEST_CODE_BEGIN_SIGN_IN: Int = 1
+        // TODO("Ensure this works with the lifecycle")
+
+        /**
+         * This finds a past version of the BeginSignInController if it exists, otherwise
+         * it generates a new instance.
+         *
+         * @param fragmentManager a fragment manager pulled from an android activity
+         * @return a credential provider controller for a specific credential request
+         */
+        @JvmStatic
+        fun getInstance(fragmentManager: android.app.FragmentManager):
+            CredentialProviderBeginSignInController {
+            var controller = findPastController(REQUEST_CODE_BEGIN_SIGN_IN, fragmentManager)
+            if (controller == null) {
+                controller = CredentialProviderBeginSignInController()
+                fragmentManager.beginTransaction().add(controller,
+                    REQUEST_CODE_BEGIN_SIGN_IN.toString())
+                    .commitAllowingStateLoss()
+                fragmentManager.executePendingTransactions()
+            }
+            return controller
+        }
+
+        internal fun findPastController(
+            requestCode: Int,
+            fragmentManager: android.app.FragmentManager
+        ): CredentialProviderBeginSignInController? {
+            try {
+                return fragmentManager.findFragmentByTag(requestCode.toString())
+                    as CredentialProviderBeginSignInController?
+            } catch (e: Exception) {
+                Log.i(TAG, "Old fragment found of different type - replacement required")
+                // TODO("Ensure this is well tested for fragment issues")
+                return null
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
new file mode 100644
index 0000000..7015a9a
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.controllers.CreatePassword
+
+import android.content.Intent
+import android.util.Log
+import androidx.credentials.CreateCredentialResponse
+import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.CredentialManagerCallback
+import androidx.credentials.playservices.controllers.CredentialProviderController
+import com.google.android.gms.auth.api.identity.SavePasswordRequest
+import java.util.concurrent.Executor
+
+/**
+ * A controller to handle the CreatePassword flow with play services.
+ *
+ * @hide
+ */
+@Suppress("deprecation")
+class CredentialProviderCreatePasswordController : CredentialProviderController<
+        CreatePasswordRequest,
+        SavePasswordRequest,
+        Intent,
+        CreateCredentialResponse>() {
+
+    /**
+     * The callback object state, used in the protected handleResponse method.
+     */
+    private lateinit var callback: CredentialManagerCallback<CreateCredentialResponse>
+
+    /**
+     * The callback requires an executor to invoke it.
+     */
+    private lateinit var executor: Executor
+
+    override fun invokePlayServices(
+        request: CreatePasswordRequest,
+        callback: CredentialManagerCallback<CreateCredentialResponse>,
+        executor: Executor
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        handleResponse(requestCode, resultCode, data)
+    }
+
+    private fun handleResponse(uniqueRequestCode: Int, resultCode: Int, data: Intent?) {
+        Log.i(TAG, "$uniqueRequestCode $resultCode $data")
+        TODO("Not yet implemented")
+    }
+
+    override fun convertToPlayServices(request: CreatePasswordRequest): SavePasswordRequest {
+        TODO("Not yet implemented")
+    }
+
+    override fun convertToCredentialProvider(response: Intent): CreateCredentialResponse {
+        TODO("Not yet implemented")
+    }
+
+    companion object {
+        private val TAG = CredentialProviderCreatePasswordController::class.java.name
+        private const val REQUEST_CODE_GIS_SAVE_PASSWORD: Int = 1
+        // TODO("Ensure this works with the lifecycle")
+
+        /**
+         * This finds a past version of the
+         * [CredentialProviderCreatePasswordController] if it exists, otherwise
+         * it generates a new instance.
+         *
+         * @param fragmentManager a fragment manager pulled from an android activity
+         * @return a credential provider controller for CreatePublicKeyCredential
+         */
+        @JvmStatic
+        fun getInstance(fragmentManager: android.app.FragmentManager):
+            CredentialProviderCreatePasswordController {
+            var controller = findPastController(REQUEST_CODE_GIS_SAVE_PASSWORD, fragmentManager)
+            if (controller == null) {
+                controller = CredentialProviderCreatePasswordController()
+                fragmentManager.beginTransaction().add(controller,
+                    REQUEST_CODE_GIS_SAVE_PASSWORD.toString())
+                    .commitAllowingStateLoss()
+                fragmentManager.executePendingTransactions()
+            }
+            return controller
+        }
+
+        internal fun findPastController(
+            requestCode: Int,
+            fragmentManager: android.app.FragmentManager
+        ): CredentialProviderCreatePasswordController? {
+            try {
+                return fragmentManager.findFragmentByTag(requestCode.toString())
+                    as CredentialProviderCreatePasswordController
+            } catch (e: Exception) {
+                Log.i(TAG, "Old fragment found of different type - replacement required")
+                // TODO("Ensure this is well tested for fragment issues")
+                return null
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
new file mode 100644
index 0000000..d6e8763
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.controllers.CreatePublicKeyCredential
+
+import android.content.Intent
+import android.util.Log
+import androidx.credentials.CreateCredentialResponse
+import androidx.credentials.CreatePublicKeyCredentialRequest
+import androidx.credentials.CredentialManagerCallback
+import androidx.credentials.playservices.controllers.CredentialProviderController
+import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential
+import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions
+import java.util.concurrent.Executor
+
+/**
+ * A controller to handle the CreatePublicKeyCredential flow with play services.
+ *
+ * @hide
+ */
+@Suppress("deprecation")
+class CredentialProviderCreatePublicKeyCredentialController :
+        CredentialProviderController<
+            CreatePublicKeyCredentialRequest,
+            PublicKeyCredentialCreationOptions,
+            PublicKeyCredential,
+            CreateCredentialResponse>() {
+
+    /**
+     * The callback object state, used in the protected handleResponse method.
+     */
+    private lateinit var callback: CredentialManagerCallback<CreateCredentialResponse>
+
+    /**
+     * The callback requires an executor to invoke it.
+     */
+    private lateinit var executor: Executor
+
+    override fun invokePlayServices(
+        request: CreatePublicKeyCredentialRequest,
+        callback: CredentialManagerCallback<CreateCredentialResponse>,
+        executor: Executor
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        handleResponse(requestCode, resultCode, data)
+    }
+
+    private fun handleResponse(uniqueRequestCode: Int, resultCode: Int, data: Intent?) {
+        Log.i(TAG, "$uniqueRequestCode $resultCode $data")
+        TODO("Not yet implemented")
+    }
+
+    override fun convertToPlayServices(request: CreatePublicKeyCredentialRequest):
+        PublicKeyCredentialCreationOptions {
+        TODO("Not yet implemented")
+    }
+
+    override fun convertToCredentialProvider(response: PublicKeyCredential):
+        CreateCredentialResponse {
+        TODO("Not yet implemented")
+    }
+
+    companion object {
+        private val TAG = CredentialProviderCreatePublicKeyCredentialController::class.java.name
+        private const val REQUEST_CODE_GIS_SAVE_PUBLIC_KEY_CREDENTIAL: Int = 1
+        // TODO("Ensure this works with the lifecycle")
+
+        /**
+         * This finds a past version of the
+         * [CredentialProviderCreatePublicKeyCredentialController] if it exists, otherwise
+         * it generates a new instance.
+         *
+         * @param fragmentManager a fragment manager pulled from an android activity
+         * @return a credential provider controller for CreatePublicKeyCredential
+         */
+        @JvmStatic
+        fun getInstance(fragmentManager: android.app.FragmentManager):
+            CredentialProviderCreatePublicKeyCredentialController {
+            var controller = findPastController(
+                REQUEST_CODE_GIS_SAVE_PUBLIC_KEY_CREDENTIAL,
+                fragmentManager)
+            if (controller == null) {
+                controller = CredentialProviderCreatePublicKeyCredentialController()
+                fragmentManager.beginTransaction().add(controller,
+                    REQUEST_CODE_GIS_SAVE_PUBLIC_KEY_CREDENTIAL.toString())
+                    .commitAllowingStateLoss()
+                fragmentManager.executePendingTransactions()
+            }
+            return controller
+        }
+
+        internal fun findPastController(
+            requestCode: Int,
+            fragmentManager: android.app.FragmentManager
+        ): CredentialProviderCreatePublicKeyCredentialController? {
+            try {
+                return fragmentManager.findFragmentByTag(requestCode.toString())
+                    as CredentialProviderCreatePublicKeyCredentialController
+            } catch (e: Exception) {
+                Log.i(TAG, "Old fragment found of different type - replacement required")
+                // TODO("Ensure this is well tested for fragment issues")
+                return null
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderController.kt
new file mode 100644
index 0000000..12ab060
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderController.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.controllers
+
+import androidx.credentials.CredentialManagerCallback
+import java.util.concurrent.Executor
+
+/**
+ * Extensible abstract class for credential controllers. Please implement this class per every
+ * request/response credential type. Unique logic is left to the use case of the implementation.
+ * If you are building your own version as an OEM, the below can be mimicked to your own
+ * credential provider equivalent and whatever internal service you invoke.
+ *
+ * @param T1 the credential request type from credential manager
+ * @param T2 the credential request type converted to play services
+ * @param R2 the credential response type from play services
+ * @param R1 the credential response type converted back to that used by credential manager
+ *
+ * @hide
+ */
+@Suppress("deprecation")
+abstract class CredentialProviderController<T1 : Any, T2 : Any, R2 : Any, R1 : Any> : android.app
+        .Fragment() {
+
+    /**
+     * Invokes the flow that starts retrieving credential data. In this use case, we invoke
+     * play service modules.
+     *
+     * @param request a credential provider request
+     * @param callback a credential manager callback with a credential provider response
+     * @param executor to be used in any multi-threaded operation calls, such as listenable futures
+     */
+    abstract fun invokePlayServices(
+        request: T1,
+        callback: CredentialManagerCallback<R1>,
+        executor: Executor
+    )
+
+    /**
+     * Allows converting from a credential provider request to a play service request.
+     *
+     * @param request a credential provider request
+     * @return a play service request
+     */
+    protected abstract fun convertToPlayServices(request: T1): T2
+
+    /**
+     * Allows converting from a play service response to a credential provider response.
+     *
+     * @param response a play service response
+     * @return a credential provider response
+     */
+    protected abstract fun convertToCredentialProvider(response: R2): R1
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProvider.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProvider.kt
index c0c51a0..0be5f0c 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProvider.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProvider.kt
@@ -55,7 +55,7 @@
      */
     fun onGetCredential(
         request: GetCredentialRequest,
-        activity: Activity?,
+        activity: Activity?, // TODO("Update on optionality")
         cancellationSignal: CancellationSignal?,
         executor: Executor,
         callback: CredentialManagerCallback<GetCredentialResponse>,
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 54cd813..60da42a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -193,8 +193,11 @@
 okhttpMockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version = "3.14.7" }
 okio = { module = "com.squareup.okio:okio", version = "3.1.0" }
 playFeatureDelivery = { module = "com.google.android.play:feature-delivery", version = "2.0.1" }
+playCore = { module = "com.google.android.play:core", version = "1.10.3" }
+playServicesAuth = {module = "com.google.android.gms:play-services-auth", version = "20.3.0"}
 playServicesBase = { module = "com.google.android.gms:play-services-base", version = "17.0.0" }
 playServicesBasement = { module = "com.google.android.gms:play-services-basement", version = "17.0.0" }
+playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "19.0.0-beta"}
 playServicesWearable = { module = "com.google.android.gms:play-services-wearable", version = "17.1.0" }
 paparazzi = { module = "app.cash.paparazzi:paparazzi", version.ref = "paparazzi" }
 paparazziNativeJvm = { module = "app.cash.paparazzi:layoutlib-native-jdk11", version.ref = "paparazziNative" }