DEV Community

KOGA Mitsuhiro
KOGA Mitsuhiro

Posted on • Originally published at qiita.com

UnityでAARに入っているAndroidManifest.xmlからマージされる要素を取り除く

はじめに

UnityでAndroid向けのプラグインはAARの中にAndroidManifest.xmlが入っているものがありmanifest mergerで統合されるので自前で設定を追加しなくていいので便利です。逆にすべての設定が統合されると不都合な場合に削除しようとしてもUnityでOculus Go/Questアプリをビルドする時に不要なパーミッションを取り除くはAndroidManifest.xmlの統合前なので削除対象のノードが存在しません。そこで複数のマニフェスト ファイルの統合 | Android Developerstools:node="remove"を使うとノードを削除することができます。

Oculusのmeta-dataを取り除くコールバック

1つのプロジェクトでGearVR / Oculus Go / Oculus Questと非VRのAndroidアプリをビルドしたい場合を例にコールバックを作ってみます。

OVRPlugin.aarに含まれているAndroidManifest.xml

<?xml version="1.0" encoding="UTF-8"?>
<manifest android:versionCode="1" android:versionName="1.0.0" package="com.oculus.Integration" xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-sdk android:targetSdkVersion="21"/>
    <application>
        <!-- ■■■■■この行を追加したくない!!!■■■■■ -->
        <meta-data android:value="vr_only" android:name="com.samsung.android.vr.application.mode"/>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest>
Enter fullscreen mode Exit fullscreen mode

IPostGenerateGradleAndroidProjectコールバックで書き換え後のAndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.oculus.UnitySample" xmlns:tools="http://schemas.android.com/tools" android:installLocation="auto">
  <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
  <application android:theme="@style/UnityThemeSelector" android:icon="@mipmap/app_icon" android:label="@string/app_name">
    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:screenOrientation="landscape" android:launchMode="singleTask" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode|density" android:hardwareAccelerated="false" android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" android:resizeableActivity="false">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
    </activity>
    <meta-data android:name="unity.build-id" android:value="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" />
    <meta-data android:name="unity.splash-mode" android:value="0" />
    <meta-data android:name="unity.splash-enable" android:value="False" />
    <meta-data android:name="android.max_aspect" android:value="2.1" />
    <!-- ■■■■■tools:node="remove"を追加したい!!!■■■■■ -->
    <meta-data android:name="com.samsung.android.vr.application.mode" tools:node="remove" />
  </application>
  <uses-feature android:glEsVersion="0x00030001" />
  <uses-feature android:name="android.hardware.opengles.aep" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />
</manifest>
Enter fullscreen mode Exit fullscreen mode

tools:node="remove"を追加するコールバック

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

public class ModifyUnityAndroidAppManifest : IPostGenerateGradleAndroidProject
{
    public void OnPostGenerateGradleAndroidProject(string basePath)
    {
        // VRをサポートしない、またはVRSDKにOculusがない場合にtools:node="remove"を使う
        var isVr = PlayerSettings.GetVirtualRealitySupported(BuildTargetGroup.Android);
        var vrSdks = PlayerSettings.GetVirtualRealitySDKs(BuildTargetGroup.Android);

        var hasOculus = Array.Exists(vrSdks, s => s == "Oculus");
        if (!(isVr && hasOculus))
        {
            var androidManifest = new AndroidManifest(GetManifestPath(basePath));
            androidManifest.RemoveOculusMetaData();
            androidManifest.Save();
        }
    }

    // Package ManagerのOculus (Android)のIPostGenerateGradleAndroidProjectコールバックの後で実行したいので大きめの値にする
    public int callbackOrder => 10;

    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 readonly string ToolsXmlNamespace = "http://schemas.android.com/tools";

    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)
    {
        var attr = CreateAttribute("android", key, AndroidXmlNamespace);
        attr.Value = value;
        return attr;
    }

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

    // GearVR / Oculus Go / Oculus Quest向けのmeta-dataノードを削除する
    public void RemoveOculusMetaData()
    {
        const string oculusMetaDataName = "com.samsung.android.vr.application.mode";
        var oculusMetaData = SelectNodes($"/manifest/application/meta-data[@android:name='{oculusMetaDataName}']", nsMgr);
        if (oculusMetaData != null)
        {
            // Package ManagerのOculus (Android)のIPostGenerateGradleAndroidProjectコールバックで追加されるので削除する
            foreach (XmlElement metaData in oculusMetaData)
            {
                ApplicationElement.RemoveChild(metaData);
            }
        }

        // /manifest/application に <meta-data android:name="com.samsung.android.vr.application.mode" tools:node="remove" /> を追加する
        var newMetaData = CreateElement("meta-data");
        newMetaData.Attributes.Append(CreateAndroidAttribute("name", oculusMetaDataName));
        newMetaData.Attributes.Append(CreateToolsAttribute("node", "remove"));
        ApplicationElement.AppendChild(newMetaData);
    }
}
#endif
Enter fullscreen mode Exit fullscreen mode

これでAARのAndroidManifest.xmlから追加されるノードを削除できました。このコールバックではXMLを操作できるのでノードの削除だけでなくintent-filterの追加や属性の変更など柔軟な処理が可能です。

参考リンク

Sentry image

Upcoming workshop: Debugging in Unity

One day, your game will crash (...sorry). But it doesn’t have to be the end of the world! Join our upcoming workshop on debugging in Unity. No digging through endless logs necessary.

RSVP here →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay