public override bool Execute() { AndroidManifest manifest; using (var stream = GetType().Assembly.GetManifestResourceStream("Xamarin.Android.Lite.Tasks.AndroidManifest.xml")) { manifest = AndroidManifest.Read(stream); } string versionCode = string.IsNullOrEmpty(VersionCode) ? "1" : VersionCode; string versionName = string.IsNullOrEmpty(VersionName) ? "1.0" : VersionName; var manifestElement = manifest.Document; if (manifestElement.Name != "manifest") { Log.LogError("No `manifest` element found!"); return(false); } var application = manifestElement.Element("application"); if (application == null) { Log.LogError("No `application` element found!"); return(false); } var ns = AndroidManifest.AndroidNamespace.Namespace; var name = ns + "name"; manifest.Mutate(manifestElement, "package", PackageName); manifest.Mutate(manifestElement, ns + "versionCode", versionCode); manifest.Mutate(manifestElement, ns + "versionName", versionName); var metadata = application.Elements("meta-data").Where(e => e.Attribute(name)?.Value == ApplicationMetadata).FirstOrDefault(); if (metadata == null) { Log.LogError("No `meta-data` element found!"); return(false); } manifest.Mutate(metadata, ns + "value", ApplicationClass); //NOTE: two other Xamarin.Android implementation-specific places *may* need the package name replaced var provider = application.Element("provider"); if (provider != null) { manifest.Mutate(provider, ns + "authorities", PackageName + ".mono.MonoRuntimeProvider.__mono_init_"); } var category = application.Elements("receiver") .Where(e => e.Attribute(name)?.Value == "mono.android.Seppuku") .FirstOrDefault()? .Element("intent-filter")? .Element("category"); if (category != null) { manifest.Mutate(category, name, "mono.android.intent.category.SEPPUKU." + PackageName); } using (var stream = File.Create(DestinationFile)) { manifest.Write(stream); } return(!Log.HasLoggedErrors); }
/// <summary> /// NOTE: does not dispose the stream /// </summary> /// <param name="onChunk">Mainly used for testing, verifying correctness in tests</param> public static AndroidManifest Read(Stream stream, Action <ChunkType, int, long> onChunk = null) { var manifest = new AndroidManifest(); var namespaces = new Dictionary <string, XName> (); XElement xml = null; var buffer = new byte [4]; var stringTable = manifest.Strings = new List <string> (); int chunk; while ((chunk = ReadInt(buffer, stream)) != -1) { int chunkSize = ReadInt(buffer, stream); //length of stream onChunk?.Invoke((ChunkType)chunk, chunkSize, stream.Position); switch ((ChunkType)chunk) { case ChunkType.START_DOC: { //Don't need to do anything break; } case ChunkType.STR_TABLE: { int stringCount = ReadInt(buffer, stream); int styleCount = ReadInt(buffer, stream); int flags = ReadInt(buffer, stream); int stringsOffset = ReadInt(buffer, stream); int stylesOffset = ReadInt(buffer, stream); var stringOffsets = ReadArray(buffer, stream, stringCount); int [] styleOffsets = ReadArray(buffer, stream, styleCount); int size = ((stylesOffset == 0) ? chunkSize : stylesOffset) - stringsOffset; var strings = new byte [size]; stream.Read(strings, 0, size); for (int i = 0; i < stringOffsets.Length; i++) { stringTable.Add(GetString(stringOffsets [i], strings)); } break; } case ChunkType.RESOURCES: { int [] resources = ReadArray(buffer, stream, chunkSize / 4 - 2); manifest.Resources = new List <int> (resources); break; } case ChunkType.NS_TABLE: { int namespaceCount = ReadInt(buffer, stream); int dunno = ReadInt(buffer, stream); //0xFFFFFFF int [] namespaceData = ReadArray(buffer, stream, namespaceCount); for (int i = 0; i < namespaceCount - 1; i += 2) { var name = stringTable [namespaceData [i]]; var url = stringTable [namespaceData [i + 1]]; namespaces [url] = XNamespace.Get(url) + name; } break; } case ChunkType.START_TAG: { if (stringTable.Count == 0) { throw new InvalidOperationException($"Unexpected file format, {nameof (ChunkType.STR_TABLE)} not found!"); } int lineNumber = ReadInt(buffer, stream); int dunno = ReadInt(buffer, stream); //0xFFFFFFFF int ns = ReadInt(buffer, stream); int name = ReadInt(buffer, stream); int flags = ReadInt(buffer, stream); int attributeCount = ReadInt(buffer, stream) & 0xFFFF; int classAttribute = ReadInt(buffer, stream); string nameText = stringTable [name]; if (xml == null) { manifest.Document = xml = new XElement(nameText); foreach (var knownNs in namespaces) { xml.SetAttributeValue(XNamespace.Xmlns + knownNs.Value.LocalName, knownNs.Value.NamespaceName); } } else { var child = new XElement(nameText); xml.Add(child); xml = child; } for (int i = 0; i < attributeCount; i++) { int attrNs = ReadInt(buffer, stream); int attrName = ReadInt(buffer, stream); int attrValue = ReadInt(buffer, stream); int attrType = ReadInt(buffer, stream); int attrData = ReadInt(buffer, stream); string attrNameText = stringTable [attrName]; XName attributeName; if (attrNs == -1) { attributeName = attrNameText; } else { var nsUrl = stringTable [attrNs]; attributeName = XName.Get(attrNameText, nsUrl); } XAttribute newAttr; switch ((AttributeType)attrType) { case AttributeType.Integer: newAttr = new XAttribute(attributeName, attrData); break; case AttributeType.String: newAttr = new XAttribute(attributeName, stringTable [attrData]); break; case AttributeType.Resource: //TODO: need the string instead? newAttr = new XAttribute(attributeName, attrData); break; case AttributeType.Enum: newAttr = new XAttribute(attributeName, attrData); break; case AttributeType.Bool: //NOTE: looks like this is -1=True 0=False ??? newAttr = new XAttribute(attributeName, attrData == -1); break; default: newAttr = new XAttribute(attributeName, $"[Unknown Data Type: {attrType.ToString ("X")}, Value: {attrData}]"); break; } newAttr.AddAnnotation(attrType); xml.Add(newAttr); } break; } case ChunkType.END_TAG: { int lineNumber = ReadInt(buffer, stream); int dunno = ReadInt(buffer, stream); //0xFFFFFFFF int ns = ReadInt(buffer, stream); int name = ReadInt(buffer, stream); xml = xml.Parent; break; } case ChunkType.END_DOC: { int fileVersion = ReadInt(buffer, stream); int [] dunno = ReadArray(buffer, stream, 3); //-1, android, NS url manifest.PlatformBuildVersionName = stringTable [fileVersion]; break; } default: throw new InvalidOperationException($"Invalid chunk `{chunk.ToString ("X")}` at position `{stream.Position}`!"); } } return(manifest); }
public override bool Execute() { AndroidManifest manifest; using (var stream = GetType().Assembly.GetManifestResourceStream("Xamarin.Android.Lite.Tasks.AndroidManifest.xml")) { manifest = AndroidManifest.Read(stream); } string versionCode = string.IsNullOrEmpty(VersionCode) ? "1" : VersionCode; string versionName = string.IsNullOrEmpty(VersionName) ? "1.0" : VersionName; var manifestElement = manifest.Document; if (manifestElement.Name != "manifest") { Log.LogError("No `manifest` element found!"); return(false); } var application = manifestElement.Element("application"); if (application == null) { Log.LogError("No `application` element found!"); return(false); } var ns = AndroidManifest.AndroidNamespace.Namespace; var name = ns + "name"; manifest.Mutate(manifestElement, "package", PackageName); manifest.Mutate(manifestElement, ns + "versionCode", versionCode); manifest.Mutate(manifestElement, ns + "versionName", versionName); if (!string.IsNullOrEmpty(AppTitle)) { manifest.Mutate(application, ns + "label", AppTitle); } var metadata = application.Elements("meta-data").Where(e => e.Attribute(name)?.Value == ApplicationMetadata).FirstOrDefault(); if (metadata == null) { Log.LogError("No `meta-data` element found!"); return(false); } manifest.Mutate(metadata, ns + "value", ApplicationClass); var activity = application.Elements("activity").Where(e => e.Attribute(name)?.Value == ActivityName).FirstOrDefault(); if (activity == null) { Log.LogError($"No `activity` element found of name `{ActivityName}`!"); return(false); } if (!string.IsNullOrEmpty(ActivityTitle)) { manifest.Mutate(activity, ns + "label", ActivityTitle); } foreach (var provider in application.Elements("provider")) { var authorities = ns + "authorities"; var attribute = provider.Attribute(authorities); if (attribute != null && attribute.Value.StartsWith(PackageNamePrefix, StringComparison.Ordinal)) { var value = PackageName + attribute.Value.Substring(PackageNamePrefix.Length - 1, attribute.Value.Length - PackageNamePrefix.Length + 1); manifest.Mutate(provider, authorities, value); } } var category = application.Elements("receiver") .Where(e => e.Attribute(name)?.Value == "mono.android.Seppuku") .FirstOrDefault()? .Element("intent-filter")? .Element("category"); if (category != null) { manifest.Mutate(category, name, "mono.android.intent.category.SEPPUKU." + PackageName); } using (var stream = File.Create(DestinationFile)) { manifest.Write(stream); } return(!Log.HasLoggedErrors); }