diff --git a/trivialkart/trivialkart-unity/Assets/Scripts/AuthManager.cs b/trivialkart/trivialkart-unity/Assets/Scripts/AuthManager.cs index 19a5f2b2..b0505821 100644 --- a/trivialkart/trivialkart-unity/Assets/Scripts/AuthManager.cs +++ b/trivialkart/trivialkart-unity/Assets/Scripts/AuthManager.cs @@ -51,12 +51,10 @@ public class AuthManager : MonoBehaviour private string customJwtToken; #if PGS_V2 - private GoogleSignInUser googleUser; - - // --- NEW VARIABLES for main-thread dispatching --- + // V2 (CredMan) Specific Variables private volatile bool googleTaskComplete = false; - private Exception googleSignInException = null; private string authCodeToExchange = null; + private string credManError = null; #endif public string serverUrl; @@ -132,18 +130,8 @@ private void Awake() PlayGamesPlatform.DebugLogEnabled = true; PlayGamesPlatform.Activate(); #elif PGS_V2 - // --- V2 INITIALIZATION --- - statusText.text = "Initializing Google Sign-In..."; - GoogleSignIn.Configuration = new GoogleSignInConfiguration - { - WebClientId = webClientId, - ForceTokenRefresh = true, - - UseGameSignIn = false, - RequestEmail = true, - RequestAuthCode = true, - }; - + // V2 Initialization + statusText.text = "Initializing..."; PlayGamesPlatform.DebugLogEnabled = true; #endif @@ -171,57 +159,52 @@ private void Awake() #if PGS_V1 PlayGamesPlatform.Instance.Authenticate(OnSilentSignInFinished, true); #elif PGS_V2 - GoogleSignIn.DefaultInstance.SignInSilently().ContinueWith(OnGoogleSignInComplete); + if (TryLoadSession()) + { + Debug.Log("Valid session found. Skipping CredMan."); + ShowGamePanel(); + SignInToPlayGamesServices(); // Achievements only + } + else if (PlayerPrefs.GetInt("UserSignedOut", 0) == 0) + { + Debug.Log("Attempting CredMan Silent Sign-In..."); + StartSignIn(false); // Silent Mode + } + else + { + ShowStartPanel(); + } #endif } private void Update() { #if PGS_V2 + // V2 Main Thread Dispatcher if (googleTaskComplete) { - googleTaskComplete = false; // Reset flag - - if (authCodeToExchange != null && googleSignInException == null) + googleTaskComplete = false; + if (!string.IsNullOrEmpty(credManError)) { - // --- Success case --- - Debug.Log($"Google Sign-In successful for: {this.googleUser.Email}"); - Debug.Log($"Retrieved Server Auth Code. Sending to backend..."); - statusText.text = "Connecting to game server..."; - StartCoroutine(ExchangeAuthcodeAndLink(authCodeToExchange)); - } - else - { - if (googleSignInException != null) + if (credManError == "SilentFailed") { - var e = googleSignInException.GetBaseException(); - - if (e is GoogleSignIn.SignInException signInException) - { - Debug.Log($"Google Sign-In Error: {signInException.Status}"); - if (signInException.Status == GoogleSignInStatusCode.Canceled) - { - statusText.text = "Sign-in cancelled."; - } - } - else - { - Debug.Log($"Google Sign-In Task Error: {e.Message}"); - if (e.Message.Contains("Canceled")) - { - statusText.text = "Sign-in cancelled."; - } - else - { - statusText.text = "Sign-in failed. Please try again."; - } - } + Debug.Log("CredMan Silent Sign-In failed."); + statusText.text = "Please sign in."; + } + else + { + Debug.LogError("CredMan Error: " + credManError); + statusText.text = "Sign-in Failed."; } - ShowStartPanel(); } - - googleSignInException = null; + else if (!string.IsNullOrEmpty(authCodeToExchange)) + { + Debug.Log("Got Auth Code. Exchanging..."); + statusText.text = "Connecting to server..."; + StartCoroutine(ExchangeAuthcodeAndLink(authCodeToExchange)); + } + credManError = null; authCodeToExchange = null; } #endif @@ -376,30 +359,57 @@ private IEnumerator VerifyAndLinkGoogleAccount(string idToken) #endif #if PGS_V2 - private void OnGoogleSignInComplete(Task task) + private bool TryLoadSession() { - try - { - if (task.IsFaulted) - { - googleSignInException = task.Exception; - } - else if (task.IsCanceled) - { - googleSignInException = new System.Exception("Google Sign-In Canceled."); - } - else - { - this.googleUser = task.Result; - this.authCodeToExchange = this.googleUser.AuthCode; - } - } - catch (System.Exception ex) - { - googleSignInException = ex; - } + string token = PlayerPrefs.GetString("Cached_JWT", null); + if (string.IsNullOrEmpty(token)) return false; + + this.customJwtToken = token; + string email = PlayerPrefs.GetString("Cached_Email", ""); + int count = PlayerPrefs.GetInt("Cached_Count", 0); + + statusText.text = $"Signed in as: {email}"; + incText.text = count.ToString("000"); + return true; + } + + private void SaveSession(LinkResponse data) + { + PlayerPrefs.SetString("Cached_JWT", data.jwtToken); + PlayerPrefs.SetString("Cached_Email", data.email); + PlayerPrefs.SetString("Cached_ID", data.inGameAccountID); + PlayerPrefs.SetInt("Cached_Count", data.inGameCount); + PlayerPrefs.SetInt("UserSignedOut", 0); + PlayerPrefs.Save(); + this.customJwtToken = data.jwtToken; + } + + private void ClearSession() + { + PlayerPrefs.DeleteKey("Cached_JWT"); + PlayerPrefs.DeleteKey("Cached_Email"); + PlayerPrefs.DeleteKey("Cached_ID"); + PlayerPrefs.DeleteKey("Cached_Count"); + PlayerPrefs.SetInt("UserSignedOut", 1); + PlayerPrefs.Save(); + this.customJwtToken = null; + } + + public void StartSignIn(bool interactive) + { + AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); + AndroidJavaObject currentActivity = unityPlayer.GetStatic("currentActivity"); + AndroidJavaClass bridge = new AndroidJavaClass("com.gamesamples.trivialkartunity.CredManBridge"); - googleTaskComplete = true; + string methodName = interactive ? "signInInteractive" : "signInSilent"; + bridge.CallStatic(methodName, currentActivity, this.webClientId); + } + + public void OnSignInSuccess(string token) { authCodeToExchange = token; googleTaskComplete = true; } + public void OnSignInError(string error) + { + credManError = error; + googleTaskComplete = true; } private IEnumerator ExchangeAuthcodeAndLink(string serverAuthCode) @@ -422,8 +432,6 @@ private IEnumerator ExchangeAuthcodeAndLink(string serverAuthCode) Debug.LogError($"Backend Error: {request.error}"); Debug.LogError($"Response: {request.downloadHandler.text}"); statusText.text = "Failed to link account. Server error."; - - GoogleSignIn.DefaultInstance.SignOut(); ShowStartPanel(); } else @@ -443,28 +451,9 @@ private IEnumerator ExchangeAuthcodeAndLink(string serverAuthCode) } } - // This is called from ExchangeAuthcodeAndLink (main thread), so it's safe. private void SignInToPlayGamesServices() { - Debug.Log("Attempting silent sign-in to Play Games Services..."); - statusText.text = "Loading game services..."; - - PlayGamesPlatform.Instance.Authenticate((SignInStatus status) => - { - if (status == SignInStatus.Success) - { - Debug.Log("Play Games Services silent sign-in successful!"); - if (this.googleUser != null) - { - statusText.text = $"Signed in as: {this.googleUser.Email}"; - } - } - else - { - Debug.LogWarning("Play Games Services silent sign-in failed: " + status); - statusText.text = "Game services (achievements) failed to load."; - } - }); + PlayGamesPlatform.Instance.Authenticate((SignInStatus status) => { Debug.Log("PGS Auth: " + status); }); } #endif @@ -528,8 +517,7 @@ private void GetStartedClicked() #if PGS_V1 PlayGamesPlatform.Instance.Authenticate(ProcessAuthenticationResult, false); #elif PGS_V2 - // This is safe because OnGoogleSignInComplete now dispatches to Update() - GoogleSignIn.DefaultInstance.SignIn().ContinueWith(OnGoogleSignInComplete); + StartSignIn(true); #endif } @@ -540,8 +528,7 @@ private void OnSignInWithGoogleClicked() #if PGS_V1 PlayGamesPlatform.Instance.Authenticate(ProcessAuthenticationResult, false); #elif PGS_V2 - // This is safe because OnGoogleSignInComplete now dispatches to Update() - GoogleSignIn.DefaultInstance.SignIn().ContinueWith(OnGoogleSignInComplete); + StartSignIn(true); #endif } @@ -642,8 +629,7 @@ private void OnSignOutClicked() PlayGamesPlatform.Instance.SignOut(); } #elif PGS_V2 - GoogleSignIn.DefaultInstance.SignOut(); - googleUser = null; + ClearSession(); #endif if (FB.IsLoggedIn) diff --git a/trivialkart/trivialkart-unity/Assets/Scripts/Utils/CredManBridge.java b/trivialkart/trivialkart-unity/Assets/Scripts/Utils/CredManBridge.java new file mode 100644 index 00000000..69c59015 --- /dev/null +++ b/trivialkart/trivialkart-unity/Assets/Scripts/Utils/CredManBridge.java @@ -0,0 +1,185 @@ +// Copyright 2022 Google LLC +// +// 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 +// +// https://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 com.gamesamples.trivialkartunity; + +import android.accounts.Account; +import android.content.Context; +import android.util.Log; +import android.os.CancellationSignal; + +import androidx.credentials.CredentialManager; +import androidx.credentials.GetCredentialRequest; +import androidx.credentials.GetCredentialResponse; +import androidx.credentials.exceptions.GetCredentialException; +import androidx.credentials.exceptions.NoCredentialException; + +import com.google.android.libraries.identity.googleid.GetGoogleIdOption; +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential; + +import com.google.android.gms.auth.api.identity.AuthorizationClient; +import com.google.android.gms.auth.api.identity.AuthorizationRequest; +import com.google.android.gms.auth.api.identity.AuthorizationResult; +import com.google.android.gms.common.api.ApiException; +import com.google.android.gms.auth.api.identity.Identity; +import com.google.android.gms.common.api.Scope; + +import com.unity3d.player.UnityPlayer; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + + +# [START google_signin_credman_example] + +public class CredManBridge { + + private static final Executor executor = Executors.newSingleThreadExecutor(); + + // --- MODE 1: SILENT SIGN-IN (Called on Awake) --- + // Tries to auto-select an authorized account. If it fails, it does NOT show UI. + public static void signInSilent(Context context, String webClientId) { + CredentialManager credentialManager = CredentialManager.create(context); + CancellationSignal cancellationSignal = new CancellationSignal(); + + Log.d("CredMan", "Attempting Silent Sign-In..."); + + GetGoogleIdOption silentOption = new GetGoogleIdOption.Builder() + .setFilterByAuthorizedAccounts(true) // Strict: Only authorized accounts + .setServerClientId(webClientId) + .setAutoSelectEnabled(true) // Auto-select if possible + .build(); + + GetCredentialRequest silentRequest = new GetCredentialRequest.Builder() + .addCredentialOption(silentOption) + .build(); + + credentialManager.getCredentialAsync( + context, + silentRequest, + cancellationSignal, + executor, + new androidx.credentials.CredentialManagerCallback() { + @Override + public void onResult(GetCredentialResponse result) { + Log.d("CredMan", "Silent Sign-In Successful!"); + handleSignInResult(context, result, webClientId); + } + + @Override + public void onError(GetCredentialException e) { + // Send a specific error code so Unity knows to just stay on the Start Screen + Log.d("CredMan", "Silent sign-in failed. Keeping UI hidden."); + UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "SilentFailed"); + } + } + ); + } + + // --- MODE 2: INTERACTIVE SIGN-IN (Called on Button Click) --- + // Forces the Account Selection / "Add Account" sheet to appear. + public static void signInInteractive(Context context, String webClientId) { + CredentialManager credentialManager = CredentialManager.create(context); + CancellationSignal cancellationSignal = new CancellationSignal(); + + Log.d("CredMan", "Starting Interactive Sign-In..."); + + GetGoogleIdOption interactiveOption = new GetGoogleIdOption.Builder() + .setFilterByAuthorizedAccounts(false) // Show ALL accounts (and "Add Account") + .setServerClientId(webClientId) + .setAutoSelectEnabled(false) // Force the UI to show + .build(); + + GetCredentialRequest interactiveRequest = new GetCredentialRequest.Builder() + .addCredentialOption(interactiveOption) + .build(); + + credentialManager.getCredentialAsync( + context, + interactiveRequest, + cancellationSignal, + executor, + new androidx.credentials.CredentialManagerCallback() { + @Override + public void onResult(GetCredentialResponse result) { + Log.d("CredMan", "Interactive Sign-In Successful!"); + handleSignInResult(context, result, webClientId); + } + + @Override + public void onError(GetCredentialException e) { + Log.e("CredMan", "Interactive Sign-In Canceled or Failed", e); + UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Canceled"); + } + } + ); + } + + + private static void handleSignInResult(Context context, GetCredentialResponse result, String webClientId) { + try { + GoogleIdTokenCredential credential = GoogleIdTokenCredential.createFrom(result.getCredential().getData()); + String email = credential.getId(); + + Account account = new Account(email, "com.google"); + // Requesting GAMES_LITE scope to check for pre-existing V1 grants + List requestedScopes = Collections.singletonList(new Scope("https://www.googleapis.com/auth/games_lite")); + + AuthorizationRequest authRequest = new AuthorizationRequest.Builder() + .setRequestedScopes(requestedScopes) + .setAccount(account) + .requestOfflineAccess(webClientId) + .build(); + + AuthorizationClient authClient = Identity.getAuthorizationClient(context); + + authClient.authorize(authRequest) + .addOnSuccessListener(authorizationResult -> { + if (authorizationResult.getServerAuthCode() != null) { + // CASE 1: RETURNING USER (Success) + // The user has already granted GAMES_LITE in the past. + // We got the code directly without showing UI. + Log.i("CredMan", "PGS v1: Existing grant found. Returning user detected. Auth Code retrieved."); + UnityPlayer.UnitySendMessage("AuthManager", "OnSignInSuccess", authorizationResult.getServerAuthCode()); + } + else if (authorizationResult.hasResolution()) { + // CASE 2: NEW USER (PendingIntent) + // The user has NOT granted GAMES_LITE before. The API returned a PendingIntent + // (authorizationResult.getPendingIntent()) to show the consent screen. + // As per your flow, we DISCARD this intent and do not show UI. + Log.i("CredMan", "PGS v1: No existing grant (PendingIntent returned). This is a NEW user or they revoked access."); + Log.i("CredMan", "PGS v1: Discarding PendingIntent. Proceeding as New User."); + + // Notify Unity that this is a "New User" so it can trigger V2 logic instead of failing + UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "NewUser_NoGrant"); + } + else { + // Edge Case: No code and no resolution? + Log.e("CredMan", "PGS v1: Authorization success but no Auth Code or Resolution returned."); + UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "No Auth Code returned"); + } + }) + .addOnFailureListener(e -> { + // CASE 3: GENERIC FAILURE + Log.e("CredMan", "PGS v1: Authorization failed completely.", e); + UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Authorization Failed: " + e.getMessage()); + }); + + } catch (Exception e) { + UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Parsing Error: " + e.getMessage()); + } + } +} +# [END google_signin_credman_example] \ No newline at end of file diff --git a/trivialkart/trivialkart-unity/Assets/Scripts/Utils/CredManBridge.java.meta b/trivialkart/trivialkart-unity/Assets/Scripts/Utils/CredManBridge.java.meta new file mode 100644 index 00000000..31224130 --- /dev/null +++ b/trivialkart/trivialkart-unity/Assets/Scripts/Utils/CredManBridge.java.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d9af6602467aa47e9a0807256de7c884 \ No newline at end of file diff --git a/trivialkart/trivialkart-unity/ProjectSettings/AndroidResolverDependencies.xml b/trivialkart/trivialkart-unity/ProjectSettings/AndroidResolverDependencies.xml index 445824e8..3f6d45a0 100644 --- a/trivialkart/trivialkart-unity/ProjectSettings/AndroidResolverDependencies.xml +++ b/trivialkart/trivialkart-unity/ProjectSettings/AndroidResolverDependencies.xml @@ -8,7 +8,7 @@ - + diff --git a/trivialkart/trivialkart-unity/ProjectSettings/GooglePlayGameSettings.txt b/trivialkart/trivialkart-unity/ProjectSettings/GooglePlayGameSettings.txt index fb011469..38e2e522 100644 --- a/trivialkart/trivialkart-unity/ProjectSettings/GooglePlayGameSettings.txt +++ b/trivialkart/trivialkart-unity/ProjectSettings/GooglePlayGameSettings.txt @@ -1,7 +1,7 @@ lastUpgrade=20100 proj.pluginVersion=2.1.0 proj.AppId=1044312393953 -and.BundleId=com.WickedCube.TrivialKart +and.BundleId=com.gamesamples.trivialkartunity proj.classDir=Assets proj.ConstantsClassName=GPGSIds and.ResourceData=%3c%3fxml+version%3d%221.0%22+encoding%3d%22utf-8%22%3f%3e%0a%3c%21--Google+Play+game+services+IDs.+Save+this+file+as+res%2fvalues%2fgames-ids.xml+in+your+project.--%3e%0a%3cresources%3e%0a++%3c%21--app_id--%3e%0a++%3cstring+name%3d%22app_id%22+translatable%3d%22false%22%3e1044312393953%3c%2fstring%3e%0a++%3c%21--package_name--%3e%0a++%3cstring+name%3d%22package_name%22+translatable%3d%22false%22%3ecom.WickedCube.TrivialKart%3c%2fstring%3e%0a++%3c%21--achievement+tk_achievement_drive--%3e%0a++%3cstring+name%3d%22achievement_tk_achievement_drive%22+translatable%3d%22false%22%3eCgkI4ZH6rrIeEAIQAw%3c%2fstring%3e%0a++%3c%21--achievement+tk_achievement_truck--%3e%0a++%3cstring+name%3d%22achievement_tk_achievement_truck%22+translatable%3d%22false%22%3eCgkI4ZH6rrIeEAIQBA%3c%2fstring%3e%0a++%3c%21--leaderboard+tk_leaderboard_distance--%3e%0a++%3cstring+name%3d%22leaderboard_tk_leaderboard_distance%22+translatable%3d%22false%22%3eCgkI4ZH6rrIeEAIQBg%3c%2fstring%3e%0a%3c%2fresources%3e diff --git a/trivialkart/trivialkart-unity/ProjectSettings/ProjectSettings.asset b/trivialkart/trivialkart-unity/ProjectSettings/ProjectSettings.asset index 730051d9..7720f73a 100644 --- a/trivialkart/trivialkart-unity/ProjectSettings/ProjectSettings.asset +++ b/trivialkart/trivialkart-unity/ProjectSettings/ProjectSettings.asset @@ -166,7 +166,7 @@ PlayerSettings: androidMaxAspectRatio: 2.1 androidMinAspectRatio: 1 applicationIdentifier: - Android: com.GoogleLLC.TrivialKart + Android: com.gamesamples.trivialkartunity Standalone: com.Google-LLC.TrivialKart buildNumber: Standalone: 0 @@ -933,7 +933,7 @@ PlayerSettings: framebufferDepthMemorylessMode: 0 qualitySettingsNames: [] projectName: TrivialKart - organizationId: wickedcube_unity + organizationId: gamesamples_unity cloudEnabled: 0 legacyClampBlendShapeWeights: 0 hmiLoadingImage: {fileID: 0}