diff --git a/samples/HelloWorld/HelloLibrary/HelloLibrary.csproj b/samples/HelloWorld/HelloLibrary/HelloLibrary.csproj index aa9cb8a9b8b..b613bde6304 100644 --- a/samples/HelloWorld/HelloLibrary/HelloLibrary.csproj +++ b/samples/HelloWorld/HelloLibrary/HelloLibrary.csproj @@ -14,6 +14,8 @@ 512 false portable + True + Resource + diff --git a/samples/HelloWorld/HelloLibrary/LibraryActivity.cs b/samples/HelloWorld/HelloLibrary/LibraryActivity.cs index f876c69d0f5..5e3147b8db6 100644 --- a/samples/HelloWorld/HelloLibrary/LibraryActivity.cs +++ b/samples/HelloWorld/HelloLibrary/LibraryActivity.cs @@ -16,7 +16,7 @@ using Android.Views; using Android.Widget; -namespace Mono.Samples.Hello +namespace HelloLibrary { [Activity(Label = "Library Activity", Name="mono.samples.hello.LibraryActivity")] public class LibraryActivity : Activity diff --git a/samples/HelloWorld/HelloLibrary/Resources/values/Attr.xml b/samples/HelloWorld/HelloLibrary/Resources/values/Attr.xml new file mode 100644 index 00000000000..6b39720b438 --- /dev/null +++ b/samples/HelloWorld/HelloLibrary/Resources/values/Attr.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/HelloWorld/HelloWorld/HelloWorld.csproj b/samples/HelloWorld/HelloWorld/HelloWorld.csproj index c0313fa8c99..17f2e7ab90d 100644 --- a/samples/HelloWorld/HelloWorld/HelloWorld.csproj +++ b/samples/HelloWorld/HelloWorld/HelloWorld.csproj @@ -66,6 +66,7 @@ + diff --git a/samples/HelloWorld/HelloWorld/Resources/values/Attr.xml b/samples/HelloWorld/HelloWorld/Resources/values/Attr.xml new file mode 100644 index 00000000000..5fbb641e067 --- /dev/null +++ b/samples/HelloWorld/HelloWorld/Resources/values/Attr.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs index 947ca275d78..928fdfd6e4f 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using Mono.Cecil.Cil; using System.Text.RegularExpressions; +using Mono.Collections.Generic; #if ILLINK using Microsoft.Android.Sdk.ILLink; #endif @@ -69,8 +70,17 @@ protected bool FindResourceDesigner (AssemblyDefinition assembly, bool mainAppli protected void ClearDesignerClass (TypeDefinition designer) { LogMessage ($" TryRemoving {designer.FullName}"); - designer.NestedTypes.Clear (); - designer.Methods.Clear (); + // for each of the nested types clear all but the + // int[] fields. + for (int i = designer.NestedTypes.Count -1; i >= 0; i--) { + var nestedType = designer.NestedTypes [i]; + RemoveFieldsFromType (nestedType, designer.Module); + if (nestedType.Fields.Count == 0) { + // no fields we do not need this class at all. + designer.NestedTypes.RemoveAt (i); + } + } + RemoveUpdateIdValues (designer); designer.Fields.Clear (); designer.Properties.Clear (); designer.CustomAttributes.Clear (); @@ -117,6 +127,48 @@ protected void FixType (TypeDefinition type, TypeDefinition localDesigner) } } + protected void RemoveFieldsFromType (TypeDefinition type, ModuleDefinition module) + { + for (int i = type.Fields.Count - 1; i >= 0; i--) { + var field = type.Fields [i]; + if (field.FieldType.IsArray) { + continue; + } + LogMessage ($"Removing {type.Name}::{field.Name}"); + type.Fields.RemoveAt (i); + } + } + + protected void RemoveUpdateIdValues (TypeDefinition type) + { + foreach (var method in type.Methods) { + if (method.Name.Contains ("UpdateIdValues")) { + FixUpdateIdValuesBody (method); + } else { + FixBody (method.Body, type); + } + } + + foreach (var nestedType in type.NestedTypes) { + RemoveUpdateIdValues (nestedType); + } + } + + protected void FixUpdateIdValuesBody (MethodDefinition method) + { + List finalInstructions = new List (); + Collection instructions = method.Body.Instructions; + for (int i = 0; i < method.Body.Instructions.Count-1; i++) { + Instruction instruction = instructions[i]; + string line = instruction.ToString (); + bool found = line.Contains ("Int32[]") || instruction.OpCode == OpCodes.Ret; + if (!found) { + method.Body.Instructions.Remove (instruction); + i--; + } + } + } + protected void FixupAssemblyTypes (AssemblyDefinition assembly, TypeDefinition designer) { foreach (ModuleDefinition module in assembly.Modules) diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs index db75512db24..2ef7e074cdc 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs @@ -55,14 +55,19 @@ protected override void FixBody (MethodBody body, TypeDefinition designer) Dictionary instructions = new Dictionary(); var processor = body.GetILProcessor (); string designerFullName = $"{designer.FullName}/"; + bool isDesignerMethod = designerFullName.Contains (body.Method.DeclaringType.FullName); + string declaringTypeName = body.Method.DeclaringType.Name; foreach (var i in body.Instructions) { string line = i.ToString (); - if (line.Contains (designerFullName) && !instructions.ContainsKey (i)) + if ((line.Contains (designerFullName) || (isDesignerMethod && i.OpCode == OpCodes.Stsfld)) && !instructions.ContainsKey (i)) { var match = opCodeRegex.Match (line); if (match.Success && match.Groups.Count == 5) { string key = match.Groups[4].Value.Replace (designerFullName, string.Empty); + if (isDesignerMethod) { + key = declaringTypeName +"::" + key; + } if (designerConstants.ContainsKey (key) && !instructions.ContainsKey (i)) instructions.Add(i, designerConstants [key]); } diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index d00dc301db8..90a922b03f4 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -712,5 +712,116 @@ public void SingleProject_ApplicationId () Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log")); Assert.IsTrue (didStart, "Activity should have started."); } + + [Test] + public void AppWithStyleableUsageRuns ([Values (true, false)] bool isRelease, [Values (true, false)] bool linkResources) + { + AssertHasDevices (); + + var rootPath = Path.Combine (Root, "temp", TestName); + var lib = new XamarinAndroidLibraryProject () { + ProjectName = "Styleable.Library" + }; + + lib.AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\values\\styleables.xml") { + TextContent = () => @" + + + + + +", + }); + lib.AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\layout\\librarylayout.xml") { + TextContent = () => @" + +", + }); + lib.Sources.Add (new BuildItem.Source ("MyLibraryLayout.cs") { + TextContent = () => @"using System; + +namespace Styleable.Library { + public class MyLibraryLayout : Android.Widget.LinearLayout + { + + public MyLibraryLayout (Android.Content.Context context, Android.Util.IAttributeSet attrs) : base (context, attrs) + { + Android.Content.Res.TypedArray a = context.Theme.ObtainStyledAttributes (attrs, Resource.Styleable.MyLibraryView, 0,0); + try { + bool b = a.GetBoolean (Resource.Styleable.MyLibraryView_MyBool, defValue: false); + if (!b) + throw new Exception (""MyBool was not true.""); + int i = a.GetInteger (Resource.Styleable.MyLibraryView_MyInt, defValue: -1); + if (i != 128) + throw new Exception (""MyInt was not 128.""); + } + finally { + a.Recycle(); + } + } + } +}" + }); + + proj = new XamarinAndroidApplicationProject () { + IsRelease = isRelease, + }; + proj.AddReference (lib); + + proj.AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\values\\styleables.xml") { + TextContent = () => @" + + + + + +", + }); + proj.SetProperty ("AndroidLinkResources", linkResources ? "False" : "True"); + proj.LayoutMain = proj.LayoutMain.Replace ("", ""); + + proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_MAINACTIVITY}", +@"public class MyLayout : Android.Widget.LinearLayout +{ + + public MyLayout (Android.Content.Context context, Android.Util.IAttributeSet attrs) : base (context, attrs) + { + Android.Content.Res.TypedArray a = context.Theme.ObtainStyledAttributes (attrs, Resource.Styleable.MyView, 0,0); + try { + bool b = a.GetBoolean (Resource.Styleable.MyView_MyBool, defValue: false); + if (!b) + throw new Exception (""MyBool was not true.""); + int i = a.GetInteger (Resource.Styleable.MyView_MyInt, defValue: -1); + if (i != 128) + throw new Exception (""MyInt was not 128.""); + } + finally { + a.Recycle(); + } + } +} +"); + + var abis = new string [] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }; + proj.SetAndroidSupportedAbis (abis); + var libBuilder = CreateDllBuilder (Path.Combine (rootPath, lib.ProjectName)); + Assert.IsTrue (libBuilder.Build (lib), "Library should have built succeeded."); + builder = CreateApkBuilder (Path.Combine (rootPath, proj.ProjectName)); + + + Assert.IsTrue (builder.Install (proj), "Install should have succeeded."); + + if (Builder.UseDotNet) + Assert.True (builder.RunTarget (proj, "Run"), "Project should have run."); + else if (CommercialBuildAvailable) + Assert.True (builder.RunTarget (proj, "_Run"), "Project should have run."); + else + AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity"); + + var didStart = WaitForActivityToStart (proj.PackageName, "MainActivity", + Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log")); + Assert.IsTrue (didStart, "Activity should have started."); + } } }