Skip to content

Commit

Permalink
[SDK-414]Feature/extract webview logic (#125)
Browse files Browse the repository at this point in the history
-   moved all data types and classes that can be used universally for any iFrame or integration (including webGL builds) 
- this is linked to this PR for WebView readyplayerme/rpm-unity-sdk-webview#21
  • Loading branch information
HarrisonHough authored Sep 28, 2023
1 parent 25f78e0 commit f972732
Show file tree
Hide file tree
Showing 26 changed files with 845 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Editor/Module Management/ModuleList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static class ModuleList
{
name = "com.readyplayerme.webview",
gitUrl = "https://github.com/readyplayerme/rpm-unity-sdk-webview.git",
branch = "",
branch = "feature/extract-shared-functionality",
version = "1.2.0"
}
};
Expand Down
8 changes: 8 additions & 0 deletions Editor/WebView.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

239 changes: 239 additions & 0 deletions Editor/WebView/WebViewBuildPostprocessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#if UNITY_EDITOR
using System.IO;
using System.Xml;
using UnityEditor;
using System.Text;
using UnityEditor.Android;
using UnityEditor.Callbacks;

#if UNITY_IOS
using UnityEditor.iOS.Xcode;
#endif

namespace ReadyPlayerMe.WebView
{
/// <summary>
/// Receives a callback after the Android Gradle project is generated,
/// and the callback is used for generating a manifest file with required permissions.
/// </summary>
public class WebViewBuildPostprocessor : IPostGenerateGradleAndroidProject
{
public int callbackOrder => 1;

public void OnPostGenerateGradleAndroidProject(string basePath)
{
var manifestPath = GetManifestPath(basePath);
var androidManifest = new AndroidManifest(manifestPath);

androidManifest
.SetHardwareAccelerated(true)
.SetUsesCleartextTraffic(true)
.UseCamera()
.UseMicrophone()
.UseGallery()
.AllowBackup();

androidManifest.Save();
}

private string GetManifestPath(string basePath)
{
var pathBuilder = new StringBuilder(basePath);
pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
return pathBuilder.ToString();
}

[PostProcessBuild(100)]
public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
{
#if UNITY_IOS
if (buildTarget == BuildTarget.iOS) {
string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject proj = new PBXProject();
proj.ReadFromString(File.ReadAllText(projPath));
proj.AddFrameworkToProject(proj.GetUnityFrameworkTargetGuid(), "WebKit.framework", false);
File.WriteAllText(projPath, proj.WriteToString());
}
#endif
}
}

/// <summary>
/// AndroidManifest.xml file that is created with necessary permissions.
/// </summary>
internal class AndroidXmlDocument : XmlDocument
{
private string manifestPath;
protected XmlNamespaceManager namespaceManager;
public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
public readonly string ToolsXmlNamespace = "http://schemas.android.com/tools";

public AndroidXmlDocument(string path)
{
manifestPath = path;
using (var reader = new XmlTextReader(manifestPath))
{
reader.Read();
Load(reader);
}

namespaceManager = new XmlNamespaceManager(NameTable);
namespaceManager.AddNamespace("android", AndroidXmlNamespace);
namespaceManager.AddNamespace("tools", ToolsXmlNamespace);
}

public string Save()
{
return SaveAs(manifestPath);
}

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 const string NodeKey = "name";
private const string UsesFeature = "uses-feature";
private const string UsesPermission = "uses-permission";

private const string CameraPermission = "android.permission.CAMERA";
private const string CameraFeature = "android.hardware.camera";

private const string MicrophonePermission = "android.permission.MICROPHONE";
private const string MicrophoneFeature = "android.hardware.microphone";

private const string ReadExternalStoragePermission = "android.permission.READ_EXTERNAL_STORAGE";
private const string WriteExternalStoragePermission = "android.permission.Write_EXTERNAL_STORAGE";

private const string UsesCleartextTrafficAttribute = "usesCleartextTraffic";
private const string HardwareAcceleratedAttribute = "hardwareAccelerated";

private const string XPath = "/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and intent-filter/category/@android:name='android.intent.category.LAUNCHER']";

private static XmlNode ActivityWithLaunchIntent = null;

private readonly XmlElement ManifestElement;

public AndroidManifest(string path) : base(path)
{
ManifestElement = SelectSingleNode("/manifest") as XmlElement;
}

internal XmlNode GetActivityWithLaunchIntent()
{
return ActivityWithLaunchIntent ?? SelectSingleNode(XPath, namespaceManager);
}

#region Node Edit Methods

private XmlAttribute CreateAndroidAttribute(string key, string value)
{
XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
attr.Value = value;
return attr;
}

private XmlAttribute CreateToolsAttribute(string key, string value)
{
XmlAttribute attr = CreateAttribute("tools", key, ToolsXmlNamespace);
attr.Value = value;
return attr;
}

internal void UpdateAttribute(XmlElement activity, string attribute, bool enabled)
{
var value = enabled.ToString();

if (activity.GetAttribute(attribute, AndroidXmlNamespace) != value)
{
activity.SetAttribute(attribute, AndroidXmlNamespace, value);
}
}

internal void UpdateNode(string nodeName, string nodeValue)
{
XmlNodeList node = SelectNodes($"/manifest/{nodeName}[@android:{NodeKey}='{nodeValue}']", namespaceManager);
if (node?.Count == 0)
{
XmlElement elem = CreateElement(nodeName);
elem.Attributes.Append(CreateAndroidAttribute(NodeKey, nodeValue));
ManifestElement.AppendChild(elem);
}
}

internal void UseFeature(string feature)
{
UpdateNode(UsesFeature, feature);
}

internal void UsePermission(string permission)
{
UpdateNode(UsesPermission, permission);
}

#endregion

#region AndroidManifest Options

internal AndroidManifest SetUsesCleartextTraffic(bool enabled)
{
var activity = GetActivityWithLaunchIntent() as XmlElement;
UpdateAttribute(activity, UsesCleartextTrafficAttribute, enabled);
return this;
}

internal AndroidManifest AllowBackup()
{
XmlNode elem = SelectSingleNode("/manifest/application");
if (elem?.Attributes != null)
{
elem.Attributes.Append(CreateAndroidAttribute("allowBackup", "false"));
elem.Attributes.Append(CreateToolsAttribute("replace", "android:allowBackup"));
}

return this;
}

internal AndroidManifest SetHardwareAccelerated(bool enabled)
{
var activity = GetActivityWithLaunchIntent() as XmlElement;
UpdateAttribute(activity, HardwareAcceleratedAttribute, enabled);
return this;
}

internal AndroidManifest UseCamera()
{
UsePermission(CameraPermission);
UseFeature(CameraFeature);
return this;
}

internal AndroidManifest UseMicrophone()
{
UsePermission(MicrophonePermission);
UseFeature(MicrophoneFeature);
return this;
}

internal AndroidManifest UseGallery()
{
UsePermission(ReadExternalStoragePermission);
UsePermission(WriteExternalStoragePermission);
return this;
}

#endregion
}
}
#endif
11 changes: 11 additions & 0 deletions Editor/WebView/WebViewBuildPostprocessor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions Editor/WebView/WebViewEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;

namespace ReadyPlayerMe.WebView
{
public class WebViewEditor : Editor
{
private const string WEB_VIEW_CANVAS_FILE_NAME = "WebView Canvas";

/// <summary>
/// Loads a WebView Canvas prefab to the current scene.
/// </summary>
[MenuItem("GameObject/UI/Ready Player Me/WebView Canvas", false)]
private static void LoadWebViewCanvas()
{
var prefab = Resources.Load<GameObject>(WEB_VIEW_CANVAS_FILE_NAME);
GameObject instance = Instantiate(prefab);
instance.name = WEB_VIEW_CANVAS_FILE_NAME;
Selection.activeGameObject = instance;
EditorUtility.SetDirty(instance);
}
}
}
#endif
11 changes: 11 additions & 0 deletions Editor/WebView/WebViewEditor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Runtime/WebView.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Runtime/WebView/Data.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f972732

Please sign in to comment.