diff --git a/.gitignore b/.gitignore index 7043d58b0..3f287e6c9 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,8 @@ crashlytics-build.properties chaintest.out TestResults_Edit.xml TestResults_Play.xml +Builds/ +PlatformCompileTestErrors/ # Temporary test files Assets/InitTestScene*.unity* diff --git a/Assets/Plugins/Android/gradleTemplate.properties b/Assets/Plugins/Android/gradleTemplate.properties new file mode 100644 index 000000000..4092513ed --- /dev/null +++ b/Assets/Plugins/Android/gradleTemplate.properties @@ -0,0 +1,8 @@ +org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M +org.gradle.parallel=true +android.enableJetifier=true +android.useAndroidX=true +unityStreamingAssets=**STREAMING_ASSETS** +**ADDITIONAL_PROPERTIES** + +android.enableR8=**MINIFY_WITH_R_EIGHT** \ No newline at end of file diff --git a/Assets/PlayFab/PlayFabEditorExtensions/Editor/Resources/MostRecentPackage.unitypackage.meta b/Assets/Plugins/Android/gradleTemplate.properties.meta similarity index 74% rename from Assets/PlayFab/PlayFabEditorExtensions/Editor/Resources/MostRecentPackage.unitypackage.meta rename to Assets/Plugins/Android/gradleTemplate.properties.meta index c0cdc5250..30f01a2a1 100644 --- a/Assets/PlayFab/PlayFabEditorExtensions/Editor/Resources/MostRecentPackage.unitypackage.meta +++ b/Assets/Plugins/Android/gradleTemplate.properties.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: aa66cce13570c4036afe2e85742962d9 +guid: 8ff2f2a662c2a25499a268faa21d909a DefaultImporter: externalObjects: {} userData: diff --git a/Assets/Plugins/Android/mainTemplate.gradle b/Assets/Plugins/Android/mainTemplate.gradle new file mode 100644 index 000000000..388db62c1 --- /dev/null +++ b/Assets/Plugins/Android/mainTemplate.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.library' +**APPLY_PLUGINS** + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.security:security-crypto:1.1.0-alpha03' + +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD** + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" + }**PACKAGING_OPTIONS** +}**REPOSITORIES** +**IL_CPP_BUILD_SETUP** +**SOURCE_BUILD_SETUP** +**EXTERNAL_SOURCES** diff --git a/Assets/Plugins/Android/mainTemplate.gradle.meta b/Assets/Plugins/Android/mainTemplate.gradle.meta new file mode 100644 index 000000000..c86d0fa1b --- /dev/null +++ b/Assets/Plugins/Android/mainTemplate.gradle.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 415e8bf44b652f64abe1751afe7e319a +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SequenceSDK/Editor.meta b/Assets/SequenceSDK/Editor.meta new file mode 100644 index 000000000..7ec767b56 --- /dev/null +++ b/Assets/SequenceSDK/Editor.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c69089558d4b414195f1a76d05e6f863 +timeCreated: 1730928057 \ No newline at end of file diff --git a/Assets/SequenceSDK/Editor/SequencePlatformCompileTest.cs b/Assets/SequenceSDK/Editor/SequencePlatformCompileTest.cs new file mode 100644 index 000000000..c048ddfc7 --- /dev/null +++ b/Assets/SequenceSDK/Editor/SequencePlatformCompileTest.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using NUnit.Framework; +using Sequence.Config; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace Sequence.Editor +{ + /// + /// This test will build the project for all platforms and check if the build is compiled successfully + /// + public static class SequencePlatformCompileTest + { + private const string BuildDirectory = "Builds"; + private static List _failedBuilds; + + [MenuItem("Sequence Dev/Platform Compile Test")] + public static void RunBuildTest() + { + BigInteger startEpochTime = new BigInteger(DateTimeOffset.Now.ToUnixTimeSeconds()); + ClearPreviousErrors(); + + string[] scenes = GetEnabledScenes(); + +#if UNITY_EDITOR_WIN + BuildPlatform(BuildTarget.StandaloneWindows64, $"{BuildDirectory}/WindowsBuild", scenes); +#endif +#if UNITY_EDITOR_OSX + BuildPlatform(BuildTarget.StandaloneOSX, $"{BuildDirectory}/MacOSBuild", scenes); + BuildPlatform(BuildTarget.iOS, $"{BuildDirectory}/iOSBuild", scenes); +#endif + BuildPlatform(BuildTarget.WebGL, $"{BuildDirectory}/WebGLBuild", scenes); + AndroidBuildTest($"{BuildDirectory}/AndroidBuild", scenes); + + Debug.Log("Platform Compile Test Completed. Check the console for errors."); + foreach (var failedBuild in _failedBuilds) + { + Debug.LogError(failedBuild); + } + + BigInteger endEpochTime = new BigInteger(DateTimeOffset.Now.ToUnixTimeSeconds()); + Debug.Log($"Total Test Time: {endEpochTime - startEpochTime} seconds"); + } + + private static void ClearPreviousErrors() + { + _failedBuilds = new List(); + + string errorDirectory = "PlatformCompileTestErrors"; + if (Directory.Exists(errorDirectory)) + { + var txtFiles = Directory.GetFiles(errorDirectory, "*.txt"); + foreach (var file in txtFiles) + { + File.Delete(file); + } + } + else + { + Directory.CreateDirectory(errorDirectory); + } + } + + private static string[] GetEnabledScenes() + { + return EditorBuildSettings.scenes + .Where(scene => scene.enabled) + .Select(scene => scene.path) + .ToArray(); + } + + private static void BuildPlatform(BuildTarget target, string path, string[] scenes) + { + try + { + BuildToPlatformAndCheckForSuccess(target, path, scenes); + } + finally + { + CleanupBuildDirectory(); + } + } + + private static void BuildToPlatformAndCheckForSuccess(BuildTarget target, string path, string[] scenes) + { + BuildReport report = BuildPipeline.BuildPlayer(scenes, path, target, BuildOptions.None); + + if (report.summary.result != BuildResult.Succeeded) + { + _failedBuilds.Add($"{target} build failed with {report.summary.totalErrors} errors. Please see {BuildDirectory}/{target}.txt for details."); + LogErrorsToFile(report, target); + } + } + + private static void CleanupBuildDirectory() + { + if (Directory.Exists(BuildDirectory)) + { + Directory.Delete(BuildDirectory, true); + } + } + + private static void LogErrorsToFile(BuildReport report, BuildTarget target) + { + string errorDirectory = "PlatformCompileTestErrors"; + if (!Directory.Exists(errorDirectory)) + { + Directory.CreateDirectory(errorDirectory); + } + + string errorFilePath = Path.Combine(errorDirectory, $"{target}.txt"); + + using (StreamWriter writer = new StreamWriter(errorFilePath, false)) + { + writer.WriteLine($"Build Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); + writer.WriteLine($"Platform: {target}"); + writer.WriteLine($"Build Errors Summary:"); + writer.WriteLine($"Total Errors: {report.summary.totalErrors}"); + writer.WriteLine(); + writer.WriteLine("Detailed Errors:"); + + foreach (var step in report.steps) + { + foreach (var message in step.messages) + { + if (message.type == LogType.Error) + { + writer.WriteLine($"[{message.type}] {message.content}"); + } + } + } + } + } + + private static void AndroidBuildTest(string path, string[] scenes) + { + SequenceConfig config = SequenceConfig.GetConfig(); + bool isSecureStorageEnabled = config.StoreSessionPrivateKeyInSecureStorage; + BuildTarget target = BuildTarget.Android; + + try + { + BuildToPlatformAndCheckForSuccess(target, path, scenes); + + AssertPluginCompatibility(config, target); + AssertAppropriateScriptingDefines(config, target); + + config.StoreSessionPrivateKeyInSecureStorage = !config.StoreSessionPrivateKeyInSecureStorage; + + BuildToPlatformAndCheckForSuccess(target, path, scenes); + + AssertPluginCompatibility(config, target); + AssertAppropriateScriptingDefines(config, target); + } + finally + { + config.StoreSessionPrivateKeyInSecureStorage = isSecureStorageEnabled; + + CleanupBuildDirectory(); + } + } + + private static void AssertPluginCompatibility(SequenceConfig config, BuildTarget target) + { + PluginImporter pluginImporter = AssetImporter.GetAtPath(AndroidDependencyManager.SecureStoragePluginPath) as PluginImporter; + Assert.IsNotNull(pluginImporter, "Plugin not found at path: " + AndroidDependencyManager.SecureStoragePluginPath); + Assert.AreEqual(config.StoreSessionPrivateKeyInSecureStorage, pluginImporter.GetCompatibleWithPlatform(target)); + } + + private static void AssertAppropriateScriptingDefines(SequenceConfig config, BuildTarget target) + { + string defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(target))); + Assert.AreEqual(config.StoreSessionPrivateKeyInSecureStorage, defines.Contains(AndroidScriptDefineSetup.EnableAndroidSecureStorage)); + } + } +} \ No newline at end of file diff --git a/Assets/SequenceSDK/Editor/SequencePlatformCompileTest.cs.meta b/Assets/SequenceSDK/Editor/SequencePlatformCompileTest.cs.meta new file mode 100644 index 000000000..989f0194d --- /dev/null +++ b/Assets/SequenceSDK/Editor/SequencePlatformCompileTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2179ce729f5144d897df0065aad79ff1 +timeCreated: 1730927874 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Editor/AndroidCustomGradleCheck.cs b/Packages/Sequence-Unity/Editor/AndroidCustomGradleCheck.cs new file mode 100644 index 000000000..c79be2837 --- /dev/null +++ b/Packages/Sequence-Unity/Editor/AndroidCustomGradleCheck.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Sequence.Config; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor; +using UnityEngine; + +namespace Sequence.Editor +{ + public class AndroidCustomGradleCheck : IPreprocessBuildWithReport + { + public int callbackOrder => 0; + + private static List _cachedWarnings; + + private const string _docsUrl = "https://docs.sequence.xyz/sdk/unity/recovering-sessions#android"; + + public void OnPreprocessBuild(BuildReport report) + { + if (report.summary.platform == BuildTarget.Android) + { + SequenceConfig config = SequenceConfig.GetConfig(); + if (!config.StoreSessionPrivateKeyInSecureStorage) + { + return; + } + + List warnings = new List(); + + if (!IsCustomGradlePropertiesTemplateEnabled()) + { + warnings.Add( + "Sequence - Custom Gradle Properties Template is not enabled. This may cause issues with secure storage on Android. Refer to: " + _docsUrl); + } + + if (!IsCustomMainGradleTemplateEnabled()) + { + warnings.Add( + "Sequence - Custom Main Gradle Template is not enabled. This may cause issues with secure storage on Android. Refer to: " + _docsUrl); + } + + foreach (var warning in warnings) + { + Debug.LogWarning(warning); + } + + if (warnings.Count > 0) + { + WarningPopup.ShowWindow(warnings); + } + } + } + + private bool IsCustomGradlePropertiesTemplateEnabled() + { + return GetProjectSettingsFlag("useCustomGradlePropertiesTemplate"); + } + + private bool IsCustomMainGradleTemplateEnabled() + { + return GetProjectSettingsFlag("useCustomMainGradleTemplate"); + } + + private bool GetProjectSettingsFlag(string key) + { + string projectSettingsPath = Path.Combine(Application.dataPath, "../ProjectSettings/ProjectSettings.asset"); + + if (!File.Exists(projectSettingsPath)) + { + Debug.LogError("ProjectSettings.asset file not found."); + return false; + } + + var lines = File.ReadAllLines(projectSettingsPath); + foreach (var line in lines) + { + if (line.Trim().StartsWith(key + ":")) + { + return line.Trim().EndsWith("1"); + } + } + + return false; + } + + private class WarningPopup : EditorWindow + { + private static List warnings; + + public static void ShowWindow(List warningsToShow) + { + if (warningsToShow == null || warningsToShow.Count == 0) + { + return; + } + warnings = warningsToShow; + var window = GetWindow("Sequence Build Warnings"); + window.position = new Rect(Screen.width / 2, Screen.height / 2, 400, 200); + window.Show(); + } + + private void OnGUI() + { + GUILayout.Label("Warnings Detected", EditorStyles.boldLabel); + + foreach (string warning in warnings) + { + EditorGUILayout.HelpBox(warning, MessageType.Warning); + + if (GUILayout.Button("Learn More", GUILayout.Width(100))) + { + Application.OpenURL(_docsUrl); + } + } + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Dismiss", GUILayout.Height(30))) + { + Close(); + } + } + } + } +} diff --git a/Packages/Sequence-Unity/Editor/AndroidCustomGradleCheck.cs.meta b/Packages/Sequence-Unity/Editor/AndroidCustomGradleCheck.cs.meta new file mode 100644 index 000000000..7a26e5e93 --- /dev/null +++ b/Packages/Sequence-Unity/Editor/AndroidCustomGradleCheck.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ab166bc75c424f65a56922cb657e042e +timeCreated: 1736786751 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs b/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs new file mode 100644 index 000000000..728f47906 --- /dev/null +++ b/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs @@ -0,0 +1,40 @@ +using Sequence.Config; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor.Callbacks; +using UnityEngine; + +namespace Sequence.Editor +{ + /// + /// When building for Android, some plugins, like our Secure Storage system, require custom gradle files in order to build successfully + /// If an integrator isn't using the features of one of these plugins, they shouldn't need to use the custom gradle files + /// This class will exclude any unused plugins from the build (using config from SequenceConfig) so that integrators can build without custom gradle files + /// + public class AndroidDependencyManager : IPreprocessBuildWithReport + { + public const string SecureStoragePluginPath = "Packages/xyz.0xsequence.waas-unity/Plugins/Android/AndroidKeyBridge.java"; + + public int callbackOrder => 0; + + public void OnPreprocessBuild(BuildReport report) + { +#if UNITY_ANDROID + BuildTarget target = report.summary.platform; + SequenceConfig config = SequenceConfig.GetConfig(); + + PluginImporter pluginImporter = AssetImporter.GetAtPath(SecureStoragePluginPath) as PluginImporter; + + if (pluginImporter == null) + { + Debug.LogWarning($"Plugin not found at path: {SecureStoragePluginPath}"); + return; + } + + pluginImporter.SetCompatibleWithPlatform(target, config.StoreSessionPrivateKeyInSecureStorage); + pluginImporter.SaveAndReimport(); +#endif + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs.meta b/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs.meta new file mode 100644 index 000000000..196eeeb81 --- /dev/null +++ b/Packages/Sequence-Unity/Editor/AndroidDependencyManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f1ca6f2afaf84b888012fe900f097407 +timeCreated: 1736777093 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Editor/AndroidScriptDefineSetup.cs b/Packages/Sequence-Unity/Editor/AndroidScriptDefineSetup.cs new file mode 100644 index 000000000..86e99c1f0 --- /dev/null +++ b/Packages/Sequence-Unity/Editor/AndroidScriptDefineSetup.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using Sequence.Config; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace Sequence.Editor +{ + /// + /// This class will set the script define ENABLE_SEQUENCE_ANDROID_SECURE_STORAGE based on the configuration in SequenceConfig. + /// We wrap our Android secure storage logic in a script define in order to avoid an exception; + /// if code is present in the build that attempts to access a Java class, Unity Runtime will throw an exception at startup whether that code is reached or not. + /// + public class AndroidScriptDefineSetup : IPreprocessBuildWithReport + { + public const string EnableAndroidSecureStorage = "ENABLE_SEQUENCE_ANDROID_SECURE_STORAGE"; + + public int callbackOrder => 0; + + public void OnPreprocessBuild(BuildReport report) + { + if (report.summary.platform == BuildTarget.Android) + { + BuildTargetGroup targetGroup = BuildPipeline.GetBuildTargetGroup(report.summary.platform); + List defineList; + string defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(targetGroup)); + if (string.IsNullOrWhiteSpace(defines)) + { + defineList = new List(); + } + else + { + defineList = new List(defines.Split(';')); + } + + SequenceConfig config = SequenceConfig.GetConfig(); + if (config.StoreSessionPrivateKeyInSecureStorage) + { + if (!defineList.Contains(EnableAndroidSecureStorage)) + { + defineList.Add(EnableAndroidSecureStorage); + } + } + else + { + if (defineList.Contains(EnableAndroidSecureStorage)) + { + defineList.Remove(EnableAndroidSecureStorage); + } + } + + PlayerSettings.SetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(targetGroup), string.Join(";", defineList)); + } + } + } +} diff --git a/Packages/Sequence-Unity/Editor/AndroidScriptDefineSetup.cs.meta b/Packages/Sequence-Unity/Editor/AndroidScriptDefineSetup.cs.meta new file mode 100644 index 000000000..4df92346f --- /dev/null +++ b/Packages/Sequence-Unity/Editor/AndroidScriptDefineSetup.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 320e8e73794b4ea383525309db8dbb7e +timeCreated: 1736539215 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Plugins/Android.meta b/Packages/Sequence-Unity/Plugins/Android.meta new file mode 100644 index 000000000..273257609 --- /dev/null +++ b/Packages/Sequence-Unity/Plugins/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d6320c7ac81bb42128567b31e7c3f048 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java b/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java new file mode 100644 index 000000000..7409a8ccd --- /dev/null +++ b/Packages/Sequence-Unity/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/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java.meta b/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java.meta new file mode 100644 index 000000000..cd96be83f --- /dev/null +++ b/Packages/Sequence-Unity/Plugins/Android/AndroidKeyBridge.java.meta @@ -0,0 +1,92 @@ +fileFormatVersion: 2 +guid: 244ef116dbb604697abf6460b848cb47 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/LoginPanel.cs b/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/LoginPanel.cs index 6ec4a3420..cecb566b2 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/LoginPanel.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/LoginPanel.cs @@ -67,19 +67,17 @@ public override void Open(params object[] args) _alreadyAttemptedToRestoreSession = args.GetObjectOfTypeIfExists(); } - if (SecureStorageFactory.IsSupportedPlatform()) + if (SecureStorageFactory.IsSupportedPlatform() && _storeSessionInfoAndSkipLoginWhenPossible) { - if (_storeSessionInfoAndSkipLoginWhenPossible && !_alreadyAttemptedToRestoreSession) + if (!_alreadyAttemptedToRestoreSession) { _alreadyAttemptedToRestoreSession = true; LoginHandler.TryToRestoreSession(); return; } - if (_storeSessionInfoAndSkipLoginWhenPossible) - { - LoginHandler.SetupAuthenticator(); - } + + LoginHandler.SetupAuthenticator(); } base.Open(args); diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/AndroidKeystoreStorage.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/AndroidKeystoreStorage.cs new file mode 100644 index 000000000..f067c0884 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/AndroidKeystoreStorage.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.InteropServices; +using UnityEngine; + +namespace Sequence.Utils.SecureStorage +{ + public class AndroidKeystoreStorage : ISecureStorage + { + private bool _isInitialized = false; + + public AndroidKeystoreStorage() + { +#if !UNITY_ANDROID || UNITY_EDITOR + throw new System.NotSupportedException("AndroidKeystoreStorage is only supported on Android platform."); +#elif UNITY_ANDROID && ENABLE_SEQUENCE_ANDROID_SECURE_STORAGE + InitializeAndroidKeyBridge(); +#endif + } + +#if UNITY_ANDROID && ENABLE_SEQUENCE_ANDROID_SECURE_STORAGE + private void InitializeAndroidKeyBridge() + { + if (!_isInitialized) + { + using (AndroidJavaClass javaClass = new AndroidJavaClass("xyz.sequence.AndroidKeyBridge")) + { + AndroidJavaObject bridgeObject = javaClass.CallStatic("getInstance"); + { + AndroidJavaObject unityContext = GetUnityActivity(); + bridgeObject.Call("init", unityContext); + _isInitialized = true; + } + } + } + } + + private AndroidJavaObject GetUnityActivity() + { + using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) + { + return unityPlayer.GetStatic("currentActivity"); + } + } +#endif + + public void StoreString(string key, string value) + { + #if UNITY_ANDROID && !UNITY_EDITOR && ENABLE_SEQUENCE_ANDROID_SECURE_STORAGE + AndroidJavaClass androidBridge = new AndroidJavaClass("xyz.sequence.AndroidKeyBridge"); + androidBridge.CallStatic("SaveKeychainValue", key, value); + #endif + } + + public string RetrieveString(string key) + { + #if UNITY_ANDROID && !UNITY_EDITOR && ENABLE_SEQUENCE_ANDROID_SECURE_STORAGE + AndroidJavaClass androidBridge = new AndroidJavaClass("xyz.sequence.AndroidKeyBridge"); + return androidBridge.CallStatic("GetKeychainValue", key); + #else + return null; + #endif + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/AndroidKeystoreStorage.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/AndroidKeystoreStorage.cs.meta new file mode 100644 index 000000000..f0f0bee4a --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/AndroidKeystoreStorage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03dc91e20a78d45c4af73f18a4eca18f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/SecureStorageFactory.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/SecureStorageFactory.cs index f4edafea7..5e8b2e02d 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/SecureStorageFactory.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/SecureStorage/SecureStorageFactory.cs @@ -11,6 +11,8 @@ public static ISecureStorage CreateSecureStorage() return new EditorSecureStorage(); #elif UNITY_IOS && !UNITY_EDITOR return new iOSKeychainStorage(); +#elif UNITY_ANDROID && !UNITY_EDITOR + return new AndroidKeystoreStorage(); #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX return new MacOSKeychainStorage(); #elif UNITY_WEBGL && !UNITY_EDITOR diff --git a/Packages/Sequence-Unity/package.json b/Packages/Sequence-Unity/package.json index 6ab8fdcd9..333d1bdbc 100644 --- a/Packages/Sequence-Unity/package.json +++ b/Packages/Sequence-Unity/package.json @@ -1,6 +1,6 @@ { "name": "xyz.0xsequence.waas-unity", - "version": "3.14.2", + "version": "3.15.0", "displayName": "Sequence Embedded Wallet SDK", "description": "A Unity SDK for the Sequence WaaS API", "unity": "2021.3", diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index bb00ff5ce..f947b0d97 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -9,10 +9,6 @@ EditorBuildSettings: path: Packages/xyz.0xsequence.waas-unity/Sequence/Samples/DemoScene/Demo.unity guid: d87896574aa4f42aab6b98685b555474 - enabled: 0 - path: Assets/SequenceSDK/WaaS/Tests/EndToEnd/WaaSEndToEndTests.unity - guid: 6a48e95d2401d41339a9d99c129ae1f9 - - enabled: 1 path: Assets/SequenceFrontend/Scenes/Tests.unity guid: 4dfb06a7c845a4513907bd9b1a335575 - m_configObjects: - com.unity.addressableassets: {fileID: 11400000, guid: 05a489a8ff904294fbd5aaeffa1a14d3, type: 2} + m_configObjects: {} diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index b470ce870..ad42d3430 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -167,7 +167,7 @@ PlayerSettings: overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 AndroidMinSdkVersion: 22 - AndroidTargetSdkVersion: 0 + AndroidTargetSdkVersion: 34 AndroidPreferredInstallLocation: 1 aotOptions: stripEngineCode: 1 @@ -245,10 +245,10 @@ PlayerSettings: templateDefaultScene: Assets/Scenes/SampleScene.unity useCustomMainManifest: 1 useCustomLauncherManifest: 0 - useCustomMainGradleTemplate: 0 + useCustomMainGradleTemplate: 1 useCustomLauncherGradleManifest: 0 useCustomBaseGradleTemplate: 0 - useCustomGradlePropertiesTemplate: 0 + useCustomGradlePropertiesTemplate: 1 useCustomProguardFile: 0 AndroidTargetArchitectures: 3 AndroidTargetDevices: 0 @@ -813,7 +813,7 @@ PlayerSettings: webGLThreadsSupport: 0 webGLDecompressionFallback: 0 scriptingDefineSymbols: - Android: + Android: ENABLE_SEQUENCE_ANDROID_SECURE_STORAGE Server: Standalone: UNITY_ASTOOLS_EXPERIMENTAL WebGL: VUPLEX_CCU;VUPLEX_STANDALONE diff --git a/README.md b/README.md index 8c1c48eeb..d554eb0f5 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ Many of the tests, specifically those for our custom Ethereum client, make use o Before running tests, boot up the test chain with `make start-testchain`. You may find that you need to stop (control + c) the testchain and restart it when running the test suite again. +### Platform Compile Test +When making large amounts of changes or any changes that may impact builds (assemblies, dependencies, etc.), it is useful to confirm that the SDK still compiles on the [targeted platforms](#supported-platforms). To do this navigate to the top menu and click `Sequence Dev > Platform Compile Test`. This will build the project, and the currently selected scenes in the build settings, on all targeted platforms. All build errors encountered will be recorded in `PlatformCompileTestErrors/.txt`. The builds will be cleaned up once completed. This test doesn't run any tests against the individual builds; it only confirms that the project builds on a given platform. + ### Testing via command line It can sometimes be useful to quickly test the project via command line. This can be done without opening Unity or starting the testchain. #### One-Time Setup