colyseus / colyseus-unity-sdk

⚔ Colyseus Multiplayer SDK for Unity
https://docs.colyseus.io/getting-started/unity-sdk/
MIT License
378 stars 104 forks source link

Android build: Add some guide to the documentation #117

Open endel opened 4 years ago

endel commented 4 years ago

Not really sure where to put this - @eeveeboo has made a script to make HTTP/Websockets work on Android here: https://gitlab.com/snippets/1971642

If anyone is having trouble targetting Android this can be helpful:

Keywords: android manifest, android build, oculus sdk

// Uncomment to enable additional AndroidManifest Capabilitity Routines
#define ANDROID_APPLICATION_USES_CLEARTEXT_TRAFFIC
//#define ANDROID_APPLICATION_SKIPS_PERMISSIONS_DIALOG
//#define ANDROID_APPLICATION_USES_MICROPHONE
//#define ANDROID_APPLICATION_USES_QUEST_HAND_TRACKING
//#define ANDROID_APPLICATION_USES_QUEST_HAND_TRACKING_ONLY
//#define ANDROID_APPLICATION_USES_HARDWARE_ACCELERATION

#if UNITY_ANDROID
using System.IO;
using System.Text;
using System.Xml;
using UnityEditor.Android;

public class ModifyUnityAndroidAppManifestSample : IPostGenerateGradleAndroidProject
{

    public void OnPostGenerateGradleAndroidProject(string basePath)
    {
        // If needed, add condition checks on whether you need to run the modification routine.
        // For example, specific configuration/app options enabled

        var androidManifest = new AndroidManifest(GetManifestPath(basePath));

#if ANDROID_APPLICATION_USES_CLEARTEXT_TRAFFIC
        androidManifest.SetUsesCleartextTraffic();
#endif
#if ANDROID_APPLICATION_SKIPS_PERMISSIONS_DIALOG
        androidManifest.SetSkipPermissionsDialog();
#endif
#if ANDROID_APPLICATION_USES_MICROPHONE
        androidManifest.SetMicrophonePermission();
#endif
#if ANDROID_APPLICATION_USES_HARDWARE_ACCELERATION
        androidManifest.SetHardwareAcceleration();
#endif

        androidManifest.Save();
    }

    public int callbackOrder { get { return 1; } }

    private string _manifestFilePath;

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

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;

        AddOrRemoveTag(
            "/manifest",
            "application",
            null,
            true, false
            , "label", "@string/app_name"
#if UNITY_2018_2_OR_NEWER
            , "icon", "@mipmap/app_icon"
#else
            , "icon", "@drawable/app_icon",
#endif
        );
    }

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

    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 SetHardwareAcceleration()
    {
        GetActivityWithLaunchIntent().Attributes.Append(CreateAndroidAttribute("hardwareAccelerated", "true"));
    }

    internal void SetMicrophonePermission()
    {
        AddOrRemoveTag(
            "/manifest",
            "uses-permission",
            "android.permission.RECORD_AUDIO",
            true, true
        );
    }

    internal void SetSkipPermissionsDialog()
    {
        AddOrRemoveTag(
            "/manifest/application",
            "meta-data",
            "unityplayer.SkipPermissionsDialog",
            true, true,
            "value", "true"
        );
    }

    internal void SetUsesCleartextTraffic()
    {
        AddOrRemoveTag(
            "/manifest",
            "application",
            null, true, false,
            "usesCleartextTraffic", "true"
        );
    }

    internal void SetUsesQuestHandTracking()
    {
        AddOrRemoveTag(
            "/manifest/application",
            "meta-data",
            "com.samsung.android.vr.application.mode",
            true, true,
            "value", "vr_only"
        );
        AddOrRemoveTag(
            "/manifest",
            "uses-feature",
            "oculus.software.handtracking",
            true, true
#if ANDROID_APPLICATION_USES_QUEST_HAND_TRACKING_ONLY
            , "required", "true"
#endif
        );
        AddOrRemoveTag(
            "/manifest",
            "uses-permission",
            "oculus.permission.handtracking",
            true, true
        );
    }

    /// <summary>
    /// Adds, Removes or Modifies XML Infomation
    /// </summary>
    /// <param name="path">XML Parent Element Path</param>
    /// <param name="elementName">XML Child Element Name</param>
    /// <param name="name">android:name="[name]"</param>
    /// <param name="addOrRemove">Add or remove the element; true for add, false for remove</param>
    /// <param name="modifyAttributesIfFound">Override element attributes if they already exsist?</param>
    /// <param name="attrs">android:"[key]", "[value]" string attribute keypairs</param>
    internal void AddOrRemoveTag(string path, string elementName, string name, bool addOrRemove, bool modifyAttributesIfFound, params string[] attrs) // name, value pairs  
    {
        var nodes = SelectNodes(path + "/" + elementName);
        XmlElement element = null;
        foreach (XmlElement e in nodes)
        {
            if (name == null || name == e.GetAttribute("name", AndroidXmlNamespace))
            {
                element = e;
                break;
            }
        }

        if (addOrRemove)
        {
            if (element == null)
            {
                var parent = SelectSingleNode(path);
                element = CreateElement(elementName);
                element.SetAttribute("name", AndroidXmlNamespace, name);
                parent.AppendChild(element);
            }

            for (int i = 0; i < attrs.Length; i += 2)
            {
                if (modifyAttributesIfFound || string.IsNullOrEmpty(element.GetAttribute(attrs[i], AndroidXmlNamespace)))
                {
                    if (attrs[i + 1] != null)
                    {
                        element.SetAttribute(attrs[i], AndroidXmlNamespace, attrs[i + 1]);
                    }
                    else
                    {
                        element.RemoveAttribute(attrs[i], AndroidXmlNamespace);
                    }
                }
            }
        }
        else
        {
            if (element != null && modifyAttributesIfFound)
            {
                element.ParentNode.RemoveChild(element);
            }
        }
    }
}

#endif