Skip to content

Commit df583c1

Browse files
committed
[Xamarin.Android.Build.Tasks] MAM Member Remapping?
Context: dotnet/java-interop#867 Context: dotnet/java-interop#936 Does It Build? Does It Work? (Is It Sane?) For local "test" purposes, add a new `tools/remap-mam-json-to-xml` utility which parses the MAM JSON file into XML. $ dotnet run --project tools/remap-mam-json-to-xml -- \ $HOME/.nuget/packages/microsoft.intune.mam.remapper.tasks/0.1.4635.1/content/MonoAndroid10/remapping-config.json <replacements> <replace-type from="android/app/Activity" to="com/microsoft/intune/mam/client/app/MAMActivity" /> … <replace-method source-type="com/microsoft/intune/mam/client/support/v7/app/MAMAppCompatActivity" source-method-name="onStateNotSaved" target-type="com/microsoft/intune/mam/client/support/v7/app/MAMAppCompatActivity" target-method-name="onMAMStateNotSaved" target-method-instance-to-static="false" /> </replacements> dotnet bin/Debug/net6.0/remap-mam-json-to-xml.dll $HOME/.nuget/packages/microsoft.intune.mam.remapper.tasks/0.1.4635.1/content/MonoAndroid10/remapping-config.json Add a new `@(_AndroidMamMappingFile)` item group which can be used to produce the mapping XML file. TODO: why is `$(_AndroidMamMappingXml)` e.g. `obj/Debug/assets/obj/Debug/assets/xa-mam-mapping.xml`? What's with the duplication? Update src/java-runtime to use `javac -h`, to emit JNI headers. (`javah` is not present in JDK11!) Update src/monodroid to depend on src/java-runtime, then use the headers generated by `javac -h` to ensure consistency. Rando aside: apparently `createNewContextWithData` was inconsistent?)
1 parent 98d243e commit df583c1

File tree

21 files changed

+698
-26
lines changed

21 files changed

+698
-26
lines changed

.gitmodules

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
branch = main
1313
[submodule "external/Java.Interop"]
1414
path = external/Java.Interop
15-
url = https://github.com/xamarin/java.interop.git
16-
branch = main
15+
url = https://github.com/jonpryor/java.interop.git
16+
branch = jonp-member-remapping
1717
[submodule "external/lz4"]
1818
path = external/lz4
1919
url = https://github.com/lz4/lz4.git

samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,19 @@
1010
<ItemGroup>
1111
<Compile Remove="Properties\AssemblyInfo.cs" />
1212
</ItemGroup>
13+
<ItemGroup>
14+
<PackageReference
15+
Include="Microsoft.Intune.MAM.Remapper.Tasks"
16+
Version="0.1.4635.1"
17+
IncludeAssets="none"
18+
GeneratePathProperty="True"
19+
ReferenceOutputAssembly="False"
20+
/>
21+
</ItemGroup>
22+
<Target Name="_AddMamFiles"
23+
BeforeTargets="_AddAndroidCustomMetaData">
24+
<ItemGroup>
25+
<_AndroidMamMappingFile Include="$(PkgMicrosoft_Intune_MAM_Remapper_Tasks)/content/MonoAndroid10/remapping-config.json" />
26+
</ItemGroup>
27+
</Target>
1328
</Project>

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Runtime.CompilerServices;
66
using System.Runtime.InteropServices;
7+
using System.Runtime.Versioning;
78
using System.Text;
89
using System.Threading;
910
using System.Reflection;
@@ -259,29 +260,107 @@ protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpl
259260
yield return t;
260261
}
261262

263+
#if NET
264+
[RequiresPreviewFeatures]
265+
#endif // NET
262266
protected override string? GetSimpleReference (Type type)
263267
{
264268
string? j = JNIEnv.TypemapManagedToJava (type);
265269
if (j != null) {
266-
return j;
270+
return
271+
#if NET
272+
GetReplacementTypeCore (j) ??
273+
#endif // NET
274+
j;
267275
}
268276
if (JNIEnv.IsRunningOnDesktop) {
269277
return JavaNativeTypeManager.ToJniName (type);
270278
}
271279
return null;
272280
}
273281

