Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions samples/HelloWorld/HelloLibrary/HelloLibrary.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<FileAlignment>512</FileAlignment>
<AndroidApplication>false</AndroidApplication>
<DebugType>portable</DebugType>
<AndroidUseIntermediateDesignerFile>True</AndroidUseIntermediateDesignerFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
</PropertyGroup>
<Import
Condition="Exists('..\..\..\Configuration.props')"
Expand Down Expand Up @@ -71,5 +73,6 @@
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\Case_Check.png" />
<AndroidResource Include="Resources\values\Attr.xml" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion samples/HelloWorld/HelloLibrary/LibraryActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions samples/HelloWorld/HelloLibrary/Resources/values/Attr.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyLibraryWidget">
<attr name="library_bool_attr" format="boolean" />
<attr name="library_int_attr" format="integer" />
</declare-styleable>
</resources>
1 change: 1 addition & 0 deletions samples/HelloWorld/HelloWorld/HelloWorld.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<ItemGroup>
<AndroidResource Include="Resources\layout\Main.axml" />
<AndroidResource Include="Resources\values\Strings.xml" />
<AndroidResource Include="Resources\values\Attr.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\Icon.png" />
Expand Down
7 changes: 7 additions & 0 deletions samples/HelloWorld/HelloWorld/Resources/values/Attr.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyWidget">
<attr name="bool_attr" format="boolean" />
<attr name="int_attr" format="integer" />
</declare-styleable>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ();
Expand Down Expand Up @@ -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<Instruction> finalInstructions = new List<Instruction> ();
Collection<Instruction> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,19 @@ protected override void FixBody (MethodBody body, TypeDefinition designer)
Dictionary<Instruction, int> instructions = new Dictionary<Instruction, int>();
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]);
}
Expand Down
111 changes: 111 additions & 0 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => @"<?xml version='1.0' encoding='utf-8'?>
<resources>
<declare-styleable name='MyLibraryView'>
<attr name='MyBool' format='boolean' />
<attr name='MyInt' format='integer' />
</declare-styleable>
</resources>",
});
lib.AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\layout\\librarylayout.xml") {
TextContent = () => @"<?xml version='1.0' encoding='utf-8'?>
<Styleable.Library.MyLibraryLayout xmlns:app='http://schemas.android.com/apk/res-auto' app:MyBool='true' app:MyInt='128'/>
",
});
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 = () => @"<?xml version='1.0' encoding='utf-8'?>
<resources>
<declare-styleable name='MyView'>
<attr name='MyBool' format='boolean' />
<attr name='MyInt' format='integer' />
</declare-styleable>
</resources>",
});
proj.SetProperty ("AndroidLinkResources", linkResources ? "False" : "True");
proj.LayoutMain = proj.LayoutMain.Replace ("<LinearLayout", "<UnnamedProject.MyLayout xmlns:app='http://schemas.android.com/apk/res-auto' app:MyBool='true' app:MyInt='128'")
.Replace ("</LinearLayout>", "</UnnamedProject.MyLayout>");

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.");
}
}
}