From 9f93c0a7c1ba56f38241f43b73d911d8a213d37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20Gr=C3=BCning?= Date: Mon, 9 Jun 2025 12:01:29 +0200 Subject: [PATCH 1/6] poc native google sign in plugin --- Assets/Plugins/Android/AndroidManifest.xml | 2 +- Assets/Plugins/Android/mainTemplate.gradle | 1 + Packages/Sequence-Unity/Plugins/Android.meta | 8 +++ .../Plugins/Android/GoogleSignInPlugin.java | 69 +++++++++++++++++++ .../Android/GoogleSignInPlugin.java.meta | 32 +++++++++ .../Android/SequenceUnityActivity.java | 13 ++++ .../Android/SequenceUnityActivity.java.meta | 3 + .../Authentication/SignInWithGoogle.meta | 3 + .../SignInWithGoogle/NativeGoogleSignIn.cs | 51 ++++++++++++++ .../NativeGoogleSignIn.cs.meta | 3 + .../NativeGoogleSignInReceiver.cs | 26 +++++++ .../NativeGoogleSignInReceiver.cs.meta | 3 + 12 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 Packages/Sequence-Unity/Plugins/Android.meta create mode 100644 Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java create mode 100644 Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java.meta create mode 100644 Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java create mode 100644 Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java.meta create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle.meta create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs.meta create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignInReceiver.cs create mode 100644 Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignInReceiver.cs.meta diff --git a/Assets/Plugins/Android/AndroidManifest.xml b/Assets/Plugins/Android/AndroidManifest.xml index be55bd8f1..b634a79a4 100644 --- a/Assets/Plugins/Android/AndroidManifest.xml +++ b/Assets/Plugins/Android/AndroidManifest.xml @@ -5,7 +5,7 @@ > diff --git a/Assets/Plugins/Android/mainTemplate.gradle b/Assets/Plugins/Android/mainTemplate.gradle index 388db62c1..647d627a9 100644 --- a/Assets/Plugins/Android/mainTemplate.gradle +++ b/Assets/Plugins/Android/mainTemplate.gradle @@ -4,6 +4,7 @@ apply plugin: 'com.android.library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.security:security-crypto:1.1.0-alpha03' + implementation 'com.google.android.gms:play-services-auth:21.0.0' **DEPS**} diff --git a/Packages/Sequence-Unity/Plugins/Android.meta b/Packages/Sequence-Unity/Plugins/Android.meta new file mode 100644 index 000000000..6869af135 --- /dev/null +++ b/Packages/Sequence-Unity/Plugins/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6705880e88d6447839e18aaa450747ad +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java b/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java new file mode 100644 index 000000000..9a4b86756 --- /dev/null +++ b/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java @@ -0,0 +1,69 @@ +package xyz.sequence; + +import android.app.Activity; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.google.android.gms.auth.api.signin.*; +import com.google.android.gms.common.api.ApiException; +import com.google.android.gms.tasks.Task; + +import com.unity3d.player.UnityPlayer; + +public class GoogleSignInPlugin { + private static final String TAG = "GoogleSignInPlugin"; + private static final int RC_SIGN_IN = 9001; + private static GoogleSignInClient mGoogleSignInClient; + private static Activity unityActivity; + + public static void initialize(Activity activity, String clientId) { + unityActivity = activity; + GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(clientId) + .requestEmail() + .build(); + + mGoogleSignInClient = GoogleSignIn.getClient(activity, gso); + } + + public static void signIn() { + if (mGoogleSignInClient == null) { + Log.e(TAG, "GoogleSignInClient is null. Did you call initialize()?"); + return; + } + + Log.d(TAG, "Starting Google Sign-In intent"); + + mGoogleSignInClient.revokeAccess().addOnCompleteListener(task -> { + mGoogleSignInClient.signOut().addOnCompleteListener(signOutTask -> { + Intent signInIntent = mGoogleSignInClient.getSignInIntent(); + unityActivity.startActivityForResult(signInIntent, RC_SIGN_IN); + }); + }); + } + + public static void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + Log.d(TAG, "onActivityResult called with requestCode: " + requestCode + ", resultCode: " + resultCode); + + if (requestCode == RC_SIGN_IN) { + Log.d(TAG, "Handling Google Sign-In result"); + Task task = GoogleSignIn.getSignedInAccountFromIntent(data); + handleSignInResult(task); + } + } + + private static void handleSignInResult(Task completedTask) { + try { + GoogleSignInAccount account = completedTask.getResult(ApiException.class); + String token = account.getIdToken(); + + Log.d(TAG, "Sign-In success: ID token = " + token); + UnityPlayer.UnitySendMessage("NativeGoogleSignInReceiver", "HandleIdToken", token); + } catch (ApiException e) { + Log.e(TAG, "Sign-In failed: code=" + e.getStatusCode() + " message=" + e.getMessage(), e); + UnityPlayer.UnitySendMessage("NativeGoogleSignInReceiver", "HandleError", e.getMessage()); + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java.meta b/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java.meta new file mode 100644 index 000000000..d3d7ed41b --- /dev/null +++ b/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 9ea9bd680af84849a3fc14ada4d74ab4 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java b/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java new file mode 100644 index 000000000..efaf1ec2e --- /dev/null +++ b/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java @@ -0,0 +1,13 @@ +package xyz.sequence; + +import android.content.Intent; +import android.os.Bundle; +import com.unity3d.player.UnityPlayerActivity; + +public class SequenceUnityActivity extends UnityPlayerActivity { + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + GoogleSignInPlugin.onActivityResult(requestCode, resultCode, data); + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java.meta b/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java.meta new file mode 100644 index 000000000..7293794bf --- /dev/null +++ b/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 28448b703c874602ad0cb08c3ee9e907 +timeCreated: 1749461455 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle.meta new file mode 100644 index 000000000..a25032b83 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 46113667bdd641778f5a270aabc885fe +timeCreated: 1749456540 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs new file mode 100644 index 000000000..3d9326865 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs @@ -0,0 +1,51 @@ +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Sequence.Authentication +{ + public class NativeGoogleSignIn + { + private AndroidJavaClass _pluginClass; + private NativeGoogleSignInReceiver _receiver; + + public NativeGoogleSignIn(string clientId) + { + _pluginClass = new AndroidJavaClass("xyz.sequence.GoogleSignInPlugin"); + var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); + var activity = unityPlayer.GetStatic("currentActivity"); + _pluginClass.CallStatic("initialize", activity, clientId); + } + + public async Task SignIn() + { + var done = false; + var idToken = string.Empty; + + _receiver = NativeGoogleSignInReceiver.Create(); + + Assert.IsNotNull(_receiver, "NativeGoogleSignInReceiver not initialized"); + Debug.Log($"SignIn"); + _receiver.OnIdTokenReceived.AddListener(receivedIdToken => + { + idToken = receivedIdToken; + done = true; + Debug.Log($"receivedIdToken: {receivedIdToken}"); + }); + + _receiver.OnSignInFailed.AddListener(error => + { + done = true; + Debug.LogError($"Error during native google sign-in: {error}"); + }); + + _pluginClass.CallStatic("signIn"); + + while (!done) + await Task.Delay(200); + Debug.Log($"SignIn Done"); + GameObject.Destroy(_receiver); + return idToken; + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs.meta new file mode 100644 index 000000000..b41603989 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6f21b6045fd843b7b1ec5fcad8d9c84a +timeCreated: 1749455533 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignInReceiver.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignInReceiver.cs new file mode 100644 index 000000000..5180389c5 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignInReceiver.cs @@ -0,0 +1,26 @@ +using UnityEngine; +using UnityEngine.Events; + +namespace Sequence.Authentication +{ + public class NativeGoogleSignInReceiver : MonoBehaviour + { + [HideInInspector] public UnityEvent OnIdTokenReceived = new(); + [HideInInspector] public UnityEvent OnSignInFailed = new(); + + public static NativeGoogleSignInReceiver Create() + { + return new GameObject(nameof(NativeGoogleSignInReceiver)).AddComponent(); + } + + public void HandleIdToken(string idToken) + { + OnIdTokenReceived.Invoke(idToken); + } + + public void HandleError(string error) + { + OnSignInFailed.Invoke(error); + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignInReceiver.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignInReceiver.cs.meta new file mode 100644 index 000000000..01242a7a9 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignInReceiver.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f6b4c3497a0d4ddc82285f43fa43e10b +timeCreated: 1749456573 \ No newline at end of file From 78050d4aa965b3a8697ea57cd9d12327ed5605b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20Gr=C3=BCning?= Date: Mon, 9 Jun 2025 12:31:13 +0200 Subject: [PATCH 2/6] changed to a different android google plugin --- Assets/Plugins/Android/mainTemplate.gradle | 4 + .../Plugins/Android/GoogleSignInPlugin.java | 122 +++++++++++------- .../Android/SequenceUnityActivity.java | 13 -- .../Android/SequenceUnityActivity.java.meta | 3 - .../SignInWithGoogle/NativeGoogleSignIn.cs | 17 ++- 5 files changed, 86 insertions(+), 73 deletions(-) delete mode 100644 Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java delete mode 100644 Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java.meta diff --git a/Assets/Plugins/Android/mainTemplate.gradle b/Assets/Plugins/Android/mainTemplate.gradle index 647d627a9..26e7d2013 100644 --- a/Assets/Plugins/Android/mainTemplate.gradle +++ b/Assets/Plugins/Android/mainTemplate.gradle @@ -5,6 +5,10 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.security:security-crypto:1.1.0-alpha03' implementation 'com.google.android.gms:play-services-auth:21.0.0' + implementation "androidx.credentials:credentials:1.2.0" + implementation "androidx.credentials:credentials-play-services-auth:1.2.0" + implementation "com.google.android.libraries.identity.googleid:googleid:1.1.0" + implementation "com.android.billingclient:billing:6.1.0" **DEPS**} diff --git a/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java b/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java index 9a4b86756..39ccce595 100644 --- a/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java +++ b/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java @@ -1,69 +1,91 @@ package xyz.sequence; -import android.app.Activity; -import android.content.Intent; +import android.content.Context; import android.util.Log; -import androidx.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.credentials.Credential; +import androidx.credentials.CredentialManager; +import androidx.credentials.CredentialManagerCallback; +import androidx.credentials.CustomCredential; +import androidx.credentials.GetCredentialRequest; +import androidx.credentials.GetCredentialResponse; +import androidx.credentials.exceptions.GetCredentialException; +import androidx.credentials.exceptions.NoCredentialException; +import androidx.credentials.exceptions.GetCredentialCustomException; -import com.google.android.gms.auth.api.signin.*; -import com.google.android.gms.common.api.ApiException; -import com.google.android.gms.tasks.Task; +import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption; +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential; + +import java.security.SecureRandom; +import java.util.Base64; +import java.util.concurrent.Executors; import com.unity3d.player.UnityPlayer; public class GoogleSignInPlugin { - private static final String TAG = "GoogleSignInPlugin"; - private static final int RC_SIGN_IN = 9001; - private static GoogleSignInClient mGoogleSignInClient; - private static Activity unityActivity; - - public static void initialize(Activity activity, String clientId) { - unityActivity = activity; - GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestIdToken(clientId) - .requestEmail() - .build(); + private static final String TAG = "SequenceGoogleSignIn"; - mGoogleSignInClient = GoogleSignIn.getClient(activity, gso); + public static void signIn(Context context, String clientId) + { + getCredentialAsync(context, clientId); } - public static void signIn() { - if (mGoogleSignInClient == null) { - Log.e(TAG, "GoogleSignInClient is null. Did you call initialize()?"); - return; - } - - Log.d(TAG, "Starting Google Sign-In intent"); - - mGoogleSignInClient.revokeAccess().addOnCompleteListener(task -> { - mGoogleSignInClient.signOut().addOnCompleteListener(signOutTask -> { - Intent signInIntent = mGoogleSignInClient.getSignInIntent(); - unityActivity.startActivityForResult(signInIntent, RC_SIGN_IN); - }); - }); - } + private static void getCredentialAsync(Context context, String clientId) + { + GetSignInWithGoogleOption googleIdOption = new GetSignInWithGoogleOption + .Builder(clientId) + .setNonce(generateNonce()) + .build(); - public static void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - Log.d(TAG, "onActivityResult called with requestCode: " + requestCode + ", resultCode: " + resultCode); + GetCredentialRequest request = new GetCredentialRequest.Builder().addCredentialOption(googleIdOption).build(); - if (requestCode == RC_SIGN_IN) { - Log.d(TAG, "Handling Google Sign-In result"); - Task task = GoogleSignIn.getSignedInAccountFromIntent(data); - handleSignInResult(task); - } + CredentialManager credentialManager = CredentialManager.create(context); + credentialManager.getCredentialAsync( + context, + request, + null, + Executors.newSingleThreadExecutor(), + new CredentialManagerCallback() { + @Override + public void onResult(GetCredentialResponse getCredentialResponse) { + handleGetCredentialResponse(getCredentialResponse); + } + + @Override + public void onError(@NonNull GetCredentialException e) { + Log.e(TAG, "Error getting credential", e); + if (e instanceof GetCredentialCustomException) { + GetCredentialCustomException customException = (GetCredentialCustomException) e; + Log.e(TAG, "Custom Exception Type: " + customException.getType()); + } + + UnityPlayer.UnitySendMessage("NativeGoogleSignInReceiver", "HandleError", "Error"); + } + } + ); } - private static void handleSignInResult(Task completedTask) { - try { - GoogleSignInAccount account = completedTask.getResult(ApiException.class); - String token = account.getIdToken(); - - Log.d(TAG, "Sign-In success: ID token = " + token); - UnityPlayer.UnitySendMessage("NativeGoogleSignInReceiver", "HandleIdToken", token); - } catch (ApiException e) { - Log.e(TAG, "Sign-In failed: code=" + e.getStatusCode() + " message=" + e.getMessage(), e); - UnityPlayer.UnitySendMessage("NativeGoogleSignInReceiver", "HandleError", e.getMessage()); + private static void handleGetCredentialResponse(GetCredentialResponse getCredentialResponse) { + Credential credential = getCredentialResponse.getCredential(); + if (credential instanceof CustomCredential && GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.getType())) { + try { + GoogleIdTokenCredential idTokenCredential = GoogleIdTokenCredential.createFrom(credential.getData()); + String idToken = idTokenCredential.getIdToken(); + + UnityPlayer.UnitySendMessage("NativeGoogleSignInReceiver", "HandleIdToken", idToken); + } catch (Exception e) { + Log.e(TAG, "Failed to parse Google ID token response", e); + } + } else { + Log.e(TAG, "Unexpected credential type"); } } + + private static String generateNonce() { + SecureRandom random = new SecureRandom(); + byte[] nonce = new byte[32]; + random.nextBytes(nonce); + return Base64.getEncoder().encodeToString(nonce); + } } \ No newline at end of file diff --git a/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java b/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java deleted file mode 100644 index efaf1ec2e..000000000 --- a/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package xyz.sequence; - -import android.content.Intent; -import android.os.Bundle; -import com.unity3d.player.UnityPlayerActivity; - -public class SequenceUnityActivity extends UnityPlayerActivity { - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - GoogleSignInPlugin.onActivityResult(requestCode, resultCode, data); - } -} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java.meta b/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java.meta deleted file mode 100644 index 7293794bf..000000000 --- a/Packages/Sequence-Unity/Plugins/Android/SequenceUnityActivity.java.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 28448b703c874602ad0cb08c3ee9e907 -timeCreated: 1749461455 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs index 3d9326865..470e78f26 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs @@ -8,17 +8,20 @@ public class NativeGoogleSignIn { private AndroidJavaClass _pluginClass; private NativeGoogleSignInReceiver _receiver; + + private string _clientId; public NativeGoogleSignIn(string clientId) { - _pluginClass = new AndroidJavaClass("xyz.sequence.GoogleSignInPlugin"); - var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); - var activity = unityPlayer.GetStatic("currentActivity"); - _pluginClass.CallStatic("initialize", activity, clientId); + _clientId = clientId; } public async Task SignIn() { + _pluginClass = new AndroidJavaClass("xyz.sequence.GoogleSignInPlugin"); + var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); + var activity = unityPlayer.GetStatic("currentActivity"); + var done = false; var idToken = string.Empty; @@ -39,12 +42,12 @@ public async Task SignIn() Debug.LogError($"Error during native google sign-in: {error}"); }); - _pluginClass.CallStatic("signIn"); + _pluginClass.CallStatic("signIn", activity, _clientId); while (!done) await Task.Delay(200); - Debug.Log($"SignIn Done"); - GameObject.Destroy(_receiver); + + GameObject.Destroy(_receiver.gameObject); return idToken; } } From 17edbdf12b09d290a1f3a1b36a93413b5399f683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20Gr=C3=BCning?= Date: Mon, 9 Jun 2025 12:36:02 +0200 Subject: [PATCH 3/6] version bump, back to old unity activity --- Assets/Plugins/Android/AndroidManifest.xml | 2 +- Packages/Sequence-Unity/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Plugins/Android/AndroidManifest.xml b/Assets/Plugins/Android/AndroidManifest.xml index b634a79a4..be55bd8f1 100644 --- a/Assets/Plugins/Android/AndroidManifest.xml +++ b/Assets/Plugins/Android/AndroidManifest.xml @@ -5,7 +5,7 @@ > diff --git a/Packages/Sequence-Unity/package.json b/Packages/Sequence-Unity/package.json index 6a639c536..f78c97484 100644 --- a/Packages/Sequence-Unity/package.json +++ b/Packages/Sequence-Unity/package.json @@ -1,6 +1,6 @@ { "name": "xyz.0xsequence.waas-unity", - "version": "4.2.3", + "version": "4.2.4", "displayName": "Sequence Embedded Wallet SDK", "description": "A Unity SDK for Sequence APIs", "unity": "2021.3", From a128ef11f0d3a2979923d7e1762cedd11ab3e381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20Gr=C3=BCning?= Date: Mon, 9 Jun 2025 14:51:25 +0200 Subject: [PATCH 4/6] integrated native android plugin into OpenIdAuthenticator --- .../Boilerplates/Login/SequenceLoginWindow.cs | 2 +- .../Authentication/OpenIdAuthenticator.cs | 33 ++++++++++++++++--- .../SignInWithGoogle/NativeGoogleSignIn.cs | 3 +- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Boilerplates/Login/SequenceLoginWindow.cs b/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Boilerplates/Login/SequenceLoginWindow.cs index 040431a1f..e6440eccc 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Boilerplates/Login/SequenceLoginWindow.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Boilerplates/Login/SequenceLoginWindow.cs @@ -201,7 +201,7 @@ private void AccountFederated(Account account) private void OnApplicationFocus(bool hasFocus) { -#if !UNITY_IOS +#if !UNITY_IOS && !UNITY_ANDROID if (hasFocus) { StartCoroutine(DisableLoadingScreenIfNotLoggingIn()); diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/OpenIdAuthenticator.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/OpenIdAuthenticator.cs index 4f22f51c3..a4a49a007 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/OpenIdAuthenticator.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/OpenIdAuthenticator.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Net.Sockets; using System.Collections.Generic; +using System.Threading.Tasks; using Sequence.Config; using Sequence.Utils; using UnityEngine; @@ -95,11 +96,12 @@ public void GoogleSignIn() { throw SequenceConfig.MissingConfigError("Google Client Id"); } - - string state = GenerateState(LoginMethod.Google); - string googleSignInUrl = GenerateSignInUrl("https://accounts.google.com/o/oauth2/auth", GoogleClientId, state); - _browser.SetState(state); - _browser.Authenticate(googleSignInUrl, ReverseClientId(GoogleClientId)); + +#if UNITY_ANDROID && !UNITY_EDITOR + SignInWithGoogleNativePlugin(); +#else + SignInWithGoogleExternalBrowser(); +#endif } catch (Exception e) { @@ -107,6 +109,27 @@ public void GoogleSignIn() } } + private async Task SignInWithGoogleNativePlugin() + { + var nativeHandler = new NativeGoogleSignIn(GoogleClientId); + var idToken = await nativeHandler.SignIn(); + if (string.IsNullOrEmpty(idToken)) + { + OnSignInFailed?.Invoke($"Google sign in error using native android plugin.", LoginMethod.Google); + return; + } + + InvokeSignedIn(new OpenIdAuthenticationResult(idToken, LoginMethod.Google)); + } + + private void SignInWithGoogleExternalBrowser() + { + var state = GenerateState(LoginMethod.Google); + var googleSignInUrl = GenerateSignInUrl("https://accounts.google.com/o/oauth2/auth", GoogleClientId, state); + _browser.SetState(state); + _browser.Authenticate(googleSignInUrl, ReverseClientId(GoogleClientId)); + } + private string ReverseClientId(string clientId) { string[] parts = clientId.Split('.'); diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs index 470e78f26..d0d32d423 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Authentication/SignInWithGoogle/NativeGoogleSignIn.cs @@ -28,12 +28,11 @@ public async Task SignIn() _receiver = NativeGoogleSignInReceiver.Create(); Assert.IsNotNull(_receiver, "NativeGoogleSignInReceiver not initialized"); - Debug.Log($"SignIn"); + _receiver.OnIdTokenReceived.AddListener(receivedIdToken => { idToken = receivedIdToken; done = true; - Debug.Log($"receivedIdToken: {receivedIdToken}"); }); _receiver.OnSignInFailed.AddListener(error => From a97e5c251fe02970a26b232dc8622e6927b48dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20Gr=C3=BCning?= Date: Wed, 11 Jun 2025 14:17:10 +0200 Subject: [PATCH 5/6] automatically copying android plugin files into the Assets/Plugins directory --- .../Editor/AndroidDependencyManager.cs | 80 +++++++++++++++---- .../Plugins/Android/AndroidKeyBridge.java | 27 +------ .../Android/AndroidKeyBridge.java.meta | 4 +- .../Android/GoogleSignInPlugin.java.meta | 55 ++++++++++++- .../Samples~/AndroidSecureStorage.meta | 8 -- .../AndroidSecureStorage/Plugins.meta | 8 -- .../AndroidSecureStorage/Plugins/Android.meta | 8 -- 7 files changed, 122 insertions(+), 68 deletions(-) rename Packages/Sequence-Unity/{Sequence/Samples~/AndroidSecureStorage => }/Plugins/Android/AndroidKeyBridge.java (66%) rename Packages/Sequence-Unity/{Sequence/Samples~/AndroidSecureStorage => }/Plugins/Android/AndroidKeyBridge.java.meta (97%) delete mode 100644 Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage.meta delete mode 100644 Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins.meta delete mode 100644 Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android.meta diff --git a/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs b/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs index fc1998a2b..86ae09283 100644 --- a/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs +++ b/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs @@ -17,7 +17,8 @@ namespace Sequence.Editor public class AndroidDependencyManager : IPreprocessBuildWithReport { public const string SecureStoragePluginFilename = "AndroidKeyBridge.java"; - + public const string SequenceGoogleSignInPluginFilename = "GoogleSignInPlugin.java"; + private const string RelevantDocsUrl = "https://docs.sequence.xyz/sdk/unity/onboard/recovering-sessions#android"; @@ -26,38 +27,85 @@ public class AndroidDependencyManager : IPreprocessBuildWithReport public void OnPreprocessBuild(BuildReport report) { #if UNITY_ANDROID - BuildTarget target = report.summary.platform; - SequenceConfig config = SequenceConfig.GetConfig(); + var target = report.summary.platform; + var config = SequenceConfig.GetConfig(); + + CheckPlugin(SecureStoragePluginFilename, config.StoreSessionPrivateKeyInSecureStorage, target); + CheckPlugin(SequenceGoogleSignInPluginFilename, true, target); +#endif + } + + [MenuItem("Sequence/Android Plugins")] + public static void Test() + { + CheckPlugin(SecureStoragePluginFilename, true, BuildTarget.Android); + CheckPlugin(SequenceGoogleSignInPluginFilename, true, BuildTarget.Android); + AssetDatabase.Refresh(); + } - string[] files = Directory.GetFiles("Assets", SecureStoragePluginFilename, SearchOption.AllDirectories); - string pluginPath = files.FirstOrDefault(); + private static void CheckPlugin(string fileName, bool enable, BuildTarget platform) + { + var existingFiles = Directory.GetFiles("Assets", fileName, SearchOption.AllDirectories); + var pluginPath = existingFiles.FirstOrDefault(); + if (string.IsNullOrEmpty(pluginPath)) { - if (config.StoreSessionPrivateKeyInSecureStorage) - { - ShowWarning($"Secure Storage plugin '{SecureStoragePluginFilename}' not found in project. Please make sure you have imported it via Samples in Package Manager"); - } + if (enable) + TryCopyPlugin(fileName); + return; } - PluginImporter pluginImporter = AssetImporter.GetAtPath(pluginPath) as PluginImporter; - if (pluginImporter == null) + var pluginImporter = AssetImporter.GetAtPath(pluginPath) as PluginImporter; + if (!pluginImporter) { ShowWarning($"Unable to create {nameof(PluginImporter)} instance at path: {pluginPath}"); return; } - pluginImporter.SetCompatibleWithPlatform(target, config.StoreSessionPrivateKeyInSecureStorage); + pluginImporter.SetCompatibleWithPlatform(platform, enable); pluginImporter.SaveAndReimport(); - Debug.Log( - $"Secure Storage plugin compatibility set to {config.StoreSessionPrivateKeyInSecureStorage} for path: {pluginPath}"); -#endif + + Debug.Log($"Plugin {fileName} compatibility set to {enable} for path: {pluginPath}"); + } + + private static void TryCopyPlugin(string fileName) + { + var targetPath = Path.Combine(Application.dataPath, "Plugins/Android", fileName); + var sourcePath = FindFileInPackages("Plugins/Android/" + fileName); + + var directory = Path.GetDirectoryName(targetPath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + File.Copy(sourcePath, targetPath); } - private void ShowWarning(string warning) + private static void ShowWarning(string warning) { Debug.LogWarning(warning); SequenceWarningPopup.ShowWindow(new List() {warning}, RelevantDocsUrl); } + + private static string FindFileInPackages(string relativeFilePath) + { + var filePath = $"Packages/Sequence-Unity/{relativeFilePath}"; + if (File.Exists(filePath)) + return filePath; + + var directories = Directory.GetDirectories("Library/PackageCache/", "xyz.0xsequence.waas-unity*", SearchOption.TopDirectoryOnly); + if (directories.Length > 0) + { + var packageDir = directories[0]; + var packageJsonPath = Path.Combine(packageDir, relativeFilePath); + if (File.Exists(packageJsonPath)) + { + return packageJsonPath; + } + } + + ShowWarning("Plugin file not found: " + relativeFilePath); + return null; + } } } diff --git a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android/AndroidKeyBridge.java b/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java similarity index 66% rename from Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android/AndroidKeyBridge.java rename to Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java index ce23aef09..7409a8ccd 100644 --- a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android/AndroidKeyBridge.java +++ b/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java @@ -25,28 +25,15 @@ private AndroidKeyBridge() public void init(Context context) { - if (context == null) { - Log.w("AndroidKeyBridge", "Provided context is null, trying to get UnityPlayer.currentActivity"); - try { - context = com.unity3d.player.UnityPlayer.currentActivity; - } catch (Exception e) { - Log.e("AndroidKeyBridge", "Failed to get UnityPlayer.currentActivity", e); - } - } - - if (context == null) { - Log.e("AndroidKeyBridge", "Context is still null, cannot initialize AndroidKeyBridge"); - return; - } - this.context = context; - if (masterKey == null || sharedPreferences == null) { + if (masterKey == null) { try { masterKey = new MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build(); + sharedPreferences = EncryptedSharedPreferences.create( context, "secret_shared_prefs", @@ -55,9 +42,9 @@ public void init(Context context) EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ); } catch (GeneralSecurityException e){ - Log.d("AndroidKeyBridge", "Encountered error when initializing AndroidKeyBridge: " + e.getMessage()); + Log.d("Exception", e.getMessage()); } catch (IOException e){ - Log.d("AndroidKeyBridge", "Encountered error when initializing AndroidKeyBridge: " + e.getMessage()); + Log.d("Exception", e.getMessage()); } } } @@ -87,17 +74,11 @@ public static String GetKeychainValue(String key) private void RunSaveKeychainValue(String key, String value) { - if (masterKey == null || sharedPreferences == null) { - init(context); - } sharedPreferences.edit().putString(key, value).apply(); } private String RunGetKeychainValue(String key) { - if (masterKey == null || sharedPreferences == null) { - init(context); - } return sharedPreferences.getString(key, ""); } } diff --git a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android/AndroidKeyBridge.java.meta b/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java.meta similarity index 97% rename from Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android/AndroidKeyBridge.java.meta rename to Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java.meta index f802b11c4..5e66a38cf 100644 --- a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android/AndroidKeyBridge.java.meta +++ b/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java.meta @@ -16,7 +16,7 @@ PluginImporter: second: enabled: 0 settings: - Exclude Android: 0 + Exclude Android: 1 Exclude Editor: 1 Exclude Linux64: 1 Exclude OSXUniversal: 1 @@ -28,7 +28,7 @@ PluginImporter: - first: Android: Android second: - enabled: 1 + enabled: 0 settings: CPU: ARMv7 - first: diff --git a/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java.meta b/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java.meta index d3d7ed41b..95829f75c 100644 --- a/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java.meta +++ b/Packages/Sequence-Unity/Plugins/Android/GoogleSignInPlugin.java.meta @@ -6,16 +6,30 @@ PluginImporter: iconMap: {} executionOrder: {} defineConstraints: [] - isPreloaded: 0 + isPreloaded: 1 isOverridable: 1 isExplicitlyReferenced: 0 validateReferences: 1 platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 - first: Android: Android second: - enabled: 1 - settings: {} + enabled: 0 + settings: + CPU: ARMv7 - first: Any: second: @@ -26,7 +40,42 @@ PluginImporter: second: enabled: 0 settings: + CPU: AnyCPU DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: userData: assetBundleName: assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage.meta b/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage.meta deleted file mode 100644 index c2b7a0881..000000000 --- a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: a925fdce6a58341318192029e3f7480b -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins.meta b/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins.meta deleted file mode 100644 index 4b6dafe1b..000000000 --- a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 67c08eabe76b14fa29d08a69d8824664 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android.meta b/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android.meta deleted file mode 100644 index 273257609..000000000 --- a/Packages/Sequence-Unity/Sequence/Samples~/AndroidSecureStorage/Plugins/Android.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d6320c7ac81bb42128567b31e7c3f048 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From eb85d33c4463f0d28caeba404497a6ec30d431e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20Gr=C3=BCning?= Date: Wed, 11 Jun 2025 14:38:13 +0200 Subject: [PATCH 6/6] check if gradle template includes required dependencies --- Assets/Plugins/Android/AndroidKeyBridge.java | 84 +++++++++++++++++ .../Android/AndroidKeyBridge.java.meta | 32 +++++++ .../Plugins/Android/GoogleSignInPlugin.java | 91 +++++++++++++++++++ .../Android/GoogleSignInPlugin.java.meta | 32 +++++++ .../Editor/AndroidDependencyManager.cs | 35 ++++++- 5 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 Assets/Plugins/Android/AndroidKeyBridge.java create mode 100644 Assets/Plugins/Android/AndroidKeyBridge.java.meta create mode 100644 Assets/Plugins/Android/GoogleSignInPlugin.java create mode 100644 Assets/Plugins/Android/GoogleSignInPlugin.java.meta diff --git a/Assets/Plugins/Android/AndroidKeyBridge.java b/Assets/Plugins/Android/AndroidKeyBridge.java new file mode 100644 index 000000000..7409a8ccd --- /dev/null +++ b/Assets/Plugins/Android/AndroidKeyBridge.java @@ -0,0 +1,84 @@ +package xyz.sequence; + +import android.content.SharedPreferences; +import android.util.Log; +import android.content.Context; +import android.widget.Toast; +import androidx.security.crypto.EncryptedSharedPreferences; +import androidx.security.crypto.MasterKey; + +import java.io.IOException; +import java.security.GeneralSecurityException; + + +public class AndroidKeyBridge { + + private static AndroidKeyBridge instance = null; + private Context context = null; + private MasterKey masterKey = null; + private SharedPreferences sharedPreferences; + + private AndroidKeyBridge() + { + + } + + public void init(Context context) + { + this.context = context; + + if (masterKey == null) { + + try { + masterKey = new MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + sharedPreferences = EncryptedSharedPreferences.create( + context, + "secret_shared_prefs", + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ); + } catch (GeneralSecurityException e){ + Log.d("Exception", e.getMessage()); + } catch (IOException e){ + Log.d("Exception", e.getMessage()); + } + } + } + + public void destroy() + { + this.context = null; + } + + public static AndroidKeyBridge getInstance() + { + if (instance == null) + instance = new AndroidKeyBridge(); + + return instance; + } + + public static void SaveKeychainValue(String key, String value) + { + getInstance().RunSaveKeychainValue(key, value); + } + + public static String GetKeychainValue(String key) + { + return getInstance().RunGetKeychainValue(key); + } + + private void RunSaveKeychainValue(String key, String value) + { + sharedPreferences.edit().putString(key, value).apply(); + } + + private String RunGetKeychainValue(String key) + { + return sharedPreferences.getString(key, ""); + } +} diff --git a/Assets/Plugins/Android/AndroidKeyBridge.java.meta b/Assets/Plugins/Android/AndroidKeyBridge.java.meta new file mode 100644 index 000000000..e6ff4d6c5 --- /dev/null +++ b/Assets/Plugins/Android/AndroidKeyBridge.java.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 12f42058bb5d84be683542e726ecac4a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Android/GoogleSignInPlugin.java b/Assets/Plugins/Android/GoogleSignInPlugin.java new file mode 100644 index 000000000..39ccce595 --- /dev/null +++ b/Assets/Plugins/Android/GoogleSignInPlugin.java @@ -0,0 +1,91 @@ +package xyz.sequence; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.credentials.Credential; +import androidx.credentials.CredentialManager; +import androidx.credentials.CredentialManagerCallback; +import androidx.credentials.CustomCredential; +import androidx.credentials.GetCredentialRequest; +import androidx.credentials.GetCredentialResponse; +import androidx.credentials.exceptions.GetCredentialException; +import androidx.credentials.exceptions.NoCredentialException; +import androidx.credentials.exceptions.GetCredentialCustomException; + +import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption; +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential; + +import java.security.SecureRandom; +import java.util.Base64; +import java.util.concurrent.Executors; + +import com.unity3d.player.UnityPlayer; + +public class GoogleSignInPlugin { + private static final String TAG = "SequenceGoogleSignIn"; + + public static void signIn(Context context, String clientId) + { + getCredentialAsync(context, clientId); + } + + private static void getCredentialAsync(Context context, String clientId) + { + GetSignInWithGoogleOption googleIdOption = new GetSignInWithGoogleOption + .Builder(clientId) + .setNonce(generateNonce()) + .build(); + + GetCredentialRequest request = new GetCredentialRequest.Builder().addCredentialOption(googleIdOption).build(); + + CredentialManager credentialManager = CredentialManager.create(context); + credentialManager.getCredentialAsync( + context, + request, + null, + Executors.newSingleThreadExecutor(), + new CredentialManagerCallback() { + @Override + public void onResult(GetCredentialResponse getCredentialResponse) { + handleGetCredentialResponse(getCredentialResponse); + } + + @Override + public void onError(@NonNull GetCredentialException e) { + Log.e(TAG, "Error getting credential", e); + if (e instanceof GetCredentialCustomException) { + GetCredentialCustomException customException = (GetCredentialCustomException) e; + Log.e(TAG, "Custom Exception Type: " + customException.getType()); + } + + UnityPlayer.UnitySendMessage("NativeGoogleSignInReceiver", "HandleError", "Error"); + } + } + ); + } + + private static void handleGetCredentialResponse(GetCredentialResponse getCredentialResponse) { + Credential credential = getCredentialResponse.getCredential(); + if (credential instanceof CustomCredential && GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.getType())) { + try { + GoogleIdTokenCredential idTokenCredential = GoogleIdTokenCredential.createFrom(credential.getData()); + String idToken = idTokenCredential.getIdToken(); + + UnityPlayer.UnitySendMessage("NativeGoogleSignInReceiver", "HandleIdToken", idToken); + } catch (Exception e) { + Log.e(TAG, "Failed to parse Google ID token response", e); + } + } else { + Log.e(TAG, "Unexpected credential type"); + } + } + + private static String generateNonce() { + SecureRandom random = new SecureRandom(); + byte[] nonce = new byte[32]; + random.nextBytes(nonce); + return Base64.getEncoder().encodeToString(nonce); + } +} \ No newline at end of file diff --git a/Assets/Plugins/Android/GoogleSignInPlugin.java.meta b/Assets/Plugins/Android/GoogleSignInPlugin.java.meta new file mode 100644 index 000000000..ca7afbcce --- /dev/null +++ b/Assets/Plugins/Android/GoogleSignInPlugin.java.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 16830add107da48d3a43989827a9fe12 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs b/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs index 86ae09283..070cc189c 100644 --- a/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs +++ b/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs @@ -6,6 +6,7 @@ using System.Linq; using Sequence.Config; using System.IO; +using System.Text.RegularExpressions; namespace Sequence.Editor { @@ -18,6 +19,16 @@ public class AndroidDependencyManager : IPreprocessBuildWithReport { public const string SecureStoragePluginFilename = "AndroidKeyBridge.java"; public const string SequenceGoogleSignInPluginFilename = "GoogleSignInPlugin.java"; + public const string GradleTemplateFilename = "mainTemplate.gradle"; + + private static string[] GradleDependencies = new[] + { + "androidx.security:security-crypto", + "com.google.android.gms:play-services-auth", + "androidx.credentials:credentials", + "androidx.credentials:credentials-play-services-auth", + "com.google.android.libraries.identity.googleid:googleid", + }; private const string RelevantDocsUrl = "https://docs.sequence.xyz/sdk/unity/onboard/recovering-sessions#android"; @@ -32,6 +43,8 @@ public void OnPreprocessBuild(BuildReport report) CheckPlugin(SecureStoragePluginFilename, config.StoreSessionPrivateKeyInSecureStorage, target); CheckPlugin(SequenceGoogleSignInPluginFilename, true, target); + CheckGradleTemplateDependencies(); + AssetDatabase.Refresh(); #endif } @@ -40,7 +53,6 @@ public static void Test() { CheckPlugin(SecureStoragePluginFilename, true, BuildTarget.Android); CheckPlugin(SequenceGoogleSignInPluginFilename, true, BuildTarget.Android); - AssetDatabase.Refresh(); } private static void CheckPlugin(string fileName, bool enable, BuildTarget platform) @@ -80,6 +92,27 @@ private static void TryCopyPlugin(string fileName) File.Copy(sourcePath, targetPath); } + + private static void CheckGradleTemplateDependencies() + { + var existingFiles = Directory.GetFiles("Assets", GradleTemplateFilename, SearchOption.AllDirectories); + var gradleFilePath = existingFiles.FirstOrDefault(); + + if (string.IsNullOrEmpty(gradleFilePath) || !File.Exists(gradleFilePath)) + { + ShowWarning("mainTemplate.gradle does not exist. Trigger a build once to generate it."); + return; + } + + var content = File.ReadAllText(gradleFilePath); + foreach (var dep in GradleDependencies) + { + if (!content.Contains(dep)) + { + ShowWarning($"{GradleTemplateFilename} does not include '{dep}' as a dependency."); + } + } + } private static void ShowWarning(string warning) {