AndroidUtils.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. // Copyright (c) 2024 Vuplex Inc. All rights reserved.
  2. //
  3. // Licensed under the Vuplex Commercial Software Library License, you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // https://vuplex.com/commercial-library-license
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #if UNITY_ANDROID && !UNITY_EDITOR
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Reflection;
  18. using UnityEngine;
  19. using UnityEngine.Rendering;
  20. namespace Vuplex.WebView.Internal {
  21. /// <summary>
  22. /// Static utility methods used internally by 3D WebView on Android.
  23. /// </summary>
  24. public static class AndroidUtils {
  25. public static void AssertMainThread(string methodName) {
  26. // Unity's AndroidJavaObject and AndroidJavaClass APIs can only be called on the Unity main thread
  27. // and result in cryptic JNI errors if called from other threads.
  28. if (!ThreadDispatcher.CurrentlyOnMainThread) {
  29. var publicMethodName = methodName[0].ToString().ToUpper() + methodName.Substring(1);
  30. throw new InvalidOperationException($"{publicMethodName}() can only be called on the Unity main thread but was called from a background thread instead.");
  31. }
  32. }
  33. public static Material CreateAndroidMaterial() {
  34. if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan) {
  35. return VXUtils.CreateDefaultMaterial();
  36. }
  37. // Construct a new material because Resources.Load<T>() returns a singleton.
  38. return new Material(Resources.Load<Material>("AndroidWebMaterial"));
  39. }
  40. public static byte[] ConvertFromJavaByteArray(AndroidJavaObject arrayObject) {
  41. // Unity 2019.1 and newer logs a warning that converting from byte[] is obsolete
  42. // but older versions are incapable of converting from sbyte[].
  43. #if UNITY_2019_1_OR_NEWER
  44. return (byte[])(Array)AndroidJNIHelper.ConvertFromJNIArray<sbyte[]>(arrayObject.GetRawObject());
  45. #else
  46. return AndroidJNIHelper.ConvertFromJNIArray<byte[]>(arrayObject.GetRawObject());
  47. #endif
  48. }
  49. public static bool DeviceIsMetaQuest() {
  50. // Note: this method used to use deviceName, but its value may be "<unknown>" in some cases.
  51. // For reference, I observed that SystemInfo.deviceModel returned "Oculus Quest" for a Quest 2 headset.
  52. return SystemInfo.deviceModel.Contains("Quest");
  53. }
  54. public static void RunOnAndroidUIThread(Action function) {
  55. _getActivity().Call("runOnUiThread", new AndroidJavaRunnable(function));
  56. }
  57. public static void ThrowVulkanExtensionException() {
  58. throw new InvalidOperationException("The Vulkan Graphics API is in use, but this device does not support the VK_ANDROID_external_memory_android_hardware_buffer Vulkan API required by 3D WebView. Please switch to the OpenGLES Graphics API in Player Settings. For more info, see this page: https://support.vuplex.com/articles/vulkan");
  59. }
  60. public static AndroidJavaObject ToJavaMap(Dictionary<string, string> dictionary) {
  61. AndroidJavaObject map = new AndroidJavaObject("java.util.HashMap");
  62. IntPtr putMethod = AndroidJNIHelper.GetMethodID(map.GetRawClass(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
  63. foreach (var entry in dictionary) {
  64. AndroidJNI.CallObjectMethod(
  65. map.GetRawObject(),
  66. putMethod,
  67. AndroidJNIHelper.CreateJNIArgArray(new object[] { entry.Key, entry.Value })
  68. );
  69. }
  70. return map;
  71. }
  72. public static AndroidJavaObject ToJavaObject(IntPtr jobject) {
  73. if (jobject == IntPtr.Zero) {
  74. return null;
  75. }
  76. #if UNITY_2022_2_OR_NEWER
  77. return new AndroidJavaObject(jobject);
  78. #else
  79. return (AndroidJavaObject)_legacyAndroidJavaObjectIntPtrConstructor.Invoke(new object[] { jobject });
  80. #endif
  81. }
  82. // Get a reference to AndroidJavaObject's hidden constructor that takes
  83. // the IntPtr for a jobject as a parameter.
  84. readonly static ConstructorInfo _legacyAndroidJavaObjectIntPtrConstructor = typeof(AndroidJavaObject).GetConstructor(
  85. BindingFlags.Instance | BindingFlags.NonPublic,
  86. null,
  87. new []{ typeof(IntPtr) },
  88. null
  89. );
  90. static AndroidJavaClass _unityPlayerClass;
  91. static AndroidJavaObject _getActivity() {
  92. if (_unityPlayerClass == null) {
  93. _unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
  94. }
  95. return _unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
  96. }
  97. }
  98. }
  99. #endif