diff options
author | Becca Hughes <beccahughes@google.com> | 2023-07-05 19:46:37 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2023-07-05 19:46:37 +0000 |
commit | 08006ea049db1221bbe3da19b08b8bf0f65a76af (patch) | |
tree | 1e3b8e746bb08495df17ab5de7f80802238b8985 | |
parent | 60ba3de45074eabd8a32fc7dff2269cbd2bea983 (diff) | |
parent | f0d0958c3fb518e5a4b48db0bf4c1db0953c7430 (diff) | |
download | support-08006ea049db1221bbe3da19b08b8bf0f65a76af.tar.gz |
Merge "Use fragments internally for play services" into androidx-main
14 files changed, 948 insertions, 437 deletions
diff --git a/credentials/credentials-play-services-auth/src/androidTest/AndroidManifest.xml b/credentials/credentials-play-services-auth/src/androidTest/AndroidManifest.xml index d7b0e91c4a1..73badf78bfb 100644 --- a/credentials/credentials-play-services-auth/src/androidTest/AndroidManifest.xml +++ b/credentials/credentials-play-services-auth/src/androidTest/AndroidManifest.xml @@ -21,5 +21,9 @@ android:name="androidx.credentials.playservices.TestCredentialsActivity" android:exported="false" /> + <activity + android:name="androidx.credentials.playservices.TestCredentialsFragmentActivity" + android:exported="false" + /> </application> </manifest>
\ No newline at end of file diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestCredentialsFragmentActivity.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestCredentialsFragmentActivity.kt new file mode 100644 index 00000000000..f7ad106061f --- /dev/null +++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestCredentialsFragmentActivity.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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.os.Build +import androidx.annotation.RequiresApi +import androidx.fragment.app.FragmentActivity + +/** + * This is a test activity used by the Robolectric Activity Scenario tests. It acts as a calling + * activity in our test cases. This activity uses fragments. + */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) +class TestCredentialsFragmentActivity : FragmentActivity() diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java index e4cdddae662..acbfe57aee6 100644 --- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java +++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java @@ -20,12 +20,14 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import android.app.Activity; +import android.os.Build; + import androidx.credentials.GetCredentialRequest; import androidx.credentials.GetPasswordOption; import androidx.credentials.playservices.TestCredentialsActivity; import androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController; import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.google.android.gms.auth.api.identity.BeginSignInRequest; @@ -33,19 +35,56 @@ import com.google.android.libraries.identity.googleid.GetGoogleIdOption; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import java.util.HashSet; import java.util.List; -@RunWith(AndroidJUnit4.class) +@RunWith(Parameterized.class) @SmallTest @SuppressWarnings("deprecation") public class CredentialProviderBeginSignInControllerJavaTest { + + private final boolean mUseFragmentActivity; + + @Parameterized.Parameters + public static Object[] data() { + return new Object[] {true, false}; + } + + public CredentialProviderBeginSignInControllerJavaTest(final boolean useFragmentActivity) + throws Throwable { + mUseFragmentActivity = useFragmentActivity; + } + + interface TestActivityListener { + void onActivity(Activity a); + } + + private void launchTestActivity(TestActivityListener listener) { + if (mUseFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + ActivityScenario<androidx.credentials.playservices.TestCredentialsFragmentActivity> + activityScenario = + ActivityScenario.launch( + androidx.credentials.playservices + .TestCredentialsFragmentActivity.class); + activityScenario.onActivity( + activity -> { + listener.onActivity((Activity) activity); + }); + } else { + ActivityScenario<TestCredentialsActivity> activityScenario = + ActivityScenario.launch(TestCredentialsActivity.class); + activityScenario.onActivity( + activity -> { + listener.onActivity((Activity) activity); + }); + } + } + @Test public void convertRequestToPlayServices_setPasswordOptionRequestAndFalseAutoSelect_success() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity( + launchTestActivity( activity -> { BeginSignInRequest actualResponse = CredentialProviderBeginSignInController.getInstance(activity) @@ -60,9 +99,7 @@ public class CredentialProviderBeginSignInControllerJavaTest { @Test public void convertRequestToPlayServices_setPasswordOptionRequestAndTrueAutoSelect_success() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity( + launchTestActivity( activity -> { BeginSignInRequest actualResponse = CredentialProviderBeginSignInController.getInstance(activity) @@ -79,9 +116,7 @@ public class CredentialProviderBeginSignInControllerJavaTest { @Test public void convertRequestToPlayServices_nullRequest_throws() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity( + launchTestActivity( activity -> { assertThrows( "null get credential request must throw exception", @@ -94,9 +129,7 @@ public class CredentialProviderBeginSignInControllerJavaTest { @Test public void convertResponseToCredentialManager_nullRequest_throws() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity( + launchTestActivity( activity -> { assertThrows( "null sign in credential response must throw exception", @@ -109,9 +142,6 @@ public class CredentialProviderBeginSignInControllerJavaTest { @Test public void convertRequestToPlayServices_setGoogleIdOptionRequestAndTrueAutoSelect_success() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - GetGoogleIdOption option = new GetGoogleIdOption.Builder() .setServerClientId("server_client_id") @@ -122,7 +152,7 @@ public class CredentialProviderBeginSignInControllerJavaTest { .setAutoSelectEnabled(true) .build(); - activityScenario.onActivity( + launchTestActivity( activity -> { BeginSignInRequest actualRequest = CredentialProviderBeginSignInController.getInstance(activity) @@ -151,9 +181,7 @@ public class CredentialProviderBeginSignInControllerJavaTest { @Test public void duplicateGetInstance_shouldBeEqual() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity( + launchTestActivity( activity -> { CredentialProviderBeginSignInController firstInstance = CredentialProviderBeginSignInController.getInstance(activity); diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt index 98ba9e3bdf8..dc5ea88631e 100644 --- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt +++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt @@ -16,32 +16,55 @@ package androidx.credentials.playservices.beginsignin +import android.app.Activity import android.os.Build import androidx.annotation.RequiresApi import androidx.credentials.GetCredentialRequest import androidx.credentials.GetPasswordOption import androidx.credentials.playservices.TestCredentialsActivity +import androidx.credentials.playservices.TestCredentialsFragmentActivity import androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.Companion.getInstance import androidx.test.core.app.ActivityScenario -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith +import org.junit.runners.Parameterized -@RunWith(AndroidJUnit4::class) +@RunWith(Parameterized::class) @SmallTest @Suppress("deprecation") @RequiresApi(api = Build.VERSION_CODES.O) -class CredentialProviderBeginSignInControllerTest { +class CredentialProviderBeginSignInControllerTest(val useFragmentActivity: Boolean) { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun initParameters() = listOf(true, false) + } + + private fun launchTestActivity(callback: (activity: Activity) -> Unit) { + if (useFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + var activityScenario = + ActivityScenario.launch( + androidx.credentials.playservices + .TestCredentialsFragmentActivity::class.java) + activityScenario.onActivity { activity: Activity -> + callback.invoke(activity) + } + } else { + var activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java) + activityScenario.onActivity { activity: Activity -> + callback.invoke(activity) + } + } + } + @Test fun convertRequestToPlayServices_setPasswordOptionRequestAndFalseAutoSelect_success() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> - val actualResponse = getInstance(activity!!) + launchTestActivity { activity: Activity -> + val actualResponse = getInstance(activity) .convertRequestToPlayServices( GetCredentialRequest( listOf( @@ -58,11 +81,8 @@ class CredentialProviderBeginSignInControllerTest { @Test fun convertRequestToPlayServices_setPasswordOptionRequestAndTrueAutoSelect_success() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> - val actualResponse = getInstance(activity!!) + launchTestActivity { activity: Activity -> + val actualResponse = getInstance(activity) .convertRequestToPlayServices( GetCredentialRequest( listOf( @@ -79,10 +99,6 @@ class CredentialProviderBeginSignInControllerTest { @Test fun convertRequestToPlayServices_setGoogleIdOptionRequest_success() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - val option = GetGoogleIdOption.Builder() .setServerClientId("server_client_id") .setNonce("nonce") @@ -92,8 +108,8 @@ class CredentialProviderBeginSignInControllerTest { .setAutoSelectEnabled(true) .build() - activityScenario.onActivity { activity: TestCredentialsActivity? -> - val actualRequest = getInstance(activity!!) + launchTestActivity { activity: Activity -> + val actualRequest = getInstance(activity) .convertRequestToPlayServices( GetCredentialRequest( listOf( @@ -120,12 +136,9 @@ class CredentialProviderBeginSignInControllerTest { @Test fun duplicateGetInstance_shouldBeEqual() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> - val firstInstance = getInstance(activity!!) + val firstInstance = getInstance(activity) val secondInstance = getInstance(activity) assertThat(firstInstance).isEqualTo(secondInstance) } diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java index 00b86bae5b0..f0d5a850d1e 100644 --- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java +++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java @@ -20,6 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import android.app.Activity; +import android.os.Build; import android.os.Bundle; import androidx.credentials.CreateCredentialResponse; @@ -29,7 +31,6 @@ import androidx.credentials.playservices.TestCredentialsActivity; import androidx.credentials.playservices.TestUtils; import androidx.credentials.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController; import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.google.android.gms.auth.api.identity.SignInPassword; @@ -38,17 +39,53 @@ import kotlin.Unit; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; -@RunWith(AndroidJUnit4.class) +@RunWith(Parameterized.class) @SmallTest public class CredentialProviderCreatePasswordControllerJavaTest { + private final boolean mUseFragmentActivity; + + @Parameterized.Parameters + public static Object[] data() { + return new Object[] {true, false}; + } + + public CredentialProviderCreatePasswordControllerJavaTest(final boolean useFragmentActivity) + throws Throwable { + mUseFragmentActivity = useFragmentActivity; + } + + interface TestActivityListener { + void onActivity(Activity a); + } + + private void launchTestActivity(TestActivityListener listener) { + if (mUseFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + ActivityScenario<androidx.credentials.playservices.TestCredentialsFragmentActivity> + activityScenario = + ActivityScenario.launch( + androidx.credentials.playservices + .TestCredentialsFragmentActivity.class); + activityScenario.onActivity( + activity -> { + listener.onActivity((Activity) activity); + }); + } else { + ActivityScenario<TestCredentialsActivity> activityScenario = + ActivityScenario.launch(TestCredentialsActivity.class); + activityScenario.onActivity( + activity -> { + listener.onActivity((Activity) activity); + }); + } + } + @Test public void convertResponseToCredentialManager_unitInput_success() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); String expectedResponseType = new CreatePasswordResponse().getType(); - activityScenario.onActivity( + launchTestActivity( activity -> { CreateCredentialResponse actualResponse = CredentialProviderCreatePasswordController.getInstance(activity) @@ -62,11 +99,9 @@ public class CredentialProviderCreatePasswordControllerJavaTest { @Test public void convertRequestToPlayServices_createPasswordRequest_success() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); String expectedId = "LM"; String expectedPassword = "SodaButton"; - activityScenario.onActivity( + launchTestActivity( activity -> { SignInPassword actualRequest = CredentialProviderCreatePasswordController.getInstance(activity) @@ -83,7 +118,7 @@ public class CredentialProviderCreatePasswordControllerJavaTest { public void convertRequestToPlayServices_nullRequest_throws() { ActivityScenario<TestCredentialsActivity> activityScenario = ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity( + launchTestActivity( activity -> { assertThrows( "null create password request must throw exception", @@ -97,9 +132,7 @@ public class CredentialProviderCreatePasswordControllerJavaTest { @Test public void convertResponseToCredentialManager_nullRequest_throws() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity( + launchTestActivity( activity -> { assertThrows( "null unit response must throw exception", @@ -112,9 +145,7 @@ public class CredentialProviderCreatePasswordControllerJavaTest { @Test public void duplicateGetInstance_shouldBeEqual() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity( + launchTestActivity( activity -> { CredentialProviderCreatePasswordController firstInstance = CredentialProviderCreatePasswordController.getInstance(activity); diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt index 128c9702724..5afb279ac18 100644 --- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt +++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt @@ -16,31 +16,56 @@ package androidx.credentials.playservices.createpassword +import android.app.Activity +import android.os.Build import android.os.Bundle +import androidx.annotation.DoNotInline import androidx.credentials.CreatePasswordRequest import androidx.credentials.CreatePasswordResponse import androidx.credentials.playservices.TestCredentialsActivity import androidx.credentials.playservices.TestUtils.Companion.equals import androidx.credentials.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController.Companion.getInstance import androidx.test.core.app.ActivityScenario -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith +import org.junit.runners.Parameterized -@RunWith(AndroidJUnit4::class) +@RunWith(Parameterized::class) @SmallTest -class CredentialProviderCreatePasswordControllerTest { +class CredentialProviderCreatePasswordControllerTest(val useFragmentActivity: Boolean) { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun initParameters() = listOf(true, false) + } + + @DoNotInline + private fun launchTestActivity(callback: (activity: Activity) -> Unit) { + if (useFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + var activityScenario = + ActivityScenario.launch( + androidx.credentials.playservices + .TestCredentialsFragmentActivity::class.java) + activityScenario.onActivity { activity: Activity -> + callback.invoke(activity) + } + } else { + var activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java) + activityScenario.onActivity { activity: Activity -> + callback.invoke(activity) + } + } + } + @Test fun convertResponseToCredentialManager_unitInput_success() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) val expectedResponseType = CreatePasswordResponse().type - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> - val actualResponse = getInstance(activity!!) + val actualResponse = getInstance(activity) .convertResponseToCredentialManager(Unit) assertThat(actualResponse.type) @@ -51,14 +76,11 @@ class CredentialProviderCreatePasswordControllerTest { @Test fun convertRequestToPlayServices_createPasswordRequest_success() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) val expectedId = "LM" val expectedPassword = "SodaButton" - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> - val actualRequest = getInstance(activity!!) + val actualRequest = getInstance(activity) .convertRequestToPlayServices(CreatePasswordRequest( expectedId, expectedPassword)).signInPassword @@ -70,12 +92,9 @@ class CredentialProviderCreatePasswordControllerTest { @Test fun duplicateGetInstance_shouldBeEqual() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> - val firstInstance = getInstance(activity!!) + val firstInstance = getInstance(activity) val secondInstance = getInstance(activity) assertThat(firstInstance).isEqualTo(secondInstance) } diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java index 8fba8ffa601..5e92ef88a2f 100644 --- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java +++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java @@ -33,12 +33,14 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; +import android.app.Activity; +import android.os.Build; + import androidx.credentials.CreatePublicKeyCredentialRequest; import androidx.credentials.playservices.TestCredentialsActivity; import androidx.credentials.playservices.TestUtils; import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.CredentialProviderCreatePublicKeyCredentialController; import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions; @@ -47,157 +49,209 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; -@RunWith(AndroidJUnit4.class) +@RunWith(Parameterized.class) @SmallTest public class CredentialProviderCreatePublicKeyCredentialControllerJavaTest { + + private final boolean mUseFragmentActivity; + + @Parameterized.Parameters + public static Object[] data() { + return new Object[] {true, false}; + } + + public CredentialProviderCreatePublicKeyCredentialControllerJavaTest( + final boolean useFragmentActivity) throws Throwable { + mUseFragmentActivity = useFragmentActivity; + } + + interface TestActivityListener { + void onActivity(Activity a); + } + + private void launchTestActivity(TestActivityListener listener) { + if (mUseFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + ActivityScenario<androidx.credentials.playservices.TestCredentialsFragmentActivity> + activityScenario = + ActivityScenario.launch( + androidx.credentials.playservices + .TestCredentialsFragmentActivity.class); + activityScenario.onActivity( + activity -> { + listener.onActivity((Activity) activity); + }); + } else { + ActivityScenario<TestCredentialsActivity> activityScenario = + ActivityScenario.launch(TestCredentialsActivity.class); + activityScenario.onActivity( + activity -> { + listener.onActivity((Activity) activity); + }); + } + } + + private PublicKeyCredentialCreationOptions convertRequestToPlayServices( + Activity activity, String type) { + CreatePublicKeyCredentialRequest pubKeyRequest = new CreatePublicKeyCredentialRequest(type); + return CredentialProviderCreatePublicKeyCredentialController.getInstance(activity) + .convertRequestToPlayServices(pubKeyRequest); + } + @Test - public void - convertRequestToPlayServices_correctRequiredOnlyRequest_success() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity(activity -> { - try { - JSONObject expectedJson = new JSONObject( - MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT); - - PublicKeyCredentialCreationOptions actualResponse = - CredentialProviderCreatePublicKeyCredentialController.getInstance(activity) - .convertRequestToPlayServices( - new CreatePublicKeyCredentialRequest( - MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT)); - JSONObject actualJson = createJsonObjectFromPublicKeyCredentialCreationOptions( + public void convertRequestToPlayServices_correctRequiredOnlyRequest_success() { + launchTestActivity( + activity -> { + try { + JSONObject expectedJson = + new JSONObject(MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT); + + PublicKeyCredentialCreationOptions actualResponse = + convertRequestToPlayServices( + activity, + MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT); + JSONObject actualJson = + createJsonObjectFromPublicKeyCredentialCreationOptions( actualResponse); - JSONObject requiredKeys = new JSONObject(ALL_REQUIRED_FIELDS_SIGNATURE); - - assertThat(TestUtils.Companion.isSubsetJson(expectedJson, actualJson, - requiredKeys)).isTrue(); - // TODO("Add remaining tests in detail after discussing ideal form") - } catch (JSONException e) { - throw new RuntimeException(e); - } - }); + JSONObject requiredKeys = new JSONObject(ALL_REQUIRED_FIELDS_SIGNATURE); + + assertThat( + TestUtils.Companion.isSubsetJson( + expectedJson, actualJson, requiredKeys)) + .isTrue(); + // TODO("Add remaining tests in detail after discussing ideal form") + } catch (JSONException e) { + throw new RuntimeException(e); + } + }); } @Test public void convertRequestToPlayServices_correctRequiredAndOptionalRequest_success() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity(activity -> { - try { - JSONObject expectedJson = new JSONObject( - MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT); - - PublicKeyCredentialCreationOptions actualResponse = - CredentialProviderCreatePublicKeyCredentialController.getInstance(activity) - .convertRequestToPlayServices(new CreatePublicKeyCredentialRequest( - MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT)); - JSONObject actualJson = - createJsonObjectFromPublicKeyCredentialCreationOptions( + launchTestActivity( + activity -> { + try { + JSONObject expectedJson = + new JSONObject( + MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT); + + PublicKeyCredentialCreationOptions actualResponse = + convertRequestToPlayServices( + activity, + MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT); + JSONObject actualJson = + createJsonObjectFromPublicKeyCredentialCreationOptions( actualResponse); - JSONObject requiredKeys = new JSONObject(ALL_REQUIRED_AND_OPTIONAL_SIGNATURE); - - assertThat(TestUtils.Companion.isSubsetJson(expectedJson, actualJson, - requiredKeys)).isTrue(); - // TODO("Add remaining tests in detail after discussing ideal form") - } catch (JSONException e) { - throw new RuntimeException(e); - } - }); + JSONObject requiredKeys = + new JSONObject(ALL_REQUIRED_AND_OPTIONAL_SIGNATURE); + + assertThat( + TestUtils.Companion.isSubsetJson( + expectedJson, actualJson, requiredKeys)) + .isTrue(); + // TODO("Add remaining tests in detail after discussing ideal form") + } catch (JSONException e) { + throw new RuntimeException(e); + } + }); } + @Test public void convertRequestToPlayServices_missingRequired_throws() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity(activity -> { - try { - CredentialProviderCreatePublicKeyCredentialController - .getInstance(activity) - .convertRequestToPlayServices( - new CreatePublicKeyCredentialRequest( - MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD)); - - // Should not reach here. - assertWithMessage("Exception should be thrown").that(true).isFalse(); - } catch (Exception e) { - assertThat(e.getMessage().contains("No value for id")).isTrue(); - assertThat(e.getClass().getName().contains("JSONException")).isTrue(); - } - }); + launchTestActivity( + activity -> { + try { + PublicKeyCredentialCreationOptions actualResponse = + convertRequestToPlayServices( + activity, + MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT); + + CreatePublicKeyCredentialRequest pubKeyRequest = + new CreatePublicKeyCredentialRequest( + MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT); + CredentialProviderCreatePublicKeyCredentialController.getInstance(activity) + .convertRequestToPlayServices( + new CreatePublicKeyCredentialRequest( + MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD)); + + // Should not reach here. + assertWithMessage("Exception should be thrown").that(true).isFalse(); + } catch (Exception e) { + assertThat(e.getMessage().contains("No value for id")).isTrue(); + assertThat(e.getClass().getName().contains("JSONException")).isTrue(); + } + }); } @Test public void convertRequestToPlayServices_emptyRequired_throws() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity(activity -> { - - assertThrows("Expected bad required json to throw", - JSONException.class, - () -> CredentialProviderCreatePublicKeyCredentialController - .getInstance(activity).convertRequestToPlayServices( - new CreatePublicKeyCredentialRequest( - MAIN_CREATE_JSON_REQUIRED_FIELD_EMPTY))); - }); + launchTestActivity( + activity -> { + assertThrows( + "Expected bad required json to throw", + JSONException.class, + () -> + convertRequestToPlayServices( + activity, + MAIN_CREATE_JSON_REQUIRED_FIELD_EMPTY)); + }); } @Test public void convertRequestToPlayServices_missingOptionalRequired_throws() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity(activity -> { - - assertThrows("Expected bad required json to throw", - JSONException.class, - () -> CredentialProviderCreatePublicKeyCredentialController - .getInstance(activity) - .convertRequestToPlayServices( - new CreatePublicKeyCredentialRequest( - OPTIONAL_FIELD_MISSING_REQUIRED_SUBFIELD))); - }); + launchTestActivity( + activity -> { + assertThrows( + "Expected bad required json to throw", + JSONException.class, + () -> + convertRequestToPlayServices( + activity, + OPTIONAL_FIELD_MISSING_REQUIRED_SUBFIELD)); + }); } @Test public void convertRequestToPlayServices_emptyOptionalRequired_throws() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity(activity -> { - - assertThrows("Expected bad required json to throw", - JSONException.class, - () -> CredentialProviderCreatePublicKeyCredentialController - .getInstance(activity) - .convertRequestToPlayServices( - new CreatePublicKeyCredentialRequest( - OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD))); - }); + launchTestActivity( + activity -> { + assertThrows( + "Expected bad required json to throw", + JSONException.class, + () -> + convertRequestToPlayServices( + activity, + OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD)); + }); } @Test public void convertRequestToPlayServices_missingOptionalNotRequired_success() { - ActivityScenario<TestCredentialsActivity> activityScenario = - ActivityScenario.launch(TestCredentialsActivity.class); - activityScenario.onActivity(activity -> { - try { - JSONObject expectedJson = new JSONObject( - OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD); - - PublicKeyCredentialCreationOptions actualResponse = - CredentialProviderCreatePublicKeyCredentialController.getInstance(activity) - .convertRequestToPlayServices( - new CreatePublicKeyCredentialRequest( - OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD)); - JSONObject actualJson = createJsonObjectFromPublicKeyCredentialCreationOptions( + launchTestActivity( + activity -> { + try { + JSONObject expectedJson = + new JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD); + + PublicKeyCredentialCreationOptions actualResponse = + convertRequestToPlayServices( + activity, + OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD); + JSONObject actualJson = + createJsonObjectFromPublicKeyCredentialCreationOptions( actualResponse); - JSONObject requiredKeys = new - JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD_SIGNATURE); - - assertThat(TestUtils.Companion.isSubsetJson(expectedJson, actualJson, - requiredKeys)).isTrue(); - // TODO("Add remaining tests in detail after discussing ideal form") - } catch (JSONException e) { - throw new RuntimeException(e); - } - }); + JSONObject requiredKeys = + new JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD_SIGNATURE); + + assertThat( + TestUtils.Companion.isSubsetJson( + expectedJson, actualJson, requiredKeys)) + .isTrue(); + // TODO("Add remaining tests in detail after discussing ideal form") + } catch (JSONException e) { + throw new RuntimeException(e); + } + }); } } diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerTest.kt index c33a9686cd9..e06486ae9b0 100644 --- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerTest.kt +++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerTest.kt @@ -16,6 +16,9 @@ package androidx.credentials.playservices.createpublickeycredential +import android.app.Activity +import android.os.Build +import androidx.annotation.DoNotInline import androidx.credentials.CreatePublicKeyCredentialRequest import androidx.credentials.playservices.TestCredentialsActivity import androidx.credentials.playservices.TestUtils.Companion.isSubsetJson @@ -32,7 +35,6 @@ import androidx.credentials.playservices.createkeycredential.CreatePublicKeyCred import androidx.credentials.playservices.createkeycredential.CreatePublicKeyCredentialControllerTestUtils.Companion.OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD import androidx.credentials.playservices.createkeycredential.CreatePublicKeyCredentialControllerTestUtils.Companion.createJsonObjectFromPublicKeyCredentialCreationOptions import androidx.test.core.app.ActivityScenario -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.google.common.truth.Truth.assertThat import org.json.JSONException @@ -41,20 +43,43 @@ import org.junit.Assert import org.junit.Test import org.junit.function.ThrowingRunnable import org.junit.runner.RunWith +import org.junit.runners.Parameterized -@RunWith(AndroidJUnit4::class) +@RunWith(Parameterized::class) @SmallTest -class CredentialProviderCreatePublicKeyCredentialControllerTest { +class CredentialProviderCreatePublicKeyCredentialControllerTest(val useFragmentActivity: Boolean) { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun initParameters() = listOf(true, false) + } + + @DoNotInline + private fun launchTestActivity(callback: (activity: Activity) -> Unit) { + if (useFragmentActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + var activityScenario = + ActivityScenario.launch( + androidx.credentials.playservices + .TestCredentialsFragmentActivity::class.java) + activityScenario.onActivity { activity: Activity -> + callback.invoke(activity) + } + } else { + var activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java) + activityScenario.onActivity { activity: Activity -> + callback.invoke(activity) + } + } + } + @Test fun convertRequestToPlayServices_correctRequiredOnlyRequest_success() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> try { val expectedJson = JSONObject(MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT) - val actualResponse = getInstance(activity!!).convertRequestToPlayServices( + val actualResponse = getInstance(activity).convertRequestToPlayServices( CreatePublicKeyCredentialRequest( MAIN_CREATE_JSON_ALL_REQUIRED_FIELDS_PRESENT)) val actualJson = @@ -72,15 +97,12 @@ class CredentialProviderCreatePublicKeyCredentialControllerTest { @Test fun convertRequestToPlayServices_correctRequiredAndOptionalRequest_success() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> try { val expectedJson = JSONObject( MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT) - val actualResponse = getInstance(activity!!) + val actualResponse = getInstance(activity) .convertRequestToPlayServices(CreatePublicKeyCredentialRequest( MAIN_CREATE_JSON_ALL_REQUIRED_AND_OPTIONAL_FIELDS_PRESENT)) val actualJson = @@ -98,16 +120,13 @@ class CredentialProviderCreatePublicKeyCredentialControllerTest { @Test fun convertRequestToPlayServices_missingRequired_throws() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> Assert.assertThrows("Expected bad required json to throw", JSONException::class.java, ThrowingRunnable { getInstance( - activity!! + activity ).convertRequestToPlayServices( CreatePublicKeyCredentialRequest( MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD @@ -117,30 +136,24 @@ class CredentialProviderCreatePublicKeyCredentialControllerTest { @Test fun convertRequestToPlayServices_emptyRequired_throws() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> Assert.assertThrows("Expected bad required json to throw", JSONException::class.java, - ThrowingRunnable { getInstance(activity!! + ThrowingRunnable { getInstance(activity ).convertRequestToPlayServices(CreatePublicKeyCredentialRequest( MAIN_CREATE_JSON_REQUIRED_FIELD_EMPTY)) }) } } @Test fun convertRequestToPlayServices_missingOptionalRequired_throws() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> Assert.assertThrows("Expected bad required json to throw", JSONException::class.java, ThrowingRunnable { getInstance( - activity!! + activity ).convertRequestToPlayServices( CreatePublicKeyCredentialRequest( OPTIONAL_FIELD_MISSING_REQUIRED_SUBFIELD)) }) @@ -149,14 +162,11 @@ class CredentialProviderCreatePublicKeyCredentialControllerTest { @Test fun convertRequestToPlayServices_emptyOptionalRequired_throws() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> Assert.assertThrows("Expected bad required json to throw", JSONException::class.java, - ThrowingRunnable { getInstance(activity!!).convertRequestToPlayServices( + ThrowingRunnable { getInstance(activity).convertRequestToPlayServices( CreatePublicKeyCredentialRequest( OPTIONAL_FIELD_WITH_EMPTY_REQUIRED_SUBFIELD)) }) } @@ -164,15 +174,12 @@ class CredentialProviderCreatePublicKeyCredentialControllerTest { @Test fun convertRequestToPlayServices_missingOptionalNotRequired_success() { - val activityScenario = ActivityScenario.launch( - TestCredentialsActivity::class.java - ) - activityScenario.onActivity { activity: TestCredentialsActivity? -> + launchTestActivity { activity: Activity -> try { val expectedJson = JSONObject(OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD) val actualResponse = - getInstance(activity!!) + getInstance(activity) .convertRequestToPlayServices( CreatePublicKeyCredentialRequest( OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD)) diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderFragment.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderFragment.kt new file mode 100644 index 00000000000..2d7f1e9d32e --- /dev/null +++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderFragment.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2023 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.content.Intent +import android.os.Bundle +import android.os.ResultReceiver +import androidx.annotation.RestrictTo +import androidx.credentials.playservices.controllers.CredentialProviderBaseController +import androidx.fragment.app.Fragment + +/** A fragment used if we are passed a fragment activity. */ +@RestrictTo(RestrictTo.Scope.LIBRARY) +@Suppress("Deprecation") +open class CredentialProviderFragment : Fragment() { + + private var resultReceiver: ResultReceiver? = null + private var mWaitingForActivityResult = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + resultReceiver = getResultReceiver() + if (resultReceiver == null) { + return + } + + restoreState(savedInstanceState) + if (mWaitingForActivityResult) { + return // Past call still active + } + } + + private fun getResultReceiver(): ResultReceiver? { + if (getArguments() == null) { + return null + } + + return getArguments()!!.getParcelable(CredentialProviderBaseController.RESULT_RECEIVER_TAG) + as? ResultReceiver + } + + private fun restoreState(savedInstanceState: Bundle?) { + if (savedInstanceState != null) { + mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult) + super.onSaveInstanceState(outState) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + val bundle = Bundle() + bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, false) + bundle.putInt(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, requestCode) + bundle.putParcelable(CredentialProviderBaseController.RESULT_DATA_TAG, data) + resultReceiver?.send(resultCode, bundle) + mWaitingForActivityResult = false + } + + companion object { + private const val TAG = "CredentialProviderFragment" + private const val KEY_AWAITING_RESULT = "androidx.credentials.playservices.AWAITING_RESULT" + + fun createFrom(resultReceiver: ResultReceiver): CredentialProviderFragment { + val f = CredentialProviderFragment() + + // Supply index input as an argument. + val args = Bundle() + args.putParcelable(CredentialProviderBaseController.RESULT_RECEIVER_TAG, resultReceiver) + f.setArguments(args) + + return f + } + } +} diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/GmsCoreUtils.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/GmsCoreUtils.kt new file mode 100644 index 00000000000..112599dfc76 --- /dev/null +++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/GmsCoreUtils.kt @@ -0,0 +1,266 @@ +/* + * 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.app.PendingIntent +import android.content.IntentSender +import android.os.Bundle +import android.os.ResultReceiver +import android.util.Log +import androidx.annotation.RestrictTo +import androidx.credentials.playservices.controllers.CredentialProviderBaseController +import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.CREATE_INTERRUPTED +import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.CREATE_UNKNOWN +import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_INTERRUPTED +import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_NO_CREDENTIALS +import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_UNKNOWN +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import com.google.android.gms.auth.api.identity.BeginSignInRequest +import com.google.android.gms.auth.api.identity.CredentialSavingClient +import com.google.android.gms.auth.api.identity.SavePasswordRequest +import com.google.android.gms.auth.api.identity.SignInClient +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.fido.fido2.Fido2ApiClient +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions + +/** A util class for interacting with GmsCore. */ +@RestrictTo(RestrictTo.Scope.LIBRARY) +@Suppress("Deprecation", "ForbiddenSuperClass") +open class GmsCoreUtils { + + class GmsCoreUtilsResult(var waitingForActivityResult: Boolean, var hasFinished: Boolean) + + internal companion object { + private const val TAG = "GmsCoreUtils" + + const val DEFAULT_REQUEST_CODE = 1 + + class FragmentCreationException() : Exception("Failed to create exception") + + fun handleCreatePublicKeyCredential( + apiClient: Fido2ApiClient, + resultReceiver: ResultReceiver, + fidoRegistrationRequest: PublicKeyCredentialCreationOptions?, + requestCode: Int, + activity: Activity + ): GmsCoreUtilsResult { + var waitingForActivityResult = false + var hasActivityFinished = false + var fragment = setupFragmentActivity(activity, resultReceiver) + + fidoRegistrationRequest?.let { + apiClient + .getRegisterPendingIntent(fidoRegistrationRequest) + .addOnSuccessListener { result: PendingIntent -> + try { + startIntentSender( + activity, + result.intentSender, + requestCode, + fragment, + ) + } catch (e: IntentSender.SendIntentException) { + setupFailure( + resultReceiver, + CREATE_UNKNOWN, + "During public key credential, found IntentSender " + + "failure on public key creation: ${e.message}" + ) + } + } + .addOnFailureListener { e: Exception -> + var errName: String = CREATE_UNKNOWN + if (e is ApiException && e.statusCode in CredentialProviderBaseController.retryables) { + errName = CREATE_INTERRUPTED + } + setupFailure( + resultReceiver, + errName, + "During create public key credential, fido registration " + "failure: ${e.message}" + ) + } + } + ?: run { + Log.w( + TAG, + "During create public key credential, request is null, so nothing to " + + "launch for public key credentials" + ) + hasActivityFinished = true + } + return GmsCoreUtilsResult(waitingForActivityResult, hasActivityFinished) + } + + fun handleBeginSignIn( + apiClient: SignInClient, + resultReceiver: ResultReceiver, + params: BeginSignInRequest?, + requestCode: Int, + activity: Activity + ): GmsCoreUtilsResult { + var waitingForActivityResult = false + var hasFinished = false + var fragment = setupFragmentActivity(activity, resultReceiver) + + params?.let { + apiClient + .beginSignIn(params) + .addOnSuccessListener { + try { + waitingForActivityResult = true + startIntentSender( + activity, + it.pendingIntent.intentSender, + requestCode, + fragment, + ) + } catch (e: IntentSender.SendIntentException) { + setupFailure( + resultReceiver, + GET_UNKNOWN, + "During begin sign in, one tap ui intent sender " + "failure: ${e.message}" + ) + } + } + .addOnFailureListener { e: Exception -> + var errName: String = GET_NO_CREDENTIALS + if (e is ApiException && e.statusCode in CredentialProviderBaseController.retryables) { + errName = GET_INTERRUPTED + } + setupFailure( + resultReceiver, + errName, + "During begin sign in, failure response from one tap: ${e.message}" + ) + } + } + ?: run { + Log.i( + TAG, + "During begin sign in, params is null, nothing to launch for " + "begin sign in" + ) + hasFinished = true + } + return GmsCoreUtilsResult(waitingForActivityResult, hasFinished) + } + + fun handleCreatePassword( + apiClient: CredentialSavingClient, + resultReceiver: ResultReceiver, + params: SavePasswordRequest?, + requestCode: Int, + activity: Activity + ): GmsCoreUtilsResult { + var waitingForActivityResult = false + var hasFinished = false + var fragment = setupFragmentActivity(activity, resultReceiver) + + params?.let { + apiClient + .savePassword(params) + .addOnSuccessListener { + try { + waitingForActivityResult = true + startIntentSender( + activity, + it.pendingIntent.intentSender, + requestCode, + fragment, + ) + } catch (e: IntentSender.SendIntentException) { + setupFailure( + resultReceiver, + CREATE_UNKNOWN, + "During save password, found UI intent sender " + "failure: ${e.message}" + ) + } + } + .addOnFailureListener { e: Exception -> + var errName: String = CREATE_UNKNOWN + if (e is ApiException && e.statusCode in CredentialProviderBaseController.retryables) { + errName = CREATE_INTERRUPTED + } + setupFailure( + resultReceiver, + errName, + "During save password, found " + "password failure response from one tap ${e.message}" + ) + } + } + ?: run { + Log.i( + TAG, + "During save password, params is null, nothing to launch for create" + " password" + ) + hasFinished = true + } + return GmsCoreUtilsResult(waitingForActivityResult, hasFinished) + } + + private fun setupFragmentActivity( + activity: Activity, + resultReceiver: ResultReceiver + ): Fragment? { + if (activity is FragmentActivity) { + val fragment = CredentialProviderFragment.createFrom(resultReceiver) + val manager = activity.getSupportFragmentManager() + manager.beginTransaction().add(fragment, "credman").commit() + + if (!fragment.isAdded()) { + throw FragmentCreationException() + } + + return fragment + } + + return null + } + + private fun startIntentSender( + activity: Activity, + intentSender: IntentSender, + requestCode: Int, + fragment: Fragment?, + ) { + if (fragment != null && fragment.isAdded() && activity is FragmentActivity) { + activity.startIntentSenderFromFragment( + fragment, + intentSender, + requestCode, + null, + 0, + 0, + 0, + null, + ) + return + } + + activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, null) + } + + private fun setupFailure(resultReceiver: ResultReceiver, errName: String, errMsg: String) { + val bundle = Bundle() + bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, true) + bundle.putString(CredentialProviderBaseController.EXCEPTION_TYPE_TAG, errName) + bundle.putString(CredentialProviderBaseController.EXCEPTION_MESSAGE_TAG, errMsg) + resultReceiver.send(Integer.MAX_VALUE, bundle) + } + } +} diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt index faf3b6fd828..8586305fb2a 100644 --- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt +++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt @@ -17,230 +17,150 @@ package androidx.credentials.playservices import android.app.Activity -import android.app.PendingIntent import android.content.Intent -import android.content.IntentSender import android.os.Bundle import android.os.ResultReceiver import android.util.Log import androidx.annotation.RestrictTo import androidx.credentials.playservices.controllers.CredentialProviderBaseController -import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.CREATE_INTERRUPTED -import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.CREATE_UNKNOWN -import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_INTERRUPTED -import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_NO_CREDENTIALS -import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_UNKNOWN import com.google.android.gms.auth.api.identity.BeginSignInRequest import com.google.android.gms.auth.api.identity.Identity import com.google.android.gms.auth.api.identity.SavePasswordRequest -import com.google.android.gms.common.api.ApiException import com.google.android.gms.fido.Fido import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions -/** - * An activity used to ensure all required API versions work as intended. - */ +/** An activity used to ensure all required API versions work as intended. */ @RestrictTo(RestrictTo.Scope.LIBRARY) @Suppress("Deprecation", "ForbiddenSuperClass") open class HiddenActivity : Activity() { - private var resultReceiver: ResultReceiver? = null - private var mWaitingForActivityResult = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - overridePendingTransition(0, 0) - val type: String? = intent.getStringExtra(CredentialProviderBaseController.TYPE_TAG) - resultReceiver = intent.getParcelableExtra( - CredentialProviderBaseController.RESULT_RECEIVER_TAG) - - if (resultReceiver == null) { - finish() - } - - restoreState(savedInstanceState) - if (mWaitingForActivityResult) { - return; // Past call still active - } - - when (type) { - CredentialProviderBaseController.BEGIN_SIGN_IN_TAG -> { - handleBeginSignIn() - } - CredentialProviderBaseController.CREATE_PASSWORD_TAG -> { - handleCreatePassword() - } - CredentialProviderBaseController.CREATE_PUBLIC_KEY_CREDENTIAL_TAG -> { - handleCreatePublicKeyCredential() - } else -> { - Log.w(TAG, "Activity handed an unsupported type") - finish() - } - } - } + private var resultReceiver: ResultReceiver? = null + private var mWaitingForActivityResult = false - private fun restoreState(savedInstanceState: Bundle?) { - if (savedInstanceState != null) { - mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false) - } - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + overridePendingTransition(0, 0) + val type: String? = intent.getStringExtra(CredentialProviderBaseController.TYPE_TAG) + resultReceiver = intent.getParcelableExtra(CredentialProviderBaseController.RESULT_RECEIVER_TAG) - private fun handleCreatePublicKeyCredential() { - val fidoRegistrationRequest: PublicKeyCredentialCreationOptions? = intent - .getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG) - val requestCode: Int = intent.getIntExtra( - CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, - DEFAULT_VALUE) - fidoRegistrationRequest?.let { - Fido.getFido2ApiClient(this) - .getRegisterPendingIntent(fidoRegistrationRequest) - .addOnSuccessListener { result: PendingIntent -> - try { - mWaitingForActivityResult = true - startIntentSenderForResult( - result.intentSender, - requestCode, - null, /* fillInIntent= */ - 0, /* flagsMask= */ - 0, /* flagsValue= */ - 0, /* extraFlags= */ - null /* options= */ - ) - } catch (e: IntentSender.SendIntentException) { - setupFailure(resultReceiver!!, - CREATE_UNKNOWN, - "During public key credential, found IntentSender " + - "failure on public key creation: ${e.message}") - } - } - .addOnFailureListener { e: Exception -> - var errName: String = CREATE_UNKNOWN - if (e is ApiException && e.statusCode in - CredentialProviderBaseController.retryables) { - errName = CREATE_INTERRUPTED - } - setupFailure(resultReceiver!!, errName, - "During create public key credential, fido registration " + - "failure: ${e.message}") - } - } ?: run { - Log.w(TAG, "During create public key credential, request is null, so nothing to " + - "launch for public key credentials") - finish() - } + if (resultReceiver == null) { + finish() } - private fun setupFailure(resultReceiver: ResultReceiver, errName: String, errMsg: String) { - val bundle = Bundle() - bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, true) - bundle.putString(CredentialProviderBaseController.EXCEPTION_TYPE_TAG, errName) - bundle.putString(CredentialProviderBaseController.EXCEPTION_MESSAGE_TAG, errMsg) - resultReceiver.send(Integer.MAX_VALUE, bundle) - finish() + restoreState(savedInstanceState) + if (mWaitingForActivityResult) { + return + // Past call still active } - override fun onSaveInstanceState(outState: Bundle) { - outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult) - super.onSaveInstanceState(outState) + when (type) { + CredentialProviderBaseController.BEGIN_SIGN_IN_TAG -> { + handleBeginSignIn(intent, resultReceiver) + } + CredentialProviderBaseController.CREATE_PASSWORD_TAG -> { + handleCreatePassword(intent, resultReceiver) + } + CredentialProviderBaseController.CREATE_PUBLIC_KEY_CREDENTIAL_TAG -> { + handleCreatePublicKeyCredential(intent, resultReceiver) + } + else -> { + Log.w(TAG, "Activity handed an unsupported type") + finish() + } } + } - private fun handleBeginSignIn() { - val params: BeginSignInRequest? = intent.getParcelableExtra( - CredentialProviderBaseController.REQUEST_TAG) - val requestCode: Int = intent.getIntExtra( - CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, - DEFAULT_VALUE) - params?.let { - Identity.getSignInClient(this).beginSignIn(params).addOnSuccessListener { - try { - mWaitingForActivityResult = true - startIntentSenderForResult( - it.pendingIntent.intentSender, - requestCode, - null, - 0, - 0, - 0, - null - ) - } catch (e: IntentSender.SendIntentException) { - setupFailure(resultReceiver!!, - GET_UNKNOWN, - "During begin sign in, one tap ui intent sender " + - "failure: ${e.message}") - } - }.addOnFailureListener { e: Exception -> - var errName: String = GET_NO_CREDENTIALS - if (e is ApiException && e.statusCode in - CredentialProviderBaseController.retryables) { - errName = GET_INTERRUPTED - } - setupFailure(resultReceiver!!, errName, - "During begin sign in, failure response from one tap: ${e.message}") - } - } ?: run { - Log.i(TAG, "During begin sign in, params is null, nothing to launch for " + - "begin sign in") - finish() - } + private fun restoreState(savedInstanceState: Bundle?) { + if (savedInstanceState != null) { + mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false) } - - private fun handleCreatePassword() { - val params: SavePasswordRequest? = intent.getParcelableExtra( - CredentialProviderBaseController.REQUEST_TAG) - val requestCode: Int = intent.getIntExtra( - CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, - DEFAULT_VALUE) - params?.let { - Identity.getCredentialSavingClient(this).savePassword(params) - .addOnSuccessListener { - try { - mWaitingForActivityResult = true - startIntentSenderForResult( - it.pendingIntent.intentSender, - requestCode, - null, - 0, - 0, - 0, - null - ) - } catch (e: IntentSender.SendIntentException) { - setupFailure(resultReceiver!!, - CREATE_UNKNOWN, - "During save password, found UI intent sender " + - "failure: ${e.message}") - } - }.addOnFailureListener { e: Exception -> - var errName: String = CREATE_UNKNOWN - if (e is ApiException && e.statusCode in - CredentialProviderBaseController.retryables) { - errName = CREATE_INTERRUPTED - } - setupFailure(resultReceiver!!, errName, "During save password, found " + - "password failure response from one tap ${e.message}") - } - } ?: run { - Log.i(TAG, "During save password, params is null, nothing to launch for create" + - " password") - finish() - } + } + + private fun handleBeginSignIn(intent: Intent, resultReceiver: ResultReceiver?) { + val params: BeginSignInRequest? = + intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG) + val requestCode: Int = + intent.getIntExtra(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, DEFAULT_VALUE) + + if (intent.hasExtra(CredentialProviderBaseController.REQUEST_TAG) && resultReceiver != null) { + val result = + GmsCoreUtils.handleBeginSignIn( + Identity.getSignInClient(this), + resultReceiver, + params!!, + requestCode, + this + ) + mWaitingForActivityResult = result.waitingForActivityResult + if (result.hasFinished) { + finish() + } } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - val bundle = Bundle() - bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, false) - bundle.putInt(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, requestCode) - bundle.putParcelable(CredentialProviderBaseController.RESULT_DATA_TAG, data) - resultReceiver?.send(resultCode, bundle) - mWaitingForActivityResult = false + } + + private fun handleCreatePassword(intent: Intent, resultReceiver: ResultReceiver?) { + val params: SavePasswordRequest? = + intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG) + val requestCode: Int = + intent.getIntExtra(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, DEFAULT_VALUE) + + if (intent.hasExtra(CredentialProviderBaseController.REQUEST_TAG) && resultReceiver != null) { + val result = + GmsCoreUtils.handleCreatePassword( + Identity.getCredentialSavingClient(this), + resultReceiver, + params!!, + requestCode, + this + ) + mWaitingForActivityResult = result.waitingForActivityResult + if (result.hasFinished) { finish() + } } - - companion object { - private const val DEFAULT_VALUE: Int = 1 - private const val TAG = "HiddenActivity" - private const val KEY_AWAITING_RESULT = "androidx.credentials.playservices.AWAITING_RESULT" + } + + private fun handleCreatePublicKeyCredential(intent: Intent, resultReceiver: ResultReceiver?) { + val fidoRegistrationRequest: PublicKeyCredentialCreationOptions? = + intent.getParcelableExtra(CredentialProviderBaseController.REQUEST_TAG) + val requestCode: Int = + intent.getIntExtra(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, DEFAULT_VALUE) + + if (intent.hasExtra(CredentialProviderBaseController.REQUEST_TAG) && resultReceiver != null) { + val result = + GmsCoreUtils.handleCreatePublicKeyCredential( + Fido.getFido2ApiClient(this), + resultReceiver, + fidoRegistrationRequest!!, + requestCode, + this + ) + mWaitingForActivityResult = result.waitingForActivityResult + if (result.hasFinished) { + finish() + } } -}
\ No newline at end of file + } + + override fun onSaveInstanceState(outState: Bundle) { + outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult) + super.onSaveInstanceState(outState) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + val bundle = Bundle() + bundle.putBoolean(CredentialProviderBaseController.FAILURE_RESPONSE_TAG, false) + bundle.putInt(CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG, requestCode) + bundle.putParcelable(CredentialProviderBaseController.RESULT_DATA_TAG, data) + resultReceiver?.send(resultCode, bundle) + mWaitingForActivityResult = false + finish() + } + + companion object { + private const val DEFAULT_VALUE: Int = 1 + private const val TAG = "HiddenActivity" + private const val KEY_AWAITING_RESULT = "androidx.credentials.playservices.AWAITING_RESULT" + } +} 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 index b0d94538173..18f8a16b813 100644 --- 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 @@ -36,11 +36,13 @@ import androidx.credentials.exceptions.GetCredentialException import androidx.credentials.exceptions.GetCredentialInterruptedException import androidx.credentials.exceptions.GetCredentialUnknownException import androidx.credentials.playservices.CredentialProviderPlayServicesImpl +import androidx.credentials.playservices.GmsCoreUtils import androidx.credentials.playservices.HiddenActivity import androidx.credentials.playservices.controllers.BeginSignIn.BeginSignInControllerUtility.Companion.constructBeginSignInRequest import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.PublicKeyCredentialControllerUtility import androidx.credentials.playservices.controllers.CredentialProviderBaseController import androidx.credentials.playservices.controllers.CredentialProviderController +import androidx.fragment.app.FragmentActivity import com.google.android.gms.auth.api.identity.BeginSignInRequest import com.google.android.gms.auth.api.identity.Identity import com.google.android.gms.auth.api.identity.SignInCredential @@ -117,6 +119,20 @@ internal class CredentialProviderBeginSignInController(private val context: Cont } val convertedRequest: BeginSignInRequest = this.convertRequestToPlayServices(request) + + // If we were passed a fragment activity use that instead of a hidden one. + if (context is FragmentActivity) { + try { + GmsCoreUtils.handleBeginSignIn( + Identity.getSignInClient(context), + resultReceiver, convertedRequest, GmsCoreUtils.DEFAULT_REQUEST_CODE, + context) + return + } catch (e: Exception) { + Log.e(TAG, "Failed to use fragment flow", e) + } + } + val hiddenIntent = Intent(context, HiddenActivity::class.java) hiddenIntent.putExtra(REQUEST_TAG, convertedRequest) generateHiddenActivityIntent(resultReceiver, hiddenIntent, BEGIN_SIGN_IN_TAG) 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 index 360b0c61251..ee4c3bb23ae 100644 --- 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 @@ -32,9 +32,12 @@ import androidx.credentials.CredentialManagerCallback import androidx.credentials.exceptions.CreateCredentialException import androidx.credentials.exceptions.CreateCredentialUnknownException import androidx.credentials.playservices.CredentialProviderPlayServicesImpl +import androidx.credentials.playservices.GmsCoreUtils import androidx.credentials.playservices.HiddenActivity import androidx.credentials.playservices.controllers.CredentialProviderBaseController import androidx.credentials.playservices.controllers.CredentialProviderController +import androidx.fragment.app.FragmentActivity +import com.google.android.gms.auth.api.identity.Identity import com.google.android.gms.auth.api.identity.SavePasswordRequest import com.google.android.gms.auth.api.identity.SignInPassword import java.util.concurrent.Executor @@ -100,6 +103,20 @@ internal class CredentialProviderCreatePasswordController(private val context: C } val convertedRequest: SavePasswordRequest = this.convertRequestToPlayServices(request) + + // If we were passed a fragment activity use that instead of a hidden one. + if (context is FragmentActivity) { + try { + GmsCoreUtils.handleCreatePassword( + Identity.getCredentialSavingClient(context), + resultReceiver, convertedRequest, GmsCoreUtils.DEFAULT_REQUEST_CODE, + context) + return + } catch (e: Exception) { + Log.e(TAG, "Failed to use fragment flow", e) + } + } + val hiddenIntent = Intent(context, HiddenActivity::class.java) hiddenIntent.putExtra(REQUEST_TAG, convertedRequest) generateHiddenActivityIntent(resultReceiver, hiddenIntent, CREATE_PASSWORD_TAG) 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 index 7f3a2693ef9..15420581f40 100644 --- 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 @@ -35,9 +35,11 @@ import androidx.credentials.exceptions.domerrors.EncodingError import androidx.credentials.exceptions.domerrors.UnknownError import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException import androidx.credentials.playservices.CredentialProviderPlayServicesImpl +import androidx.credentials.playservices.GmsCoreUtils import androidx.credentials.playservices.HiddenActivity import androidx.credentials.playservices.controllers.CredentialProviderBaseController import androidx.credentials.playservices.controllers.CredentialProviderController +import androidx.fragment.app.FragmentActivity import com.google.android.gms.fido.Fido import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions @@ -120,6 +122,19 @@ internal class CredentialProviderCreatePublicKeyCredentialController(private val if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) { return } + + // If we were passed a fragment activity use that instead of a hidden one. + if (context is FragmentActivity) { + try { + GmsCoreUtils.handleCreatePublicKeyCredential(Fido.getFido2ApiClient(context), + resultReceiver, fidoRegistrationRequest, GmsCoreUtils.DEFAULT_REQUEST_CODE, + context) + return + } catch (e: Exception) { + Log.e(TAG, "Failed to use fragment flow", e) + } + } + val hiddenIntent = Intent(context, HiddenActivity::class.java) hiddenIntent.putExtra(REQUEST_TAG, fidoRegistrationRequest) generateHiddenActivityIntent(resultReceiver, hiddenIntent, |