/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
* copy, modify, and distribute this software in source code or binary form for use
* in connection with the web services and APIs provided by Facebook.
*
* As with any software that integrates with the Facebook platform, your use of
* this software is subject to the Facebook Developer Principles and Policies
* [http://developers.facebook.com/policy/]. This copyright notice shall be
* included in all copies or substantial portions of the software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace UnityEditor.FacebookEditor
{
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Facebook.Unity;
using UnityEditor;
using UnityEngine;
public class ManifestMod
{
public const string AppLinkActivityName = "com.facebook.unity.FBUnityAppLinkActivity";
public const string DeepLinkingActivityName = "com.facebook.unity.FBUnityDeepLinkingActivity";
public const string LoginActivityName = "com.facebook.LoginActivity";
public const string UnityLoginActivityName = "com.facebook.unity.FBUnityLoginActivity";
public const string UnityDialogsActivityName = "com.facebook.unity.FBUnityDialogsActivity";
public const string UnityGameRequestActivityName = "com.facebook.unity.FBUnityGameRequestActivity";
public const string UnityGameGroupCreateActivityName = "com.facebook.unity.FBUnityCreateGameGroupActivity";
public const string UnityGameGroupJoinActivityName = "com.facebook.unity.FBUnityJoinGameGroupActivity";
public const string UnityAppInviteDialogActivityName = "com.facebook.unity.AppInviteDialogActivity";
public const string ApplicationIdMetaDataName = "com.facebook.sdk.ApplicationId";
public const string FacebookContentProviderName = "com.facebook.FacebookContentProvider";
public const string FacebookContentProviderAuthFormat = "com.facebook.app.FacebookContentProvider{0}";
public const string FacebookActivityName = "com.facebook.FacebookActivity";
public static void GenerateManifest()
{
var outputFile = Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml");
// only copy over a fresh copy of the AndroidManifest if one does not exist
if (!File.Exists(outputFile))
{
var inputFile = Path.Combine(
EditorApplication.applicationContentsPath,
"PlaybackEngines/androidplayer/AndroidManifest.xml");
if (!File.Exists(inputFile))
{
// Unity moved this file. Try to get it at its new location
inputFile = Path.Combine(
EditorApplication.applicationContentsPath,
"PlaybackEngines/AndroidPlayer/Apk/AndroidManifest.xml");
}
File.Copy(inputFile, outputFile);
}
UpdateManifest(outputFile);
}
public static bool CheckManifest()
{
bool result = true;
var outputFile = Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml");
if (!File.Exists(outputFile))
{
Debug.LogError("An android manifest must be generated for the Facebook SDK to work. Go to Facebook->Edit Settings and press \"Regenerate Android Manifest\"");
return false;
}
XmlDocument doc = new XmlDocument();
doc.Load(outputFile);
if (doc == null)
{
Debug.LogError("Couldn't load " + outputFile);
return false;
}
XmlNode manNode = FindChildNode(doc, "manifest");
XmlNode dict = FindChildNode(manNode, "application");
if (dict == null)
{
Debug.LogError("Error parsing " + outputFile);
return false;
}
XmlElement loginElement;
if (!ManifestMod.TryFindElementWithAndroidName(dict, UnityLoginActivityName, out loginElement))
{
Debug.LogError(string.Format("{0} is missing from your android manifest. Go to Facebook->Edit Settings and press \"Regenerate Android Manifest\"", LoginActivityName));
result = false;
}
var deprecatedMainActivityName = "com.facebook.unity.FBUnityPlayerActivity";
XmlElement deprecatedElement;
if (ManifestMod.TryFindElementWithAndroidName(dict, deprecatedMainActivityName, out deprecatedElement))
{
Debug.LogWarning(string.Format("{0} is deprecated and no longer needed for the Facebook SDK. Feel free to use your own main activity or use the default \"com.unity3d.player.UnityPlayerNativeActivity\"", deprecatedMainActivityName));
}
return result;
}
public static void UpdateManifest(string fullPath)
{
string appId = FacebookSettings.AppId;
if (!FacebookSettings.IsValidAppId)
{
Debug.LogError("You didn't specify a Facebook app ID. Please add one using the Facebook menu in the main Unity editor.");
return;
}
XmlDocument doc = new XmlDocument();
doc.Load(fullPath);
if (doc == null)
{
Debug.LogError("Couldn't load " + fullPath);
return;
}
XmlNode manNode = FindChildNode(doc, "manifest");
XmlNode dict = FindChildNode(manNode, "application");
if (dict == null)
{
Debug.LogError("Error parsing " + fullPath);
return;
}
string ns = dict.GetNamespaceOfPrefix("android");
// add the unity login activity
XmlElement unityLoginElement = CreateUnityOverlayElement(doc, ns, UnityLoginActivityName);
ManifestMod.SetOrReplaceXmlElement(dict, unityLoginElement);
// add the unity dialogs activity
XmlElement unityDialogsElement = CreateUnityOverlayElement(doc, ns, UnityDialogsActivityName);
ManifestMod.SetOrReplaceXmlElement(dict, unityDialogsElement);
// add the login activity
XmlElement loginElement = CreateLoginElement(doc, ns);
ManifestMod.SetOrReplaceXmlElement(dict, loginElement);
ManifestMod.AddAppLinkingActivity(doc, dict, ns, FacebookSettings.AppLinkSchemes[FacebookSettings.SelectedAppIndex].Schemes);
ManifestMod.AddSimpleActivity(doc, dict, ns, DeepLinkingActivityName, true);
ManifestMod.AddSimpleActivity(doc, dict, ns, UnityGameRequestActivityName);
ManifestMod.AddSimpleActivity(doc, dict, ns, UnityGameGroupCreateActivityName);
ManifestMod.AddSimpleActivity(doc, dict, ns, UnityGameGroupJoinActivityName);
ManifestMod.AddSimpleActivity(doc, dict, ns, UnityAppInviteDialogActivityName);
// add the app id
//
XmlElement appIdElement = doc.CreateElement("meta-data");
appIdElement.SetAttribute("name", ns, ApplicationIdMetaDataName);
appIdElement.SetAttribute("value", ns, "fb" + appId);
ManifestMod.SetOrReplaceXmlElement(dict, appIdElement);
// Add the facebook content provider
//
XmlElement contentProviderElement = CreateContentProviderElement(doc, ns, appId);
ManifestMod.SetOrReplaceXmlElement(dict, contentProviderElement);
// Add the facebook activity
//
XmlElement facebookElement = CreateFacebookElement(doc, ns);
ManifestMod.SetOrReplaceXmlElement(dict, facebookElement);
// Save the document formatted
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = "\r\n",
NewLineHandling = NewLineHandling.Replace
};
using (XmlWriter xmlWriter = XmlWriter.Create(fullPath, settings))
{
doc.Save(xmlWriter);
}
}
private static XmlNode FindChildNode(XmlNode parent, string name)
{
XmlNode curr = parent.FirstChild;
while (curr != null)
{
if (curr.Name.Equals(name))
{
return curr;
}
curr = curr.NextSibling;
}
return null;
}
private static void SetOrReplaceXmlElement(
XmlNode parent,
XmlElement newElement)
{
string attrNameValue = newElement.GetAttribute("name");
string elementType = newElement.Name;
XmlElement existingElment;
if (TryFindElementWithAndroidName(parent, attrNameValue, out existingElment, elementType))
{
parent.ReplaceChild(newElement, existingElment);
}
else
{
parent.AppendChild(newElement);
}
}
private static bool TryFindElementWithAndroidName(
XmlNode parent,
string attrNameValue,
out XmlElement element,
string elementType = "activity")
{
string ns = parent.GetNamespaceOfPrefix("android");
var curr = parent.FirstChild;
while (curr != null)
{
var currXmlElement = curr as XmlElement;
if (currXmlElement != null &&
currXmlElement.Name == elementType &&
currXmlElement.GetAttribute("name", ns) == attrNameValue)
{
element = currXmlElement;
return true;
}
curr = curr.NextSibling;
}
element = null;
return false;
}
private static void AddSimpleActivity(XmlDocument doc, XmlNode xmlNode, string ns, string className, bool export = false)
{
XmlElement element = CreateActivityElement(doc, ns, className, export);
ManifestMod.SetOrReplaceXmlElement(xmlNode, element);
}
private static XmlElement CreateLoginElement(XmlDocument doc, string ns)
{
//
//
XmlElement activityElement = ManifestMod.CreateActivityElement(doc, ns, LoginActivityName);
activityElement.SetAttribute("configChanges", ns, "keyboardHidden|orientation");
activityElement.SetAttribute("theme", ns, "@android:style/Theme.Translucent.NoTitleBar.Fullscreen");
return activityElement;
}
private static XmlElement CreateUnityOverlayElement(XmlDocument doc, string ns, string activityName)
{
//
//
XmlElement activityElement = ManifestMod.CreateActivityElement(doc, ns, activityName);
activityElement.SetAttribute("configChanges", ns, "fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen");
activityElement.SetAttribute("theme", ns, "@android:style/Theme.Translucent.NoTitleBar.Fullscreen");
return activityElement;
}
private static XmlElement CreateFacebookElement(XmlDocument doc, string ns)
{
//
//
XmlElement activityElement = ManifestMod.CreateActivityElement(doc, ns, FacebookActivityName);
activityElement.SetAttribute("configChanges", ns, "keyboard|keyboardHidden|screenLayout|screenSize|orientation");
activityElement.SetAttribute("label", ns, "@string/app_name");
activityElement.SetAttribute("theme", ns, "@android:style/Theme.Translucent.NoTitleBar");
return activityElement;
}
private static XmlElement CreateContentProviderElement(XmlDocument doc, string ns, string appId)
{
XmlElement provierElement = doc.CreateElement("provider");
provierElement.SetAttribute("name", ns, FacebookContentProviderName);
string authorities = string.Format(CultureInfo.InvariantCulture, FacebookContentProviderAuthFormat, appId);
provierElement.SetAttribute("authorities", ns, authorities);
provierElement.SetAttribute("exported", ns, "true");
return provierElement;
}
private static XmlElement CreateActivityElement(XmlDocument doc, string ns, string activityName, bool exported = false)
{
//
//
XmlElement activityElement = doc.CreateElement("activity");
activityElement.SetAttribute("name", ns, activityName);
if (exported)
{
activityElement.SetAttribute("exported", ns, "true");
}
return activityElement;
}
private static void AddAppLinkingActivity(XmlDocument doc, XmlNode xmlNode, string ns, List schemes)
{
XmlElement element = ManifestMod.CreateActivityElement(doc, ns, AppLinkActivityName, true);
foreach (var scheme in schemes)
{
// We have to create an intent filter for each scheme since an intent filter
// can have only one data element.
XmlElement intentFilter = doc.CreateElement("intent-filter");
var action = doc.CreateElement("action");
action.SetAttribute("name", ns, "android.intent.action.VIEW");
intentFilter.AppendChild(action);
var category = doc.CreateElement("category");
category.SetAttribute("name", ns, "android.intent.category.DEFAULT");
intentFilter.AppendChild(category);
XmlElement dataElement = doc.CreateElement("data");
dataElement.SetAttribute("scheme", ns, scheme);
intentFilter.AppendChild(dataElement);
element.AppendChild(intentFilter);
}
ManifestMod.SetOrReplaceXmlElement(xmlNode, element);
}
}
}