2026-06-25 15:48:45 +08:00
#if UNITY_EDITOR
using System.Collections.Generic ;
using System.Collections ;
using System.IO ;
using System.Reflection ;
using System.Text.RegularExpressions ;
using System.Text ;
using System.Xml ;
using System ;
using UnityEditor.Android ;
2026-06-30 22:15:06 +08:00
#if UNITY_2020_1_OR_NEWER
using UnityEditor.Build.Reporting ;
#endif
2026-06-25 15:48:45 +08:00
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Build ;
#endif
using UnityEditor.Callbacks ;
using UnityEditor ;
using UnityEngine ;
2026-06-30 22:15:06 +08:00
namespace Gree.UnityWebView
{
#if UNITY_2020_1_OR_NEWER
public class UnityWebViewPostprocessBuild : IPreprocessBuildWithReport , IPostGenerateGradleAndroidProject
#elif UNITY_2018_1_OR_NEWER
public class UnityWebViewPostprocessBuild : IPreprocessBuild , IPostGenerateGradleAndroidProject
2026-06-25 15:48:45 +08:00
#else
2026-06-30 22:15:06 +08:00
public class UnityWebViewPostprocessBuild
2026-06-25 15:48:45 +08:00
#endif
2026-06-30 22:15:06 +08:00
{
private static bool nofragment = false ;
2026-06-25 15:48:45 +08:00
2026-06-30 22:15:06 +08:00
//// for android/unity 2018.1 or newer
//// cf. https://forum.unity.com/threads/android-hardwareaccelerated-is-forced-false-in-all-activities.532786/
//// cf. https://github.com/Over17/UnityAndroidManifestCallback
2026-06-25 15:48:45 +08:00
#if UNITY_2018_1_OR_NEWER
2026-06-30 22:15:06 +08:00
#if UNITY_2020_1_OR_NEWER
public void OnPreprocessBuild ( BuildReport buildReport ) {
var buildTarget = buildReport . summary . platform ;
#else
public void OnPreprocessBuild ( BuildTarget buildTarget , string path ) {
#endif
if ( buildTarget == BuildTarget . Android ) {
var dev = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl" ;
var rel = "Packages/net.gree.unity-webview/Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl" ;
if (! File . Exists ( dev ) || ! File . Exists ( rel )) {
dev = "Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl" ;
rel = "Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl" ;
}
var src = ( EditorUserBuildSettings . development ) ? dev : rel ;
//Directory.CreateDirectory("Temp/StagingArea/aar");
//File.Copy(src, "Temp/StagingArea/aar/WebViewPlugin.aar", true);
Directory . CreateDirectory ( "Assets/Plugins/Android" );
File . Copy ( src , "Assets/Plugins/Android/WebViewPlugin.aar" , true );
}
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
public void OnPostGenerateGradleAndroidProject ( string basePath ) {
var changed = false ;
var androidManifest = new AndroidManifest ( GetManifestPath ( basePath ));
if (! nofragment ) {
changed = ( androidManifest . AddFileProvider ( basePath ) || changed );
2026-06-25 15:48:45 +08:00
{
2026-06-30 22:15:06 +08:00
var path = GetBuildGradlePath ( basePath );
var lines0 = File . ReadAllText ( path ). Replace ( "\r\n" , "\n" ). Replace ( "\r" , "\n" ). Split ( new []{ '\n' });
{
var lines = new List < string >();
var independencies = false ;
foreach ( var line in lines0 ) {
if ( line == "dependencies {" ) {
independencies = true ;
} else if ( independencies && line == "}" ) {
independencies = false ;
lines . Add ( " implementation 'androidx.core:core:1.6.0'" );
} else if ( independencies ) {
if ( line . Contains ( "implementation(name: 'core" )
|| line . Contains ( "implementation(name: 'androidx.core.core" )
|| line . Contains ( "implementation 'androidx.core:core" )) {
break ;
}
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
lines . Add ( line );
}
if ( lines . Count > lines0 . Length ) {
File . WriteAllText ( path , string . Join ( "\n" , lines ) + "\n" );
2026-06-25 15:48:45 +08:00
}
}
}
2026-06-30 22:15:06 +08:00
{
var path = GetGradlePropertiesPath ( basePath );
var lines0 = "" ;
var lines = "" ;
if ( File . Exists ( path )) {
lines0 = File . ReadAllText ( path ). Replace ( "\r\n" , "\n" ). Replace ( "\r" , "\n" ) + "\n" ;
lines = lines0 ;
}
if (! lines . Contains ( "android.useAndroidX=true" )) {
lines += "android.useAndroidX=true\n" ;
}
if (! lines . Contains ( "android.enableJetifier=true" )) {
lines += "android.enableJetifier=true\n" ;
}
if ( lines != lines0 ) {
File . WriteAllText ( path , lines );
}
2026-06-25 15:48:45 +08:00
}
}
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . SetExported ( true ) || changed );
changed = ( androidManifest . SetWindowSoftInputMode ( "adjustPan" ) || changed );
changed = ( androidManifest . SetHardwareAccelerated ( true ) || changed );
2026-06-25 15:48:45 +08:00
#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . SetUsesCleartextTraffic ( true ) || changed );
2026-06-25 15:48:45 +08:00
#endif
2026-06-30 22:15:06 +08:00
if (! nofragment ) {
changed = ( androidManifest . AddGallery () || changed );
2026-06-25 15:48:45 +08:00
#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . AddCamera () || changed );
2026-06-25 15:48:45 +08:00
#endif
#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . AddMicrophone () || changed );
2026-06-25 15:48:45 +08:00
#endif
2026-06-30 22:15:06 +08:00
}
if ( changed ) {
androidManifest . Save ();
Debug . Log ( "unitywebview: adjusted AndroidManifest.xml." );
}
2026-06-25 15:48:45 +08:00
}
#endif
2026-06-30 22:15:06 +08:00
public int callbackOrder {
get {
return 1 ;
}
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
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 ();
}
2026-06-25 15:48:45 +08:00
2026-06-30 22:15:06 +08:00
private string GetBuildGradlePath ( string basePath ) {
var pathBuilder = new StringBuilder ( basePath );
pathBuilder . Append ( Path . DirectorySeparatorChar ). Append ( "build.gradle" );
return pathBuilder . ToString ();
}
2026-06-25 15:48:45 +08:00
2026-06-30 22:15:06 +08:00
private string GetGradlePropertiesPath ( string basePath ) {
var pathBuilder = new StringBuilder ( basePath );
if ( basePath . EndsWith ( "unityLibrary" )) {
pathBuilder . Append ( Path . DirectorySeparatorChar ). Append ( ".." );
}
pathBuilder . Append ( Path . DirectorySeparatorChar ). Append ( "gradle.properties" );
return pathBuilder . ToString ();
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
//// for others
2026-06-25 15:48:45 +08:00
2026-06-30 22:15:06 +08:00
[PostProcessBuild(100)]
public static void OnPostprocessBuild ( BuildTarget buildTarget , string path ) {
2026-06-25 15:48:45 +08:00
#if UNITY_2018_1_OR_NEWER
2026-06-30 22:15:06 +08:00
try {
File . Delete ( "Assets/Plugins/Android/WebViewPlugin.aar" );
File . Delete ( "Assets/Plugins/Android/WebViewPlugin.aar.meta" );
Directory . Delete ( "Assets/Plugins/Android" );
File . Delete ( "Assets/Plugins/Android.meta" );
Directory . Delete ( "Assets/Plugins" );
File . Delete ( "Assets/Plugins.meta" );
} catch ( Exception ) {
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
#else
if ( buildTarget == BuildTarget . Android ) {
string manifest = Path . Combine ( Application . dataPath , "Plugins/Android/AndroidManifest.xml" );
if (! File . Exists ( manifest )) {
string manifest0 = Path . Combine ( Application . dataPath , "../Temp/StagingArea/AndroidManifest-main.xml" );
if (! File . Exists ( manifest0 )) {
Debug . LogError ( "unitywebview: cannot find both Assets/Plugins/Android/AndroidManifest.xml and Temp/StagingArea/AndroidManifest-main.xml. please build the app to generate Assets/Plugins/Android/AndroidManifest.xml and then rebuild it again." );
return ;
} else {
File . Copy ( manifest0 , manifest , true );
}
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
var changed = false ;
if ( EditorUserBuildSettings . development ) {
if (! File . Exists ( "Assets/Plugins/Android/WebView.aar" )
|| ! File . ReadAllBytes ( "Assets/Plugins/Android/WebView.aar" ). SequenceEqual ( File . ReadAllBytes ( "Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl" ))) {
File . Copy ( "Assets/Plugins/Android/WebViewPlugin-development.aar.tmpl" , "Assets/Plugins/Android/WebView.aar" , true );
changed = true ;
}
} else {
if (! File . Exists ( "Assets/Plugins/Android/WebView.aar" )
|| ! File . ReadAllBytes ( "Assets/Plugins/Android/WebView.aar" ). SequenceEqual ( File . ReadAllBytes ( "Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl" ))) {
File . Copy ( "Assets/Plugins/Android/WebViewPlugin-release.aar.tmpl" , "Assets/Plugins/Android/WebView.aar" , true );
changed = true ;
2026-06-25 15:48:45 +08:00
}
}
2026-06-30 22:15:06 +08:00
var androidManifest = new AndroidManifest ( manifest );
if (! nofragment ) {
changed = ( androidManifest . AddFileProvider ( "Assets/Plugins/Android" ) || changed );
var files = Directory . GetFiles ( "Assets/Plugins/Android/" );
var found = false ;
2026-06-25 15:48:45 +08:00
foreach ( var file in files ) {
2026-06-30 22:15:06 +08:00
if ( Regex . IsMatch ( file , @"^Assets/Plugins/Android/(androidx\.core\.)?core-.*.aar$" )) {
found = true ;
2026-06-25 15:48:45 +08:00
break ;
}
}
2026-06-30 22:15:06 +08:00
if (! found ) {
foreach ( var file in files ) {
var match = Regex . Match ( file , @"^Assets/Plugins/Android/(core.*.aar).tmpl$" );
if ( match . Success ) {
var name = match . Groups [ 1 ]. Value ;
File . Copy ( file , "Assets/Plugins/Android/" + name , true );
break ;
}
}
}
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . SetWindowSoftInputMode ( "adjustPan" ) || changed );
changed = ( androidManifest . SetHardwareAccelerated ( true ) || changed );
2026-06-25 15:48:45 +08:00
#if UNITYWEBVIEW_ANDROID_USES_CLEARTEXT_TRAFFIC
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . SetUsesCleartextTraffic ( true ) || changed );
2026-06-25 15:48:45 +08:00
#endif
2026-06-30 22:15:06 +08:00
if (! nofragment ) {
changed = ( androidManifest . AddGallery () || changed );
2026-06-25 15:48:45 +08:00
#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . AddCamera () || changed );
2026-06-25 15:48:45 +08:00
#endif
#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . AddMicrophone () || changed );
2026-06-25 15:48:45 +08:00
#endif
2026-06-30 22:15:06 +08:00
}
2026-06-25 15:48:45 +08:00
#if UNITY_5_6_0 || UNITY_5_6_1
2026-06-30 22:15:06 +08:00
changed = ( androidManifest . SetActivityName ( "net.gree.unitywebview.CUnityPlayerActivity" ) || changed );
2026-06-25 15:48:45 +08:00
#endif
2026-06-30 22:15:06 +08:00
if ( changed ) {
androidManifest . Save ();
Debug . LogError ( "unitywebview: adjusted AndroidManifest.xml and/or WebView.aar. Please rebuild the app." );
}
2026-06-25 15:48:45 +08:00
}
#endif
2026-06-30 22:15:06 +08:00
if ( buildTarget == BuildTarget . iOS ) {
string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj" ;
var type = Type . GetType ( "UnityEditor.iOS.Xcode.PBXProject, UnityEditor.iOS.Extensions.Xcode" );
if ( type == null )
{
Debug . LogError ( "unitywebview: failed to get PBXProject. please install iOS build support." );
return ;
}
var src = File . ReadAllText ( projPath );
//dynamic proj = type.GetConstructor(Type.EmptyTypes).Invoke(null);
var proj = type . GetConstructor ( Type . EmptyTypes ). Invoke ( null );
//proj.ReadFromString(src);
{
var method = type . GetMethod ( "ReadFromString" );
method . Invoke ( proj , new object []{ src });
}
var target = "" ;
2026-06-25 15:48:45 +08:00
#if UNITY_2019_3_OR_NEWER
2026-06-30 22:15:06 +08:00
//target = proj.GetUnityFrameworkTargetGuid();
{
var method = type . GetMethod ( "GetUnityFrameworkTargetGuid" );
target = ( string ) method . Invoke ( proj , null );
}
2026-06-25 15:48:45 +08:00
#else
2026-06-30 22:15:06 +08:00
//target = proj.TargetGuidByName("Unity-iPhone");
{
var method = type . GetMethod ( "TargetGuidByName" );
target = ( string ) method . Invoke ( proj , new object []{ "Unity-iPhone" });
}
2026-06-25 15:48:45 +08:00
#endif
2026-06-30 22:15:06 +08:00
//proj.AddFrameworkToProject(target, "WebKit.framework", false);
{
var method = type . GetMethod ( "AddFrameworkToProject" );
method . Invoke ( proj , new object []{ target , "WebKit.framework" , false });
}
var cflags = "" ;
if ( EditorUserBuildSettings . development ) {
cflags += " -DUNITYWEBVIEW_DEVELOPMENT" ;
}
2026-06-25 15:48:45 +08:00
#if UNITYWEBVIEW_IOS_ALLOW_FILE_URLS
2026-06-30 22:15:06 +08:00
cflags += " -DUNITYWEBVIEW_IOS_ALLOW_FILE_URLS" ;
2026-06-25 15:48:45 +08:00
#endif
2026-06-30 22:15:06 +08:00
cflags = cflags . Trim ();
if (! string . IsNullOrEmpty ( cflags )) {
// proj.AddBuildProperty(target, "OTHER_LDFLAGS", cflags);
var method = type . GetMethod ( "AddBuildProperty" , new Type []{ typeof ( string ), typeof ( string ), typeof ( string )});
method . Invoke ( proj , new object []{ target , "OTHER_CFLAGS" , cflags });
}
var dst = "" ;
//dst = proj.WriteToString();
{
var method = type . GetMethod ( "WriteToString" );
dst = ( string ) method . Invoke ( proj , null );
}
File . WriteAllText ( projPath , dst );
}
2026-06-25 15:48:45 +08:00
}
}
2026-06-30 22:15:06 +08:00
internal class AndroidXmlDocument : XmlDocument {
private string m_Path ;
protected XmlNamespaceManager nsMgr ;
public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android" ;
2026-06-25 15:48:45 +08:00
2026-06-30 22:15:06 +08:00
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 );
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
public string Save () {
return SaveAs ( m_Path );
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
public string SaveAs ( string path ) {
using ( var writer = new XmlTextWriter ( path , new UTF8Encoding ( false ))) {
writer . Formatting = Formatting . Indented ;
Save ( writer );
}
return path ;
}
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal class AndroidManifest : AndroidXmlDocument {
private readonly XmlElement ManifestElement ;
private readonly XmlElement ApplicationElement ;
2026-06-25 15:48:45 +08:00
2026-06-30 22:15:06 +08:00
public AndroidManifest ( string path ) : base ( path ) {
ManifestElement = SelectSingleNode ( "/manifest" ) as XmlElement ;
ApplicationElement = SelectSingleNode ( "/manifest/application" ) as XmlElement ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
private XmlAttribute CreateAndroidAttribute ( string key , string value ) {
XmlAttribute attr = CreateAttribute ( "android" , key , AndroidXmlNamespace );
attr . Value = value ;
return attr ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
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 );
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal bool SetUsesCleartextTraffic ( bool enabled ) {
// android:usesCleartextTraffic
bool changed = false ;
if ( ApplicationElement . GetAttribute ( "usesCleartextTraffic" , AndroidXmlNamespace ) != (( enabled ) ? "true" : "false" )) {
ApplicationElement . SetAttribute ( "usesCleartextTraffic" , AndroidXmlNamespace , ( enabled ) ? "true" : "false" );
changed = true ;
}
return changed ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
// for api level 33
internal bool SetExported ( bool enabled ) {
bool changed = false ;
var activity = GetActivityWithLaunchIntent () as XmlElement ;
if ( activity == null ) {
return false ;
}
if ( activity . GetAttribute ( "exported" , AndroidXmlNamespace ) != (( enabled ) ? "true" : "false" )) {
activity . SetAttribute ( "exported" , AndroidXmlNamespace , ( enabled ) ? "true" : "false" );
changed = true ;
}
return changed ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal bool SetWindowSoftInputMode ( string mode ) {
bool changed = false ;
var activity = GetActivityWithLaunchIntent () as XmlElement ;
if ( activity == null ) {
return false ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
if ( activity . GetAttribute ( "windowSoftInputMode" , AndroidXmlNamespace ) != mode ) {
activity . SetAttribute ( "windowSoftInputMode" , AndroidXmlNamespace , mode );
changed = true ;
}
return changed ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal bool SetHardwareAccelerated ( bool enabled ) {
bool changed = false ;
var activity = GetActivityWithLaunchIntent () as XmlElement ;
if ( activity == null ) {
return false ;
}
if ( activity . GetAttribute ( "hardwareAccelerated" , AndroidXmlNamespace ) != (( enabled ) ? "true" : "false" )) {
activity . SetAttribute ( "hardwareAccelerated" , AndroidXmlNamespace , ( enabled ) ? "true" : "false" );
changed = true ;
}
return changed ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal bool SetActivityName ( string name ) {
bool changed = false ;
var activity = GetActivityWithLaunchIntent () as XmlElement ;
if ( activity == null ) {
return false ;
}
if ( activity . GetAttribute ( "name" , AndroidXmlNamespace ) != name ) {
activity . SetAttribute ( "name" , AndroidXmlNamespace , name );
changed = true ;
}
return changed ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal bool AddFileProvider ( string basePath ) {
bool changed = false ;
var authorities = PlayerSettings . applicationIdentifier + ".unitywebview.fileprovider" ;
if ( SelectNodes ( "/manifest/application/provider[@android:authorities='" + authorities + "']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "provider" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "androidx.core.content.FileProvider" ));
elem . Attributes . Append ( CreateAndroidAttribute ( "authorities" , authorities ));
elem . Attributes . Append ( CreateAndroidAttribute ( "exported" , "false" ));
elem . Attributes . Append ( CreateAndroidAttribute ( "grantUriPermissions" , "true" ));
var meta = CreateElement ( "meta-data" );
meta . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.support.FILE_PROVIDER_PATHS" ));
meta . Attributes . Append ( CreateAndroidAttribute ( "resource" , "@xml/unitywebview_file_provider_paths" ));
elem . AppendChild ( meta );
ApplicationElement . AppendChild ( elem );
changed = true ;
var xml = GetFileProviderSettingPath ( basePath );
if (! File . Exists ( xml )) {
Directory . CreateDirectory ( Path . GetDirectoryName ( xml ));
File . WriteAllText (
xml ,
"<paths xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" +
" <external-path name=\"unitywebview_file_provider_images\" path=\".\"/>\n" +
"</paths>\n" );
}
}
return changed ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
private string GetFileProviderSettingPath ( string basePath ) {
var pathBuilder = new StringBuilder ( basePath );
pathBuilder . Append ( Path . DirectorySeparatorChar ). Append ( "src" );
pathBuilder . Append ( Path . DirectorySeparatorChar ). Append ( "main" );
pathBuilder . Append ( Path . DirectorySeparatorChar ). Append ( "res" );
pathBuilder . Append ( Path . DirectorySeparatorChar ). Append ( "xml" );
pathBuilder . Append ( Path . DirectorySeparatorChar ). Append ( "unitywebview_file_provider_paths.xml" );
return pathBuilder . ToString ();
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal bool AddCamera () {
bool changed = false ;
if ( SelectNodes ( "/manifest/uses-permission[@android:name='android.permission.CAMERA']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-permission" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.permission.CAMERA" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
if ( SelectNodes ( "/manifest/uses-feature[@android:name='android.hardware.camera']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-feature" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.hardware.camera" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
// cf. https://developer.android.com/training/data-storage/shared/media#media-location-permission
if ( SelectNodes ( "/manifest/uses-permission[@android:name='android.permission.ACCESS_MEDIA_LOCATION']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-permission" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.permission.ACCESS_MEDIA_LOCATION" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
// cf. https://developer.android.com/training/package-visibility/declaring
if ( SelectNodes ( "/manifest/queries" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "queries" );
ManifestElement . AppendChild ( elem );
changed = true ;
}
if ( SelectNodes ( "/manifest/queries/intent/action[@android:name='android.media.action.IMAGE_CAPTURE']" , nsMgr ). Count == 0 ) {
var action = CreateElement ( "action" );
action . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.media.action.IMAGE_CAPTURE" ));
var intent = CreateElement ( "intent" );
intent . AppendChild ( action );
var queries = SelectSingleNode ( "/manifest/queries" ) as XmlElement ;
queries . AppendChild ( intent );
changed = true ;
}
return changed ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal bool AddGallery () {
bool changed = false ;
// for api level 33
if ( SelectNodes ( "/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_IMAGES']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-permission" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.permission.READ_MEDIA_IMAGES" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
if ( SelectNodes ( "/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_VIDEO']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-permission" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.permission.READ_MEDIA_VIDEO" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
if ( SelectNodes ( "/manifest/uses-permission[@android:name='android.permission.READ_MEDIA_AUDIO']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-permission" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.permission.READ_MEDIA_AUDIO" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
// cf. https://developer.android.com/training/package-visibility/declaring
if ( SelectNodes ( "/manifest/queries" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "queries" );
ManifestElement . AppendChild ( elem );
changed = true ;
}
if ( SelectNodes ( "/manifest/queries/intent/action[@android:name='android.media.action.GET_CONTENT']" , nsMgr ). Count == 0 ) {
var action = CreateElement ( "action" );
action . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.media.action.GET_CONTENT" ));
var intent = CreateElement ( "intent" );
intent . AppendChild ( action );
var queries = SelectSingleNode ( "/manifest/queries" ) as XmlElement ;
queries . AppendChild ( intent );
changed = true ;
}
return changed ;
2026-06-25 15:48:45 +08:00
}
2026-06-30 22:15:06 +08:00
internal bool AddMicrophone () {
bool changed = false ;
if ( SelectNodes ( "/manifest/uses-permission[@android:name='android.permission.MICROPHONE']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-permission" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.permission.MICROPHONE" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
if ( SelectNodes ( "/manifest/uses-feature[@android:name='android.hardware.microphone']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-feature" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.hardware.microphone" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
// cf. https://github.com/gree/unity-webview/issues/679
// cf. https://github.com/fluttercommunity/flutter_webview_plugin/issues/138#issuecomment-559307558
// cf. https://stackoverflow.com/questions/38917751/webview-webrtc-not-working/68024032#68024032
// cf. https://stackoverflow.com/questions/40236925/allowing-microphone-accesspermission-in-webview-android-studio-java/47410311#47410311
if ( SelectNodes ( "/manifest/uses-permission[@android:name='android.permission.MODIFY_AUDIO_SETTINGS']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-permission" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.permission.MODIFY_AUDIO_SETTINGS" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
if ( SelectNodes ( "/manifest/uses-permission[@android:name='android.permission.RECORD_AUDIO']" , nsMgr ). Count == 0 ) {
var elem = CreateElement ( "uses-permission" );
elem . Attributes . Append ( CreateAndroidAttribute ( "name" , "android.permission.RECORD_AUDIO" ));
ManifestElement . AppendChild ( elem );
changed = true ;
}
return changed ;
2026-06-25 15:48:45 +08:00
}
}
}
#endif