282+
#if NET
283+
[RequiresPreviewFeatures]
284+
#endif // NET
274285
protected override IEnumerable<string> GetSimpleReferences (Type type)
275286
{
276287
string? j = JNIEnv.TypemapManagedToJava (type);
277288
if (j != null) {
278-
yield return j;
289+
yield return
290+
#if NET
291+
GetReplacementTypeCore (j) ??
292+
#endif // NET
293+
j;
279294
}
280295
if (JNIEnv.IsRunningOnDesktop) {
281296
yield return JavaNativeTypeManager.ToJniName (type);
282297
}
283298
}
284299

300+
#if NET
301+
[RequiresPreviewFeatures]
302+
protected override IReadOnlyList<string>? GetStaticMethodFallbackTypesCore (string jniSimpleReference)
303+
{
304+
return new[]{$"{jniSimpleReference}$-CC"};
305+
}
306+
307+
[RequiresPreviewFeatures]
308+
protected override string? GetReplacementTypeCore (string jniSimpleReference)
309+
{
310+
if (JNIEnv.ReplacementTypes == null) {
311+
return null;
312+
}
313+
Logger.Log (LogLevel.Warn, "*jonp*", $"# jonp: looking for replacement type for `{jniSimpleReference}`");
314+
if (JNIEnv.ReplacementTypes.TryGetValue (jniSimpleReference, out var v)) {
315+
return v;
316+
}
317+
return null;
318+
}
319+
320+
[RequiresPreviewFeatures]
321+
protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature)
322+
{
323+
if (JNIEnv.ReplacementMethods == null) {
324+
return null;
325+
}
326+
Logger.Log (LogLevel.Warn, "*jonp*", $"# jonp: looking for replacement method for (\"{jniSourceType}\", \"{jniMethodName}\", \"{jniMethodSignature}\")");
327+
if (!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, jniMethodSignature), out var r) &&
328+
!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, GetAlternateMethodSignature ()), out r) &&
329+
!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, null), out r)) {
330+
return null;
331+
}
332+
var targetSig = r.TargetSignature;
333+
var paramCount = r.ParamCount;
334+
if (targetSig == null && r.TurnStatic) {
335+
targetSig = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length);
336+
paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature);
337+
paramCount++;
338+
}
339+
Logger.Log (LogLevel.Warn, "*jonp*", $"# jonp: found replacement: ({GetValue (r.TargetType)}, {GetValue (r.TargetName)}, {GetValue (r.TargetSignature)}, {r.ParamCount?.ToString () ?? "null"}, {r.TurnStatic})");
340+
return new JniRuntime.ReplacementMethodInfo {
341+
SourceJniType = jniSourceType,
342+
SourceJniMethodName = jniMethodName,
343+
SourceJniMethodSignature = jniMethodSignature,
344+
TargetJniType = r.TargetType ?? jniSourceType,
345+
TargetJniMethodName = r.TargetName ?? jniMethodName,
346+
TargetJniMethodSignature = targetSig ?? jniMethodSignature,
347+
TargetJniMethodParameterCount = paramCount,
348+
TargetJniMethodInstanceToStatic = r.TurnStatic,
349+
};
350+
351+
string GetAlternateMethodSignature ()
352+
{
353+
int i = jniMethodSignature.IndexOf (')');
354+
return jniMethodSignature.Substring (0, i+1);
355+
}
356+
357+
string GetValue (string? value)
358+
{
359+
return value == null ? "null" : $"\"{value}\"";
360+
}
361+
}
362+
#endif // NET
363+
285364
delegate Delegate GetCallbackHandler ();
286365

287366
static MethodInfo? dynamic_callback_gen;

src/Mono.Android/Android.Runtime/JNIEnv.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@
1515
using Java.Interop.Tools.TypeNameMappings;
1616
using System.Diagnostics.CodeAnalysis;
1717

