Android Device Testing

Several of my developer friends have contacted me over the past few months to ask me about which devices we test TouchDraw for Android on given that there are lots of little layout differences depending on the device you're on.  Now that it is available for non-Samsung devices (and now available globally via the Google Play store), I thought it's finally time to publicly document which devices we test and develop with.

Early on, we decided that we needed the UI to be adaptive based on the screen size, pixel density and orientation of the device so that we could try to maximize the area dedicated to the drawing canvas and deliver an app that could work well on devices with a 4" screen or bigger.  The unfortunate side effect of that decision was that we kept needing more devices as we created layouts targeting the different configuration.  While we could have used the emulator, we we felt that we couldn't get a feel for the size of the tap targets and the general usability without real physical devices.

Here's a table the outlines the final tally of devices that test, target and develop with:

LDPI MDPI HDPI XHDPI
small
normal Samsung Galaxy S2 Samsung Galaxy S3
Google Nexus 4
large Samsung Galaxy Tab 2 7.0 Google Nexus 7 (TVDPI) Samsung Galaxy Note
Samsung Galaxy Note 2
xlarge Motorola Xoom
Samsung Galaxy Note 2 10.1
Asus Transformer Infinity Google Nexus 10

As you can see, we don't support or target devices with LDPI and/or small screens; however given TouchDraws' target mareket I don't think we're excluding many potential customers. You'll also notice that we consider the Nexus 7 (even though it's an TVDPI device) to be our Large/HDPI test device, since there don't seem to be too many of these devices in the (mainstream) wild.

It may seem a bit excessive to try to target so many combinations individually; however by doing so, we were able to build the app in a way that it is (and can easily be) optimized for the device you're using it on instead of a "one size fit" all compromised UI.

The surprise for most people is that of all of the devices above, the one that we target and test on first is the Galaxy Tab 2 7.0.  We've found that because of it's resolution (1024x600) and low screen density that if we can come up with a UI paradigm that works on it, we can usually scale it up to the other devices.  Also, being the lowest powered device, it's the best way to get immediate feedback on potential performance implications of new drawing features.  With that said, the more we test with the Nexus 7, the more we find that it's the worst performer of all devices and is slowly becoming our device for performance tuning.

In addition the screen issues, I also should mention that at least for us it was important to have actual devices with different hardware accessories as well.  Specifically, we wanted test devices with styluses (which the Galaxy Note devices provide) and hardware keyboards (which the keyboard dock of the Transformer Infinity provides).

Realistically, I don't think we could have developed TouchDraw (at least with the level of polish that it has) without this many device variations to test with. 

As our stable of test devices grows and/or changes ove time I'll try to make an effort to update this for future reference, though once I finally have time to play with the Xamarin Test Cloud, we may not need many more devices.

TouchDraw Code Reuse - Updated

As we just seeded the third beta release of TouchDraw for Android to our testers, I thought it would be a good time to publish some updated statistics on the code re-use in TouchDraw since we now have near final statistics for the Android version.

For those of you that didn't read the first post (and the associated disclaimers related to how these statistics were calculated), you can find it here.

As always, here are the pretty pictures first:

Next, here is the raw output from the script that calculated those statistics:

app      t       u      s       u%      s%
Mac 121114 28945 92169 23.90% 76.10%
Android 128036 35867 92169 28.01% 71.99%
iOS 141949 55363 86586 39.00% 61.00%

t is Total lines of code
u is Unique (platform specific) lines of code
s in Shared lines of code.

At a first glance, it seems like the Android version requires quite a bit less code than the iPad version; however the statistics don't tell the whole story.  What's not accounted for in the numbers is all of the things (layouts, menus, etc...) that is managed in code in the iPad version vs managed via resource files in Android.  

If we take into consideration all of the resource files, then platform specific "code" (term used loosely in this case) jumps up to around 35% for the Android version.  If our expectations hold true then the platform specific code in TouchDraw for iPad should drop to around 30 to 35% once we get version 2.0 finished.

I'm sure we would not have been able to build and deliver the first Android beta version in less than 3.5 months if we wouldn't have been able to re-use so much of our code base, and if we didn't have great tooling (MonoTouch, MonoMac and Mono for Android) at our disposal to enable this.  

Calling a dynamic library from MonoMac - Part 1

