Fixes: dotnet#1296
Xamarin.Android attempts to expose the Java-based Android API as a
".NET feeling" API. This takes many forms, such as prefixing interface
names with `I`, mapping `get` and `set` methods to properties, mapping
listener interfaces to events, and PascalCasing method names.
This also affects Java package names and C# namespaces.
When creating the `.apk` file, we philosophically need to go the
opposite direction: PascalCased members need to be mapped to
"something appropriate" within Java. For example, many Android
Resource ids *must* be all lowercase, and Android doesn't support
package names starting with an uppercase letter in all circumstances.
At one point, we tried mapping C# PascalCased namespaces to camelCased
namespaces, so e.g. `MyExampleNamespace` became the
`myExampleNamesapce` Java package within Java Callable Wrappers.
This turned out to be a Terrible Mistake, particularly on
case-sensitive filesystems, as if casing was *inconsistent*
(`MyExampleNamespace` vs `MyExamplenamespace`), files might not be
packaged.
By Mono for Android 1.0, we settled on just lowercasing the namespace
name to produce Java package names within Android Callable Wrappers.
This was not without it's own problems; in particular, the assembly
name wasn't involved, so if the "same" type (namespace + type) were
present in two different assemblies and we needed to generate Android
Callable Wrappers, they'd "collide," the build would fail, and we'd
have some unhappy customers.
This was later addressed in Xamarin.Android 5.1 by changing the Java
package name generation algorithm to be an MD5SUM of the assembly name
and namespace name, thus allowing types to have assembly identity.
(This was not without its own problems.)
Then it gets slightly more complicated: Android allows type names to
appear in various locations, such as in layout View XML. These don't
allow "just" using the type name; the package name is required for
types outside the `android.widget` Java package.
Initially, we did nothing, so developers had to directly use the
Android Callable Wrapper names:
<myexamplenamespace.MyCustomCSharpView android:id="@+id/yay_csharp" .../>
<fragment android:name="myexamplenamespace.MyCustomCSharpFragment" ... />
With the change in Xamarin.Android 5.1, *this couldn't work*; those
types didn't exist anymore.
To square this circle, we processed the resource files to "fixup"
identifiers and replace them with the actual Android Callable Wrapper
names. We'd replace any/all of:
MyExampleNamespace.MyCustomCSharpView // Full name
MyExampleNamespace.MyCustomCSharpView, MyAssembly // Partial assembly-qualified name
MyExampleNamespace.MyCustomCSharpView, MyAssembly, Version=... // Full assembly qualified name
myexamplenamespace.MyCustomCSharpView // compatibility name
with the appropriate md5'd Android Callable Wrapper name.
Brilliant as this was, there was a problem: [Bug #61073][61073].
If the assembly had a wildcard in the assembly version:
[61073]: https://bugzilla.xamarin.com/show_bug.cgi?id=61073
[assembly: AssemblyVersion ("1.0.0.*")]
then the "Full assembly qualified name" value would change on *every
build*, which had numerious unintended knock-on effects.
This was fixed in commit e5b1c92, which worked largely by just
killing the Full assembly qualified name version entirely.
Xamarin.Android doesn't support embedding two different versions of
the same assembly, so this was considered to be fine.
...except for one compatibility case: `<fragment/>`s can contain
~arbitrary strings, and we support replacing the entire Full assembly
qualified name within them:
<fragment
android:name="MyExampleNamespace.MyCustomCSharpFragment, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
...
/>
However, in a post e5b1c92 world, the above now fails to load on the
device, because it's *not* being appropriately fixed up!
FATAL EXCEPTION: main
Process: Mono.Samples.HelloTests, PID: 22977
java.lang.RuntimeException: Unable to start activity ComponentInfo{Mono.Samples.HelloTests/mono.samples.HelloApp}: android.view.InflateException: Binary XML file line #1: Binary XML file line #1: Error inflating class fragment
...
Caused by: android.view.InflateException: Binary XML file line #1: Binary XML file line #1: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #1: Error inflating class fragment
Caused by: android.app.Fragment$InstantiationException: Unable to instantiate fragment Mono.Samples.Hello.MyFragment, Hello, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: make sure class name exists, is public, and has an empty constructor that is public
...
Caused by: java.lang.ClassNotFoundException: Didn't find class "Mono.Samples.Hello.MyFragment, Hello, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" on path: DexPathList[[zip file "/data/app/Mono.Samples.HelloTests-1/base.apk"],nativeLibraryDirectories=[/data/app/Mono.Samples.HelloTests-1/lib/arm64, /system/fake-libs64, /data/app/Mono.Samples.HelloTests-1/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]
...
The problem is that what Android "sees" *should* be
<fragment
android:name="md5whatever.MyCustomCSharpFragment"
...
/>
where `md5whatever.MyCustomCSharpFragment` *is* a valid Java type that
Android is able to load successfully, but because of e5b1c92 this
replacement was removed.
The fix: simplify the Full assembly-qualified name case to an already
supported example. If the `//fragment/@android:name` value contains a
`,`, assume it's an assembly qualified name and compute the Full name
from it, by stripping off the comma and everything after it, then use
the Full name to lookup the correct Android Callable Wrapper type.