/****************************************************************************
* Copyright 2019 Nreal Techonology Limited.All rights reserved.
*
* This file is part of NRSDK.
*
* https://www.nreal.ai/
*
*****************************************************************************/
namespace NRKernal
{
using UnityEditor;
using UnityEngine;
using System.IO;
using UnityEngine.Rendering;
using System.Collections.Generic;
using NRKernal.Release;
using LitJson;
using System.Linq;
using System;
using System.Text;
/// Form for viewing the project tips.
[InitializeOnLoad]
public class ProjectTipsWindow : EditorWindow
{
/// A check.
private abstract class Check
{
/// The key.
protected string _key;
protected MessageType _level;
public MessageType level
{
get
{
return _level;
}
}
public Check(MessageType level)
{
_level = level;
}
/// Ignores this object.
public void Ignore()
{
EditorPrefs.SetBool(ignorePrefix + _key, true);
}
/// Query if this object is ignored.
/// True if ignored, false if not.
public bool IsIgnored()
{
return EditorPrefs.HasKey(ignorePrefix + _key);
}
/// Deletes the ignore.
public void DeleteIgnore()
{
EditorPrefs.DeleteKey(ignorePrefix + _key);
}
/// Query if this object is valid.
/// True if valid, false if not.
public abstract bool IsValid();
/// Draw graphical user interface.
public abstract void DrawGUI();
/// Query if this object is fixable.
/// True if fixable, false if not.
public abstract bool IsFixable();
/// Fixes this object.
public abstract void Fix();
protected void DrawContent(string title, string message)
{
EditorGUILayout.HelpBox(title, level);
EditorGUILayout.LabelField(message, EditorStyles.textArea);
}
}
/// A ckeck for buildTarget .
private class CkeckBuildTargetAndroid : Check
{
/// Default constructor.
public CkeckBuildTargetAndroid(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
return EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android;
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"In order to develop on NRSDK, BuildTarget must be set to Android.
in panel of Player Settings, choose 'Androi' in platform list, and click 'Switch Platform' button.";
DrawContent("BuildTarget is Android", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
{
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
}
}
}
/// A ckeck android vsyn.
private class CkeckAndroidVsyn : Check
{
/// Default constructor.
public CkeckAndroidVsyn(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
return QualitySettings.vSyncCount == 0;
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"In order to render correct on mobile devices, the vSyn in quality settings must be disabled.
in dropdown list of Quality Settings > V Sync Count, choose 'Dont't Sync' for all levels.";
DrawContent("vSyn is opened on Mobile Devices", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android ||
EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS)
{
QualitySettings.vSyncCount = 0;
}
}
}
/// Ckeck android SD card permission descriptor.
private class CkeckAndroidSDCardPermission : Check
{
/// Default constructor.
public CkeckAndroidSDCardPermission(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
return PlayerSettings.Android.forceSDCardPermission;
}
else
{
return false;
}
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"In order to run correct on mobile devices, the sdcard write permission should be set.
in dropdown list of Player Settings > Other Settings > Write Permission, choose 'External(SDCard)'.";
DrawContent("Sdcard permission not available", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
PlayerSettings.Android.forceSDCardPermission = true;
}
}
}
/// Android minSdkVersion should be higher than 26.
private class CkeckAndroidMinAPILevel : Check
{
public CkeckAndroidMinAPILevel(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
public override bool IsValid()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
return PlayerSettings.Android.minSdkVersion >= AndroidSdkVersions.AndroidApiLevel26 ||
PlayerSettings.Android.minSdkVersion == AndroidSdkVersions.AndroidApiLevelAuto;
}
else
{
return false;
}
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"In order to run correct on mobile devices, Android minSdkVersion should be higher than 26.";
DrawContent("Android minSdkVersion should be higher than 26", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel26;
}
}
}
/// A ckeck android orientation.
private class CkeckAndroidOrientation : Check
{
/// Default constructor.
public CkeckAndroidOrientation(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
return PlayerSettings.defaultInterfaceOrientation == UIOrientation.Portrait;
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"In order to display correct on mobile devices, the orientation should be set to portrait.
in dropdown list of Player Settings > Resolution and Presentation > Default Orientation, choose 'Portrait'.";
DrawContent("Orientation is not portrait", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
PlayerSettings.defaultInterfaceOrientation = UIOrientation.Portrait;
}
}
}
/// A ckeck android graphics a pi.
private class CkeckAndroidGraphicsAPI : Check
{
/// Default constructor.
public CkeckAndroidGraphicsAPI(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
var graphics = PlayerSettings.GetGraphicsAPIs(BuildTarget.Android);
if (graphics != null && graphics.Length == 1 &&
graphics[0] == UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3)
{
return true;
}
return false;
}
else
{
return false;
}
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"In order to render correct on mobile devices, the graphicsAPIs should be set to OpenGLES3.
in dropdown list of Player Settings > Other Settings > Graphics APIs , choose 'OpenGLES3'.";
DrawContent("GraphicsAPIs is not OpenGLES3", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new GraphicsDeviceType[1] { GraphicsDeviceType.OpenGLES3 });
}
}
}
/// A ckeck color space.
private class CkeckColorSpace : Check
{
/// Default constructor.
public CkeckColorSpace(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
return PlayerSettings.colorSpace == ColorSpace.Linear;
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"In order to display correct on mobile devices, the colorSpace should be set to linear.
in dropdown list of Player Settings > Other Settings > Color Space, choose 'Linear'.";
DrawContent("ColorSpace is not Linear", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
PlayerSettings.colorSpace = ColorSpace.Linear;
}
}
}
/// A ckeck color space.
private class CkeckXRDefine : Check
{
/// Default constructor.
public CkeckXRDefine(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
var dict = PackageUtility.GetAllPackagesByManifest();
if (dict.Count == 0 || !dict.ContainsKey(NativeConstants.XRPLUGIN))
{
return !DefineSymbolsUtility.HasSymbol(NativeConstants.XRDEFINE);
}
else
{
return DefineSymbolsUtility.HasSymbol(NativeConstants.XRDEFINE);
}
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"Not configured correctly.";
DrawContent("Define is not correctly.", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
var dict = PackageUtility.GetAllPackagesByManifest();
if (dict.Count == 0 || !dict.ContainsKey(NativeConstants.XRPLUGIN))
{
DefineSymbolsUtility.RemoveSymbol(NativeConstants.XRDEFINE);
}
else
{
DefineSymbolsUtility.AddSymbol(NativeConstants.XRDEFINE);
}
}
}
/// A ckeck color space.
private class CkeckDependency : Check
{
public class PackageInfo
{
public string name;
public string version;
}
private PackageInfo XRPluginPackageInfo;
/// Default constructor.
public CkeckDependency(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
private void FreshXRPluginVersion(Action callback = null)
{
PackageUtility.GetAllPackages((info) =>
{
if (!info.isSuccess)
{
NRDebugger.Warning("Can not get all packages info...");
return;
}
var package_result = info.packages.Select((package) =>
{
UnityEditor.PackageManager.PackageInfo p = null;
if (package.name.Equals(NativeConstants.XRPLUGIN))
{
p = package;
}
return p;
}).First();
if (XRPluginPackageInfo == null)
{
XRPluginPackageInfo = new PackageInfo();
}
if (package_result != null)
{
XRPluginPackageInfo.name = package_result.name;
XRPluginPackageInfo.version = package_result.version;
}
callback?.Invoke(XRPluginPackageInfo);
});
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
if (XRPluginPackageInfo == null)
{
FreshXRPluginVersion();
return true;
}
bool result = true;
if (string.Compare(XRPluginPackageInfo.version, NativeConstants.XRPLUGIN_MIN_VERSION) < 0)
{
result = false;
}
if (!result)
{
FreshXRPluginVersion();
}
return result;
}
public override void DrawGUI()
{
const string title = "Check dependencies";
const string messageFormat = "package \"{0}\" version is \"{1}\", need to upgrade to \"{2}\"";
StringBuilder st = new StringBuilder();
if (XRPluginPackageInfo != null)
{
if (string.Compare(XRPluginPackageInfo.version, NativeConstants.XRPLUGIN_MIN_VERSION) < 0)
{
st.AppendLine(string.Format(messageFormat, XRPluginPackageInfo.name, XRPluginPackageInfo.version, NativeConstants.XRPLUGIN_MIN_VERSION));
}
}
else
{
FreshXRPluginVersion();
st.AppendLine("Waitting to get dependencies version...");
}
DrawContent(title, st.ToString());
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
if (string.Compare(XRPluginPackageInfo.version, NativeConstants.XRPLUGIN_MIN_VERSION) < 0)
{
Debug.LogFormat("[CkeckDependency] Fix dependency , current:{0} dependency:{1}",
XRPluginPackageInfo.version, NativeConstants.XRPLUGIN_MIN_VERSION);
FixedXRProviderPlugin(NativeConstants.XRPLUGIN, NativeConstants.XRPLUGIN_MIN_VERSION);
}
}
public static void FixedXRProviderPlugin(string key, string version)
{
string path = Path.Combine(Directory.GetParent(Application.dataPath).FullName, "Packages/manifest.json");
var contents = File.ReadAllLines(path);
var json = JsonMapper.ToObject(File.ReadAllText(path));
for (int i = 0; i < contents.Length; i++)
{
if (contents[i].Contains(key) && !contents[i].Contains('{'))
{
var valueofkey = json["dependencies"][key].ToString();
if (key.Equals(NativeConstants.XRPLUGIN))
{
if (valueofkey.Contains('#'))
{
var value_params = valueofkey.Split('#');
if (value_params.Length != 2)
{
NRDebugger.Warning("Dependencie format error:[{0}]", valueofkey);
break;
}
valueofkey = string.Format("{0}#{1}", value_params[0], version);
}
else
{
valueofkey = string.Format("{0}#{1}", valueofkey, version);
}
}
else
{
valueofkey = version;
}
contents[i] = string.Format(" \"{0}\": \"{1}\",", key, valueofkey.Replace("\"", ""));
}
}
File.WriteAllLines(path, contents);
}
}
/// A ckeck that android build system is gradle.
private class CkeckAndroidBuildGradle: Check
{
/// Default constructor.
public CkeckAndroidBuildGradle(MessageType level) : base(level)
{
_key = this.GetType().Name;
}
/// Query if this object is valid.
/// True if valid, false if not.
public override bool IsValid()
{
return EditorUserBuildSettings.androidBuildSystem == AndroidBuildSystem.Gradle;
}
/// Draw graphical user interface.
public override void DrawGUI()
{
string message = @"";
DrawContent("Gradle plugin is valid", message);
}
/// Query if this object is fixable.
/// True if fixable, false if not.
public override bool IsFixable()
{
return true;
}
/// Fixes this object.
public override void Fix()
{
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
}
}
/// The checks.
private static Check[] checks = new Check[]
{
new CkeckBuildTargetAndroid(MessageType.Error),
new CkeckAndroidVsyn(MessageType.Error),
new CkeckAndroidMinAPILevel(MessageType.Error),
//new CkeckAndroidSDCardPermission(),
new CkeckAndroidOrientation(MessageType.Warning),
new CkeckAndroidGraphicsAPI(MessageType.Error),
new CkeckXRDefine(MessageType.Error),
#if USING_XR_SDK
new CkeckDependency(MessageType.Error)
#endif
new CkeckAndroidBuildGradle(MessageType.Error),
//new CkeckColorSpace(Level.Error),
};
/// The window.
private static ProjectTipsWindow m_Window;
/// The scroll position.
private Vector2 m_ScrollPosition;
/// The ignore prefix.
private const string ignorePrefix = "NRKernal.ignore";
static ProjectTipsWindow()
{
EditorApplication.update -= Update;
}
/// Shows the window.
[MenuItem("NRSDK/Project Tips", false, 50)]
public static void ShowWindow()
{
m_Window = GetWindow(true);
m_Window.minSize = new Vector2(320, 300);
m_Window.maxSize = new Vector2(320, 800);
m_Window.titleContent = new GUIContent("NRSDK | Project Tips");
}
/// Updates this object.
private static void Update()
{
bool show = false;
foreach (Check check in checks)
{
if (!check.IsIgnored() && !check.IsValid() && check.level > MessageType.Warning)
{
show = true;
}
}
if (show)
{
ShowWindow();
}
EditorApplication.update -= Update;
}
/// Executes the 'graphical user interface' action.
public void OnGUI()
{
var resourcePath = GetResourcePath();
var logo = AssetDatabase.LoadAssetAtPath(resourcePath + "icon.png");
var rect = GUILayoutUtility.GetRect(position.width, 80, GUI.skin.box);
GUI.DrawTexture(rect, logo, ScaleMode.ScaleToFit);
string aboutText = "This window provides tips to help fix common issues with the NRSDK and your project.";
EditorGUILayout.LabelField(aboutText, EditorStyles.textArea);
int ignoredCount = 0;
int fixableCount = 0;
int invalidNotIgnored = 0;
for (int i = 0; i < checks.Length; i++)
{
Check check = checks[i];
bool ignored = check.IsIgnored();
bool valid = check.IsValid();
bool fixable = check.IsFixable();
if (!valid && !ignored && fixable)
{
fixableCount++;
}
if (!valid && !ignored)
{
invalidNotIgnored++;
}
if (ignored)
{
ignoredCount++;
}
}
Rect issuesRect = EditorGUILayout.GetControlRect();
GUI.Box(new Rect(issuesRect.x - 4, issuesRect.y, issuesRect.width + 8, issuesRect.height), "Tips", EditorStyles.toolbarButton);
if (invalidNotIgnored > 0)
{
m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition);
{
for (int i = 0; i < checks.Length; i++)
{
Check check = checks[i];
if (!check.IsIgnored() && !check.IsValid())
{
invalidNotIgnored++;
GUILayout.BeginVertical("box");
{
check.DrawGUI();
EditorGUILayout.BeginHorizontal();
{
// Aligns buttons to the right
GUILayout.FlexibleSpace();
if (check.IsFixable())
{
if (GUILayout.Button("Fix"))
check.Fix();
}
//if (GUILayout.Button("Ignore"))
// check.Ignore();
}
EditorGUILayout.EndHorizontal();
}
GUILayout.EndVertical();
}
}
}
GUILayout.EndScrollView();
}
GUILayout.FlexibleSpace();
if (invalidNotIgnored == 0)
{
GUILayout.BeginHorizontal();
{
GUILayout.FlexibleSpace();
GUILayout.BeginVertical();
{
GUILayout.Label("No issues found");
if (GUILayout.Button("Close Window"))
Close();
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
}
EditorGUILayout.BeginHorizontal("box");
{
if (fixableCount > 0)
{
if (GUILayout.Button("Accept All"))
{
if (EditorUtility.DisplayDialog("Accept All", "Are you sure?", "Yes, Accept All", "Cancel"))
{
for (int i = 0; i < checks.Length; i++)
{
Check check = checks[i];
if (!check.IsIgnored() &&
!check.IsValid())
{
if (check.IsFixable())
check.Fix();
}
}
}
}
}
//if (invalidNotIgnored > 0)
//{
// if (GUILayout.Button("Ignore All"))
// {
// if (EditorUtility.DisplayDialog("Ignore All", "Are you sure?", "Yes, Ignore All", "Cancel"))
// {
// for (int i = 0; i < checks.Length; i++)
// {
// Check check = checks[i];
// if (!check.IsIgnored())
// check.Ignore();
// }
// }
// }
//}
//if (ignoredCount > 0)
//{
// if (GUILayout.Button("Show Ignored"))
// {
// foreach (Check check in checks)
// check.DeleteIgnore();
// }
//}
}
GUILayout.EndHorizontal();
}
/// Gets resource path.
/// The resource path.
private string GetResourcePath()
{
var ms = MonoScript.FromScriptableObject(this);
var path = AssetDatabase.GetAssetPath(ms);
path = Path.GetDirectoryName(path);
return path.Substring(0, path.Length - "Editor".Length - 1) + "Textures/";
}
}
}