18+
#if NET
19+
using ReplacementTypesDict = System.Collections.Generic.Dictionary<string, string>;
20+
using ReplacementMethodsDict = System.Collections.Generic.Dictionary<
21+
(string SourceType, string SourceName, string? SourceSignature),
22+
(string? TargetType, string? TargetName, string? TargetSignature, int? ParamCount, bool TurnStatic)
23+
>;
24+
#endif // NET
25+
1826
namespace Android.Runtime {
1927
#pragma warning disable 0649
2028
struct JnienvInitializeArgs {
@@ -35,6 +43,8 @@ struct JnienvInitializeArgs {
3543
public int packageNamingPolicy;
3644
public byte ioExceptionType;
3745
public int jniAddNativeMethodRegistrationAttributePresent;
46+
public IntPtr mappingXml;
47+
public int mappingXmlLen;
3848
}
3949
#pragma warning restore 0649
4050

@@ -61,6 +71,11 @@ public static partial class JNIEnv {
6171
static AndroidRuntime? androidRuntime;
6272
static BoundExceptionType BoundExceptionType;
6373

74+
#if NET
75+
internal static ReplacementTypesDict? ReplacementTypes;
76+
internal static ReplacementMethodsDict? ReplacementMethods;
77+
#endif // NET
78+
6479
[ThreadStatic]
6580
static byte[]? mvid_bytes;
6681

@@ -196,6 +211,14 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
196211
SynchronizationContext.SetSynchronizationContext (Android.App.Application.SynchronizationContext);
197212
#endif
198213

214+
#if NET
215+
if (args->mappingXml != IntPtr.Zero) {
216+
var xml = Encoding.UTF8.GetString ((byte*) args->mappingXml, args->mappingXmlLen);
217+
Logger.Log (LogLevel.Warn, "*jonp*", $"# jonp: mapping xml: len={args->mappingXmlLen}; {xml}");
218+
(ReplacementTypes, ReplacementMethods) = MamXmlParser.Parse (xml);
219+
}
220+
#endif // NET
221+
199222
if (logTiming) {
200223
monodroid_timing_stop (total_timing_sequence, "JNIEnv.Initialize end");
201224
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// File must be "stand-alone"; it's included by
2+
// `tools/remap-mam-json-to-xml`
3+
4+
#nullable enable
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Globalization;
9+
using System.IO;
10+
using System.Xml;
11+
12+
using ReplacementTypesDict = System.Collections.Generic.Dictionary<string, string>;
13+
using ReplacementMethodsDict = System.Collections.Generic.Dictionary<
14+
(string SourceType, string SourceName, string? SourceSignature),
15+
(string? TargetType, string? TargetName, string? TargetSignature, int? ParamCount, bool IsStatic)
16+
>;
17+
18+
namespace Android.Runtime {
19+
20+
class MamXmlParser {
21+
22+
public static (ReplacementTypesDict ReplacementTypes, ReplacementMethodsDict ReplacementMethods) Parse (string xml)
23+
{
24+
var replacementTypes = new ReplacementTypesDict ();
25+
var replacementMethods = new ReplacementMethodsDict ();
26+
27+
using var t = new StringReader (xml);
28+
using var reader = XmlReader.Create (t, new XmlReaderSettings { XmlResolver = null });
29+
while (reader.Read ()) {
30+
if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment) {
31+
continue;
32+
}
33+
if (!reader.IsStartElement ()) {
34+
continue;
35+
}
36+
if (!reader.HasAttributes) {
37+
continue;
38+
}
39+
switch (reader.LocalName) {
40+
case "replace-type":
41+
ParseReplaceTypeAttributes (replacementTypes, reader);
42+
break;
43+
case "replace-method":
44+
ParseReplaceMethodAttributes (replacementMethods, reader);
45+
break;
46+
}
47+
}
48+
49+
return (replacementTypes, replacementMethods);
50+
}
51+
52+
static void ParseReplaceTypeAttributes (ReplacementTypesDict replacementTypes, XmlReader reader)
53+
{
54+
// <replace-type
55+
// from="android/app/Activity"'
56+
// to="com/microsoft/intune/mam/client/app/MAMActivity"
57+
// />
58+
string? from = null;
59+
string? to = null;
60+
while (reader.MoveToNextAttribute ()) {
61+
switch (reader.LocalName) {
62+
case "from":
63+
from = reader.Value;
64+
break;
65+
case "to":
66+
to = reader.Value;
67+
break;
68+
}
69+
}
70+
if (string.IsNullOrEmpty (from) || string.IsNullOrEmpty (to)) {
71+
return;
72+
}
73+
replacementTypes [from] = to;
74+
}
75+
76+
static void ParseReplaceMethodAttributes (ReplacementMethodsDict replacementMethods, XmlReader reader)
77+
{
78+
// <replace-method
79+
// source-type="jni-simple-type"
80+
// source-method-name="method-name"
81+
// source-method-signature="jni-method-signature"
82+
// target-type="jni-simple-type"
83+
// target-method-name="method-name"
84+
// target-method-signature="jni-method-signature"
85+
// target-method-parameter-count="int"
86+
// target-method-instance-to-static="bool"
87+
// />
88+
89+
string? sourceType = null;
90+
string? sourceMethod = null;
91+
string? sourceMethodSig = null;
92+
string? targetType = null;
93+
string? targetMethod = null;
94+
string? targetMethodSig = null;
95+
int? targetMethodParamCount = null;
96+
bool targetMethodInstanceToStatic = false;
97+
98+
while (reader.MoveToNextAttribute ()) {
99+
switch (reader.LocalName) {
100+
case "source-type":
101+
sourceType = reader.Value;
102+
break;
103+
case "source-method-name":
104+
sourceMethod = reader.Value;
105+
break;
106+
case "source-method-signature":
107+
sourceMethodSig = reader.Value;
108+
break;
109+
case "target-type":
110+
targetType = reader.Value;
111+
break;
112+
case "target-method-name":
113+
targetMethod = reader.Value;
114+
break;
115+
case "target-method-signature":
116+
targetMethodSig = reader.Value;
117+
break;
118+
case "target-method-parameter-count":
119+
if (int.TryParse (reader.Value, 0, CultureInfo.InvariantCulture, out var v)) {
120+
targetMethodParamCount = v;
121+
}
122+
break;
123+
case "target-method-instance-to-static":
124+
targetMethodInstanceToStatic = reader.Value == "true";
125+
break;
126+
}
127+
}
128+
if (string.IsNullOrEmpty (sourceType) || string.IsNullOrEmpty (sourceMethod)) {
129+
return;
130+
}
131+
replacementMethods [(sourceType, sourceMethod, sourceMethodSig)]
132+
= (targetType, targetMethod, targetMethodSig, targetMethodParamCount, targetMethodInstanceToStatic);
133+
}
134+
}
135+
}

src/Mono.Android/Mono.Android.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<Nullable>enable</Nullable>
2323
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
2424
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
25+
<EnablePreviewFeatures>True</EnablePreviewFeatures>
2526
</PropertyGroup>
2627

2728
<PropertyGroup Condition=" '$(TargetFramework)' == 'monoandroid10' ">
@@ -244,6 +245,7 @@
244245
<Compile Include="Android.Runtime\JObjectRefType.cs" />
245246
<Compile Include="Android.Runtime\JValue.cs" />
246247
<Compile Include="Android.Runtime\Logger.cs" />
248+
<Compile Include="Android.Runtime\MamXmlParser.cs" />
247249
<Compile Include="Android.Runtime\NamespaceMappingAttribute.cs" />
248250
<Compile Include="Android.Runtime\OutputStreamAdapter.cs" />
249251
<Compile Include="Android.Runtime\OutputStreamInvoker.cs" />
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.IO;
3+
4+
using Microsoft.Build.Framework;
5+
using Microsoft.Build.Utilities;
6+
using Microsoft.Android.Build.Tasks;
7+
8+
namespace Xamarin.Android.Tasks
9+
{
10+
public class MamJsonToXml : AndroidTask
11+
{
12+
public override string TaskPrefix => "A2C";
13+
14+
[Required]
15+
public ITaskItem[] MappingFiles { get; set; }
16+
17+
[Required]
18+
public ITaskItem XmlMappingOutput { get; set; }
19+
20+
public override bool RunTask ()
21+
{
22+
var parser = new MamJsonParser (this.CreateTaskLogger ());
23+
foreach (var file in MappingFiles) {
24+
parser.Load (file.ItemSpec);
25+
}
26+
var tree = parser.ToXml ();
27+
using (var o = File.CreateText (XmlMappingOutput.ItemSpec)) {
28+
o.WriteLine (tree.ToString ());
29+
}
30+
return true;
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)