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