I ran into some difficulty today calling a native C library from MonoMac today and would have killed to have a working example to play with and build off of.  Since I finally was able to get everything working, I thought I'd share the sample here so that if someone else finds themselves in the same position I was they'll hopefully find this post.  (Besides, I'll probably need this as a refresher the next time I try to do this.)

This post will simply cover having a C# application call a native library with Mono from the command line.  I'll follow up with a second post later that explains how to integrate the library into a packaged MonoMac application.

First, in order to call a native library, we'll need a native library to call.  For the purposes of this sample, we'll create a simple function that echo's an integer value back to us.  Here's the C source code for that simple function:

//
// echo.c
//

#include <stdio.h>

int EchoInteger(int value)
{
    return value;
}

Second, we'll need to compile the source code:

gcc -arch i386 -c echo.c -o bin/echo.o

Third, we'll need to create dynamic library:

gcc -arch i386 \
 -dynamiclib \
 -o bin/libEchoTest.dylib \
 bin/echo.o \
 -install_name $CURRENT_DIR/libEchoTest.dylib

Next, one of the problems that I ran into originally was that my library wasn't built properly and the  function I was trying to call wasn't actually exposed externally in the library.  If you want to verify which symbols are exposed externally, you can use the "nm" command.  Here's how it would be invoked for this library, along with the results of execution.

/Applications/Xcode.app/Contents/Developer/usr/bin/nm -g bin/libEchoTest.dylib

00000f90 T _EchoInteger
         U dyld_stub_binder

The key thing to notice is the "T _EchoInteger" entry, which tells us the function we wrote is exposed externally.

Now that we know that our library is built correctly, we can move onto the C# call to invoke it.:

//
// Main.cs
//

using System;
using System.Runtime.InteropServices;

namespace MonoMacLoadLib
{
	class MainClass
	{
		[DllImport ("EchoTest")]
 		public static extern int EchoInteger (int arg);
 			
		static void Main (string[] args)
		{
			int echoResults = EchoInteger(11);
			System.Console.WriteLine("Echo Results:{0}",echoResults);
		}
	}
}

The key things is the code are the [DllImport("EchoTest")] public static extern int EchoInteger(int arg); lines.  These will bind the C# method to the C function via P/Invoke.  By default (as shown above), it will bind the C# method to a function of the same name (which in our case is "EchoInteger" in both the C and the C# code).  If you want to C# name to be different than in the C library, you can add an EntryPoint to the annotation: [DllImport ("EchoTest", EntryPoint="SomeOtherName")]

Now that we have the C# code written, we can move onto compiling and trying to run this.  Here is the command necessary to compile that class with Mono:

dmcs /noconfig "/out:bin/Test.exe" \
 "/r:/Library/Frameworks/Mono.framework/Versions/2.10.9/lib/mono/4.0/System.dll" \
 "/r:/Applications/MonoDevelop.app/Contents/MacOS/lib/monodevelop/AddIns/MonoDevelop.MonoMac/MonoMac.dll" \
 /nologo /warn:4 /debug:full /optimize- /codepage:utf8 /platform:x86 /define:DEBUG  /t:exe "Main.cs"

And here is the command necessary to run this:

mono bin/Test.exe

If everything went successfully you should see the following output:

Echo Results:11

Troubleshooting

If you run into problems (as I did originally) you may see a couple different errors/exceptions.  Here are a few things to look for and possible solutions to them:

The first error that you may see is the System.EntryPointNotFoundException exception.  If this happens, then you know that Mono was able to find your library; however it wasn't able to find the function that you referenced with your [DllImport] annotation.  You can use the "nm" command referenced above to make sure the expected function is externally available in the library, and you can also verify that your EntryPoint entry or C# method name are spelled correctly to match the C function.

The second error that you may see a System.DllNotFoundException exception.  This will occur if Mono can't find and load the dynamic library.  To resolve this, make sure that the name of the library is correct in your [DllImport] annotation, and make sure that it's in a location that it can be found by Mono.

Lastly, if you want to verify that Mono can even load your dynamic library, you can manually load the library using "MonoMac.ObjCRuntime.Dlfcn.dlopen()" method.  If that method is able to load the library, then it will return an IntPtr referencing the loaded library, otherwise it will return a 0 (IntPtr.Zero).  This was useful to me diagnose that I had errors in my library that prevented it from being loaded even though it was named and placed in the correct location.  Here is the C# sample from above that has been modified to also call the "dlopen" method as a first step.

using System;
using System.Runtime.InteropServices;
using MonoMac.ObjCRuntime;

namespace MonoMacLoadLib
{
	class MainClass
	{
		[DllImport ("EchoTest", EntryPoint="EchoInteger")]
 		public static extern int EchoInteger (int arg);
 			
		static void Main (string[] args)
		{
			// The current directly is expected to be passed in as arg[0] for this sample to work
			string path = args[0] + "/bin/libEchoTest.dylib";
			System.Console.WriteLine("Dylib Path:{0}", path);	
					
			IntPtr libPtr = Dlfcn.dlopen(path, 1);
			System.Console.WriteLine("Pointer:{0}",libPtr);
			
			int echoResults = EchoInteger(11);
			System.Console.WriteLine("Echo Results:{0}",echoResults);
		}
	}
}	

The source code for this sample is available on GitHub, along with a shell script that will compile everything and run the sample.