Files
plugin-library/Assets/99.imdk_unity/Editor/ImmersalProjectValidation.cs
2025-06-19 10:56:43 +08:00

773 lines
31 KiB
C#

/*===============================================================================
Copyright (C) 2024 Immersal - Part of Hexagon. All Rights Reserved.
This file is part of the Immersal SDK.
The Immersal SDK cannot be copied, distributed, or made available to
third-parties for commercial purposes without written permission of Immersal Ltd.
Contact sales@immersal.com for licensing requests.
===============================================================================*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.XR.Management;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.XR.Management;
namespace Immersal
{
internal enum ProjectValidationState
{
Uninitialized = 0,
Issues = 1,
NoIssues = 2
}
internal class ImmersalProjectValidationWindow : EditorWindow, IActiveBuildTargetChanged
{
private static ImmersalProjectValidationWindow window = null;
private Vector2 m_ScrollViewPos = Vector2.zero;
private static ProjectValidationState SavedValidationState
{
get => (ProjectValidationState)PlayerPrefs.GetInt(ImmersalProjectValidation.PlayerPrefsStateString, 0);
set => PlayerPrefs.SetInt(ImmersalProjectValidation.PlayerPrefsStateString, (int)value);
}
[InitializeOnLoadMethod]
internal static void InitializeOnLoad()
{
EditorApplication.delayCall += () =>
{
if (SavedValidationState is ProjectValidationState.Uninitialized or ProjectValidationState.Issues)
{
SavedValidationState = ProjectValidationState.Issues;
ShowWindow();
}
};
}
[MenuItem("Immersal SDK/Project Validation")]
private static void MenuItem()
{
ShowWindow();
}
private static void ShowWindow(BuildTargetGroup buildTargetGroup = BuildTargetGroup.Unknown)
{
if (window == null)
{
window = (ImmersalProjectValidationWindow) GetWindow(typeof(ImmersalProjectValidationWindow));
window.titleContent = Content.Title;
window.minSize = new Vector2(500.0f, 300.0f);
}
window.UpdateIssues();
window.Show();
}
private static void InitStyles()
{
if (Styles.s_ListLabel != null)
return;
Styles.s_ListLabel = new GUIStyle(Styles.s_SelectionStyle)
{
border = new RectOffset(0, 0, 0, 0),
padding = new RectOffset(5, 5, 5, 5),
margin = new RectOffset(5, 5, 5, 5)
};
Styles.s_TargetPlatformLabel = new GUIStyle(EditorStyles.label)
{
fontSize = 9,
fontStyle = FontStyle.Normal,
padding = new RectOffset(10, 10, 0, 0),
};
Styles.s_IssuesTitleLabel = new GUIStyle(EditorStyles.label)
{
fontSize = 16,
fontStyle = FontStyle.Bold,
padding = new RectOffset(10, 10, 0, 0),
};
Styles.s_Wrap = new GUIStyle(EditorStyles.label)
{
wordWrap = true,
alignment = TextAnchor.MiddleLeft,
padding = new RectOffset(0, 5, 5, 5)
};
Styles.s_Icon = new GUIStyle(EditorStyles.label)
{
margin = new RectOffset(10, 10, 8, 0),
fixedWidth = Content.IconSize.x * 2
};
Styles.s_InfoBanner = new GUIStyle(EditorStyles.label)
{
padding = new RectOffset(10, 10, 15, 5)
};
Styles.s_Fix = new GUIStyle(EditorStyles.miniButton)
{
stretchWidth = false,
fixedWidth = 80,
margin = new RectOffset(0, 0, 8, 5)
};
Styles.s_FixAll = new GUIStyle(EditorStyles.miniButton)
{
stretchWidth = false,
fixedWidth = 80,
margin = new RectOffset(0, 10, 5, 8)
};
}
private readonly List<ImmersalProjectIssue> projectIssues = new List<ImmersalProjectIssue>();
private List<ImmersalProjectIssue> fixAllIssues = new List<ImmersalProjectIssue>();
private double lastUpdate;
private const double updateInterval = 1.0;
private const double backgroundUpdateInterval = 1.0;
private static class Content
{
public static readonly GUIContent Title = new GUIContent("Immersal Project Validation", "");
public static readonly GUIContent WarningIcon = EditorGUIUtility.IconContent("Warning@2x");
public static readonly GUIContent ErrorIcon = EditorGUIUtility.IconContent("Error@2x");
public static readonly GUIContent FixButton = new GUIContent("Fix", "");
public static readonly GUIContent PlayMode = new GUIContent("Exit play mode", EditorGUIUtility.IconContent("console.infoicon").image);
public static readonly Vector2 IconSize = new Vector2(16.0f, 16.0f);
}
private static class Styles
{
public static GUIStyle s_SelectionStyle = "TV Selection";
public static GUIStyle s_IssuesBackground = "ScrollViewAlt";
public static GUIStyle s_ListLabel;
public static GUIStyle s_TargetPlatformLabel;
public static GUIStyle s_IssuesTitleLabel;
public static GUIStyle s_Wrap;
public static GUIStyle s_Icon;
public static GUIStyle s_InfoBanner;
public static GUIStyle s_Fix;
public static GUIStyle s_FixAll;
}
protected void OnFocus() => UpdateIssues(true);
protected void Update() => UpdateIssues();
private void UpdateIssues(bool force = false)
{
var interval = EditorWindow.focusedWindow == this ? updateInterval : backgroundUpdateInterval;
if (!force && EditorApplication.timeSinceStartup - lastUpdate < interval)
return;
// Fix all
foreach (ImmersalProjectIssue issue in fixAllIssues)
{
issue.Fix?.Invoke();
}
fixAllIssues.Clear();
ImmersalProjectValidation.CheckIssues(projectIssues);
Repaint();
if (projectIssues.Count > 0)
{
if(SavedValidationState != ProjectValidationState.Uninitialized)
SavedValidationState = ProjectValidationState.Issues;
}
else
{
SavedValidationState = ProjectValidationState.NoIssues;
}
lastUpdate = EditorApplication.timeSinceStartup;
}
public void OnGUI()
{
InitStyles();
EditorGUIUtility.SetIconSize(Content.IconSize);
using (new EditorGUI.DisabledScope(fixAllIssues.Count > 0))
{
EditorGUILayout.BeginVertical();
if (EditorApplication.isPlaying && projectIssues.Count > 0)
{
GUILayout.Label(Content.PlayMode, Styles.s_InfoBanner);
}
EditorGUILayout.Space();
bool fixableIssues = projectIssues.Any(f => f.Fix != null);
EditorGUILayout.LabelField($"Target platform: {ImmersalProjectValidation.ActiveBuildTarget.ToString()}", Styles.s_TargetPlatformLabel);
EditorGUILayout.BeginHorizontal();
using (new EditorGUI.DisabledScope(EditorApplication.isPlaying))
{
EditorGUILayout.LabelField($"Project issues ({projectIssues.Count}):", Styles.s_IssuesTitleLabel);
}
if (fixableIssues)
{
using (new EditorGUI.DisabledScope(EditorApplication.isPlaying))
{
if (GUILayout.Button("Fix All", Styles.s_FixAll))
{
fixAllIssues = new List<ImmersalProjectIssue>();
foreach (ImmersalProjectIssue issue in projectIssues)
{
if (!issue.RequiresManualFix)
fixAllIssues.Add(issue);
}
}
}
}
EditorGUILayout.EndHorizontal();
m_ScrollViewPos = EditorGUILayout.BeginScrollView(m_ScrollViewPos, Styles.s_IssuesBackground,
GUILayout.ExpandHeight(true));
using (new EditorGUI.DisabledScope(EditorApplication.isPlaying))
{
foreach (ImmersalProjectIssue issue in projectIssues)
{
EditorGUILayout.BeginHorizontal(Styles.s_ListLabel);
GUILayout.Label(issue.Error ? Content.ErrorIcon : Content.WarningIcon, Styles.s_Icon,
GUILayout.Width(Content.IconSize.x));
GUILayout.Label(issue.Message(), Styles.s_Wrap);
GUILayout.FlexibleSpace();
GUILayout.Label("", GUILayout.Width(Content.IconSize.x * 1.5f));
if (issue.Fix != null)
{
if (GUILayout.Button(Content.FixButton, Styles.s_Fix))
{
issue.Fix();
}
}
else if (fixableIssues)
{
GUILayout.Label("", GUILayout.Width(80.0f));
}
EditorGUILayout.EndHorizontal();
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
}
public void OnActiveBuildTargetChanged(BuildTarget previousTarget, BuildTarget newTarget)
{
UpdateIssues(true);
}
public int callbackOrder => 0;
}
#if UNITY_EDITOR
public interface IImmersalProjectIssueProvider
{
public string Name { get; }
public bool Enabled { get; set; }
public bool DisableDefaultIssues { get; }
public IEnumerable<ImmersalProjectIssue> Issues { get; }
}
public class ImmersalProjectIssue
{
public Func<string> Message;
public Func<bool> Check;
public Action Fix;
public bool Error;
public bool RequiresManualFix;
}
public static class ImmersalProjectValidation
{
public static string PlayerPrefsStateString = "ImmersalProjectValidationState";
private static IImmersalProjectIssueProvider m_DefaultIssueProvider = new DefaultImmersalProjectIssueProvider();
private static Dictionary<string, IImmersalProjectIssueProvider> m_IssueProviders = new Dictionary<string, IImmersalProjectIssueProvider>();
public static BuildTargetGroup ActiveBuildTargetGroup = ActiveBuildTarget switch
{
BuildTarget.iOS => BuildTargetGroup.iOS,
BuildTarget.Android => BuildTargetGroup.Android,
BuildTarget.StandaloneOSX => BuildTargetGroup.Standalone,
BuildTarget.StandaloneWindows => BuildTargetGroup.Standalone,
BuildTarget.StandaloneWindows64 => BuildTargetGroup.Standalone,
BuildTarget.StandaloneLinux64 => BuildTargetGroup.Standalone,
BuildTarget.WSAPlayer => BuildTargetGroup.WSA,
_ => BuildTargetGroup.Unknown
};
public static BuildTarget ActiveBuildTarget => EditorUserBuildSettings.activeBuildTarget;
public static void RegisterIssueProvider(IImmersalProjectIssueProvider provider)
{
m_IssueProviders[provider.Name] = provider;
}
public static void CheckIssues(List<ImmersalProjectIssue> issues)
{
issues.Clear();
if (m_IssueProviders.Count > 1)
{
issues.Add(new ImmersalProjectIssue
{
Check = () => false,
Message = () => "Multiple packages affecting project issues.",
Error = false
});
}
foreach (IImmersalProjectIssueProvider provider in m_IssueProviders.Values)
{
if (provider.DisableDefaultIssues)
m_DefaultIssueProvider.Enabled = false;
if (provider.Enabled)
issues.AddRange(provider.Issues);
}
if (m_DefaultIssueProvider.Enabled)
issues.AddRange(m_DefaultIssueProvider.Issues);
issues.RemoveAll(issue => issue.Check?.Invoke() ?? false);
}
}
internal class DefaultImmersalProjectIssueProvider : IImmersalProjectIssueProvider
{
public string Name => "Default";
public bool DisableDefaultIssues => false;
public bool Enabled { get; set; } = true;
public IEnumerable<ImmersalProjectIssue> Issues => DefaultProjectIssues;
// ReSharper disable once HeapView.ObjectAllocation
private static readonly ImmersalProjectIssue[] DefaultProjectIssues =
{
// Graphics API
new ImmersalProjectIssue()
{
Message = () =>
{
return ImmersalProjectValidation.ActiveBuildTarget switch
{
BuildTarget.iOS => "Graphics API must be set to Metal",
BuildTarget.Android => "Graphics API must be set to OpenGLES3",
BuildTarget.StandaloneWindows => "Graphics API must set to OpenGLCore",
_ => "Unsupported GraphicsAPI for current build target."
};
},
Check = () =>
{
GraphicsDeviceType[] graphicAPIs = PlayerSettings.GetGraphicsAPIs(ImmersalProjectValidation.ActiveBuildTarget);
return ImmersalProjectValidation.ActiveBuildTarget switch
{
BuildTarget.iOS => graphicAPIs.Length == 1 && graphicAPIs[0] == GraphicsDeviceType.Metal,
BuildTarget.Android => graphicAPIs.Length == 1 && graphicAPIs[0] == GraphicsDeviceType.OpenGLES3,
BuildTarget.StandaloneWindows => graphicAPIs.Length == 1 && graphicAPIs[0] == GraphicsDeviceType.OpenGLCore,
_ => true
};
},
Fix = () =>
{
GraphicsDeviceType[] cga = PlayerSettings.GetGraphicsAPIs(ImmersalProjectValidation.ActiveBuildTarget);
var autoGraphicAPI = PlayerSettings.GetUseDefaultGraphicsAPIs(ImmersalProjectValidation.ActiveBuildTarget);
if (autoGraphicAPI)
PlayerSettings.SetUseDefaultGraphicsAPIs(ImmersalProjectValidation.ActiveBuildTarget, false);
GraphicsDeviceType[] graphicAPIs = ImmersalProjectValidation.ActiveBuildTarget switch
{
BuildTarget.iOS => new GraphicsDeviceType[] { GraphicsDeviceType.Metal },
BuildTarget.Android => new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLES3 },
BuildTarget.StandaloneWindows => new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLCore },
_ => PlayerSettings.GetGraphicsAPIs(ImmersalProjectValidation.ActiveBuildTarget)
};
PlayerSettings.SetGraphicsAPIs(ImmersalProjectValidation.ActiveBuildTarget, graphicAPIs);
},
Error = true,
},
// IL2CPP
new ImmersalProjectIssue()
{
Message = () => "IL2CPP must be enabled.",
Check = () => PlayerSettings.GetScriptingBackend(ImmersalProjectValidation.ActiveBuildTargetGroup) == ScriptingImplementation.IL2CPP,
Fix = () => { PlayerSettings.SetScriptingBackend(ImmersalProjectValidation.ActiveBuildTargetGroup, ScriptingImplementation.IL2CPP); },
Error = true,
},
// Allow unsafe code
new ImmersalProjectIssue()
{
Message = () => "Allow 'unsafe' code must be enabled.",
Check = () => PlayerSettings.allowUnsafeCode,
Fix = () => { PlayerSettings.allowUnsafeCode = true; },
Error = true,
},
// Camera usage description
new ImmersalProjectIssue()
{
Message = () => "Camera Usage Description must be defined.",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.iOS || PlayerSettings.iOS.cameraUsageDescription != "",
Fix = () => { PlayerSettings.iOS.cameraUsageDescription = "Required for augmented reality support."; },
Error = true,
},
// Location usage description
new ImmersalProjectIssue()
{
Message = () => "Location Usage Description must be defined.",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.iOS || PlayerSettings.iOS.locationUsageDescription != "",
Fix = () => { PlayerSettings.iOS.locationUsageDescription = "Required for satellite positioning support."; },
Error = true,
},
// minimum ios version 12.0
new ImmersalProjectIssue()
{
Message = () => "Target minimum iOS Version must be 12.0 or higher.",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.iOS || (float.TryParse(PlayerSettings.iOS.targetOSVersionString, out float minVersion) && minVersion >= 12.0f),
Fix = () => { PlayerSettings.iOS.targetOSVersionString = "12.0"; },
Error = true,
},
// minimum android api version 26
new ImmersalProjectIssue()
{
Message = () => "Minimum Android API Level must be 26 or higher.",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.Android ||
PlayerSettings.Android.minSdkVersion >= AndroidSdkVersions.AndroidApiLevel26,
Fix = () => { PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel26; },
Error = true,
},
// ARM64
new ImmersalProjectIssue()
{
Message = () => "ARM64 Target Architecture must be enabled.",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.Android ||
(PlayerSettings.Android.targetArchitectures & AndroidArchitecture.ARM64) != 0,
Fix = () => { PlayerSettings.Android.targetArchitectures |= AndroidArchitecture.ARM64; },
Error = true,
},
// ARKit loader
new ImmersalProjectIssue()
{
Message = () => "ARKit XR-Plugin Provider must be enabled.",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.iOS || IsPluginLoaderEnabled("AR Kit Loader") ,
Fix = () =>
{
EditorUserBuildSettings.selectedBuildTargetGroup = ImmersalProjectValidation.ActiveBuildTargetGroup;
SettingsService.OpenProjectSettings("Project/XR Plug-in Management");
},
Error = true,
RequiresManualFix = true
},
// ARCore loader
new ImmersalProjectIssue()
{
Message = () => "ARCore XR-Plugin Provider must be enabled.",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.Android || IsPluginLoaderEnabled("AR Core Loader"),
Fix = () =>
{
EditorUserBuildSettings.selectedBuildTargetGroup = ImmersalProjectValidation.ActiveBuildTargetGroup;
SettingsService.OpenProjectSettings("Project/XR Plug-in Management");
},
Error = true,
RequiresManualFix = true
},
new ImmersalProjectIssue()
{
Message = () => "Universal Render Pipeline is recommended",
Check = () => GraphicsSettings.defaultRenderPipeline != null,
Fix = SetupRenderPipeline,
Error = false,
RequiresManualFix = false
},
// android manifest exists
new ImmersalProjectIssue()
{
Message = () => "Custom Android Manifest is required.",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.Android || CheckManifestExists(),
Fix = CreateManifest,
Error = true,
},
// android manifest has necessary attributes
new ImmersalProjectIssue()
{
Message = () => "Android Manifest should include network permissions",
Check = () => ImmersalProjectValidation.ActiveBuildTarget != BuildTarget.Android || CheckManifestContent(),
Fix = ConfigureManifest,
Error = false,
},
};
private static bool IsPluginLoaderEnabled(string loaderName)
{
XRGeneralSettings generalSettings = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(ImmersalProjectValidation.ActiveBuildTargetGroup);
if (generalSettings == null)
return false;
XRManagerSettings managerSettings = generalSettings.AssignedSettings;
string loaderNameNoWhitespace = loaderName.Replace(" ", "");
return managerSettings != null && managerSettings.activeLoaders.Any(loader => (loader.name == loaderName || loader.name == loaderNameNoWhitespace));
}
private static void SetupRenderPipeline()
{
string destinationPath = Application.dataPath;
// Renderer and RenderPipelineAsset
if (CopyFromPackage("Editor/ImmersalURPAsset_Renderer.asset", Path.Combine(destinationPath, "ImmersalURPAsset_Renderer.asset"), true))
{
// Load RendererData
string rendererDataPath = Path.Combine("Assets", "ImmersalURPAsset_Renderer.asset");
UniversalRendererData rendererData =
(UniversalRendererData)AssetDatabase.LoadAssetAtPath(rendererDataPath, typeof(UniversalRendererData));
if (rendererData != null)
{
// Create / load RenderPipelineAsset
UniversalRenderPipelineAsset rpa = UniversalRenderPipelineAsset.Create(rendererData);
// Save to file
string rpaSavePath = Path.Combine("Assets", "ImmersalURPAsset.asset");
AssetDatabase.CreateAsset(rpa, rpaSavePath);
// Configure
ConfigureRenderPipelineAsset(rpa);
// Set in settings
GraphicsSettings.defaultRenderPipeline = rpa;
QualitySettings.renderPipeline = rpa;
}
}
// Global Settings
if (CopyFromPackage("Editor/ImmersalURPGlobalSettings.asset", Path.Combine(destinationPath, "ImmersalURPGlobalSettings.asset"), true))
{
string settingsPath = Path.Combine("Assets", "ImmersalURPGlobalSettings.asset");
RenderPipelineGlobalSettings settings =
(RenderPipelineGlobalSettings)AssetDatabase.LoadAssetAtPath(settingsPath,
typeof(RenderPipelineGlobalSettings));
if (settings != null)
{
GraphicsSettings.RegisterRenderPipelineSettings<UniversalRenderPipeline>(settings);
}
}
}
private static void ConfigureRenderPipelineAsset(UniversalRenderPipelineAsset asset)
{
asset.supportsHDR = false;
asset.cascadeBorder = 0.1f;
}
private static bool CopyFromPackage(string packageRelativePath, string destinationPath, bool refreshDatabase = true)
{
string sourcePath = Path.Combine("Packages/com.immersal.core/", packageRelativePath);
string absolutePath = FixWindowsPath(Path.GetFullPath(sourcePath));
destinationPath = FixWindowsPath(destinationPath);
if (File.Exists(absolutePath))
{
if (File.Exists(destinationPath))
{
ImmersalLogger.LogWarning(
$"Attempting to copy file from Immersal package but destination already exists: {destinationPath}");
}
else
{
// Have to manually create directories or CopyFileOrDirectory will error on Windows
string dirName = Path.GetDirectoryName(destinationPath);
if (dirName == null)
return false;
if (!Directory.Exists(dirName))
Directory.CreateDirectory(dirName);
FileUtil.CopyFileOrDirectory(absolutePath, destinationPath);
}
if (refreshDatabase)
AssetDatabase.Refresh();
return true;
}
ImmersalLogger.LogWarning($"Could not locate {packageRelativePath} in Immersal package.");
return false;
}
// Unity API expects forward-slashes everywhere, but .NET methods produce paths with back-slashes on Windows
private static string FixWindowsPath(string path)
{
return path.Replace("\\", "/");
}
private static bool CheckManifestExists()
{
return File.Exists(GetManifestPath());
}
private static bool CheckManifestContent()
{
if (CheckManifestExists())
{
AndroidManifest manifest = new AndroidManifest(GetManifestPath());
bool internet = manifest.CheckPermission("android.permission.INTERNET");
bool network = manifest.CheckPermission("android.permission.ACCESS_NETWORK_STATE");
return internet && network;
}
return false;
}
private static string GetManifestPath()
{
return Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml");
}
private static void CreateManifest()
{
CopyFromPackage("Editor/SampleAndroidManifest.xml", GetManifestPath());
}
private static void ConfigureManifest()
{
if (CheckManifestExists())
{
AndroidManifest manifest = new AndroidManifest(GetManifestPath());
manifest.AddPermission("android.permission.INTERNET");
manifest.AddPermission("android.permission.ACCESS_NETWORK_STATE");
manifest.Save();
AssetDatabase.Refresh();
}
}
}
internal class AndroidXmlDocument : XmlDocument
{
private string m_Path;
protected XmlNamespaceManager nsMgr;
public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
public AndroidXmlDocument(string path)
{
m_Path = path;
using (var reader = new XmlTextReader(m_Path))
{
reader.Read();
Load(reader);
}
nsMgr = new XmlNamespaceManager(NameTable);
nsMgr.AddNamespace("android", AndroidXmlNamespace);
}
public string Save()
{
return SaveAs(m_Path);
}
public string SaveAs(string path)
{
using (var writer = new XmlTextWriter(path, new UTF8Encoding(false)))
{
writer.Formatting = Formatting.Indented;
Save(writer);
}
return path;
}
}
internal class AndroidManifest : AndroidXmlDocument
{
private readonly XmlElement ApplicationElement;
public AndroidManifest(string path) : base(path)
{
ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
}
private XmlAttribute CreateAndroidAttribute(string key, string value)
{
XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
attr.Value = value;
return attr;
}
private XmlElement CreatePermissionElement(string permissionName)
{
XmlElement elem = CreateElement("uses-permission");
XmlAttribute attr = CreateAttribute("android", "name", AndroidXmlNamespace);
attr.Value = permissionName;
elem.Attributes.Append(attr);
return elem;
}
internal XmlNode GetActivityWithLaunchIntent()
{
return SelectSingleNode("/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and " +
"intent-filter/category/@android:name='android.intent.category.LAUNCHER']", nsMgr);
}
internal void SetApplicationTheme(string appTheme)
{
ApplicationElement.Attributes.Append(CreateAndroidAttribute("theme", appTheme));
}
internal void SetStartingActivityName(string activityName)
{
GetActivityWithLaunchIntent().Attributes.Append(CreateAndroidAttribute("name", activityName));
}
internal void AddPermission(string permissionName)
{
XmlElement ManifestElement = SelectSingleNode("/manifest") as XmlElement;
XmlElement permissionElement = CreatePermissionElement(permissionName);
ManifestElement?.AppendChild(permissionElement);
}
internal bool CheckPermission(string permissionName)
{
XmlElement ManifestElement = SelectSingleNode("/manifest") as XmlElement;
XmlNodeList children = ManifestElement?.ChildNodes;
if (children == null) return false;
foreach (XmlNode child in children)
{
if (child.Name != "uses-permission") continue;
if (child.Attributes == null) continue;
XmlAttribute attr = child.Attributes?["android:name"];
if (attr == null) continue;
if (attr.Value == permissionName) return true;
}
return false;
}
}
#endif
}