Friday 13 July 2012

Porting some Android SDK samples

As I mentioned last time, I’ve been porting some of the Java Android SDK demos over to Oxygene for Java. This is for various purposes:

  • to ensure I am on top of various Java code layout approaches. The one that requires the most faffing about is nested classes. In Java the nested class gets access to members of the outer class, thanks to having an implicit this parameter passed through the constructor. That requires a bit of manual rewiring, which is a bit of a bore, but no big deal.
  • to get some exposure to various additional Android programming areas.
  • to run more and more code through Oxygene for Java and report any latent bugs that show up when trying it out.
  • to build up more useful Oxygene for Java examples that can be packaged in with the product and thus be of reference use to Oxygene users
  • to find out what issues and bugs are lying in wait for unsuspecting newcomers to Android programming within the recesses of those SDK demos.

I’ve uploaded a bunch of them to Google Play now (see the end of this post) so anyone interested can check them out and report any problems left in there so I can try and remedy them. Of course they are demo apps and so you shouldn’t expect too much of them. They’re just there to demonstrate certain Android programming techniques and so aren’t professional-level applications.

In the last post I looked at the poor support for the exit-the-app-and-come-back situation with Lunar Lander and JetBoy. Well, poor support isn’t really accurate as the apps just crash out when you try and resume them. Poor implementation, poor planning, something like that perhaps.

Anyway, other common themes that come out of more demos are as follows:

1) Background image resources are not resized to accommodate screen sizes. Perhaps back when these demos were written, the various bitmaps used in AccelerometerPlay, JetBoy were larger than the largest of screens. Not now with tablets and large display phones.

In the case of an image on a game that will get an initial size on startup and not change, for example a game that is fixed to landscape or portrait orientation, then you can size the bitmap once after having loaded it. JetBoy is fixed to landscape orientation thanks to this attribute in its activity declaration in the Android manifest file:

android:screenOrientation="landscape"

Similarly the AccelerometerPlay demo is forced to portrait with this attribute in the activity declaration in the Android manifest:

android:screenOrientation="portrait"

If you draw in a View descendant, then override the onSizeChanged() method and you can resize the bitmap by passing it to the static Bitmap method Bitmap.createScaledBitmap() along with the target width and height, typically as passed in parameters to onSizeChanged(). For example, in the accelerometer demo I now have this:

method SimulationView.onSizeChanged(w, h, oldw, oldh: Integer);
begin
  // other stuff is here
  ...
  // Resize the background bitmap
  // NOTE: original code did not resize the background bitmap
  // so it did not cover the background on larger displays
  mWood := Bitmap.createScaledBitmap(mWood, w, h, true)
end;

If you’re working with a SurfaceView and have implemented the SurfaceHolder.Callback interface then you can do much the same thing in the surfaceChanged() method.

You can also choose to size the bitmap on-demand when it’s needed from its original size to the required size for the target canvas. This will be a little less performant than a one-off resize but can be useful. For example, the JetBoy app has a ready screen before play starts. The background to that screen was not resized appropriately. If you didn’t want to resize the background bitmap once ahead of time you could identify the current size and target size as Rect objects and call an appropriate override of the Canvas object’s drawBitmap() method:

method JetBoyThread.doDrawReady(aCanvas: Canvas);
begin
  var src := new Rect(0, 0, mTitleBG.Width, mTitleBG.Height);
  var dest := new Rect(0, 0, aCanvas.Width, aCanvas.Height);
  aCanvas.drawBitmap(mTitleBG, src, dest, nil);
end;

2) Older games were coded to assume the presence of a directional touchpad (D-Pad). Most modern devices come without one of these, so to make them usable on current devices I’ve had to add in onTouchEvent() overrides in either the View or Activity for simple screen touch responses. In the case of the Snake demo app I implemented GestureDetector.SimpleOnGestureListener to handle directional swipes.

3) Some demos have been updated over the Android releases to take on newer behaviour. For example, there is a simple Bluetooth Chat demo. You can install it and run it on two devices and the users can connect and do a simple Instant Message type chat. In the most recent SDKs for Android 4.0.x (Ice Cream Sandwich) this demo has been tweaked compared to what you see in the samples for the older Android versions. This newer version uses the so-called Holographic theme introduced in Android 3, which adds an ActionBar to the app as a side-effect. What are menu options in the earlier version are now ActionBar actions when installed on a more recent OS.

Also the newer version of the app offers both secure and insecure Bluetooth connections, something the original version didn’t as insecure Bluetooth connections were introduced in Android 2.3.3 (Gingerbread MR1).

The initial question was which version of the demo to bring over to Oxygene. In the end I got the translated app to operate on any Android version from 2.0 (Eclair) onwards.

To get the Holographic theme to be applied for Android 3 (Honeycomb) and later (and thus get the ActionBar) I took advantage of Android’s resource system (as mentioned here). I defined an app theme to be based on Android’s default old-school Theme in a resource file in res\values as per usual.

<resources
    xmlns:android="
http://schemas.android.com/apk/res/android">
  <style name="AppTheme" parent="@android:style/Theme">
  </style>
</resources>

But then I defined the same app theme to be based on Theme.Holo in a resource file in res\values-v11.

<resources
    xmlns:android=
http://schemas.android.com/apk/res/android>
  <style name="AppTheme" parent="@android:style/Theme.Holo">
  </style>
</resources>

If running on API 11 (Android 3.0 aka Honeycomb) or later the theme definition will be pulled from this latter directory. All that’s left to finish the job is to apply the app theme to the application in the Android manifest file:

<application android:label="@string/app_name"
             android:icon="@drawable/app_icon"
             android:theme="@style/AppTheme"
             android:debuggable="true" >

  ...
</application>

That’s the appearance. But it was also important to ensure the option for establishing an insecure Bluetooth connection was removed from the menu if running on Android earlier than 2.3.3. Since the menu is defined in a menu resource file this simply involved having two menu definitions, one in the usual res\menu directory and one in res\menu-v10. The menu definition in res\menu has the problem option removed; the menu definition in menu-v10 will be used on Android API 11 (2.3.3) or later and has all menu options defined.

The final aspect of keeping the app version-agnostic is to check the code and what relies on the newer aspects of the source. Anything that needs to be skipped on Android versions earlier than 2.3.3 can be conditionalised using Build.VERSION.SDK_INT, something like this:

if Build.VERSION.SDK_INT >=
     Build.VERSION_CODES.GINGERBREAD_MR1 then
begin
  ...
end;

Uploaded demos

Ok, so I’m done pulling Java code over to Oxygene and making it work properly for the time being. I’ve tidied up the demos that I’ve ported and uploaded them to Google Play, giving as useful a description as I can for each of them. The following are online – if you encounter any problems or have any simple improvement suggestions (bear in mind they are meant to just be ports of existing demos) I would appreciate hearing from you.

  • AccelerometerPlay: demonstrates the accelerometer sensor by representing a slab of wood with three shiny balls on it. Tilt the device and the balls roll in a quite realistic manner.
  • Bluetooth Chat: install on 2 Android phones and you can establish a connection (secure or, if on Android 2.3.3 or later, insecure) and have an IM-style chat
  • Cube Live Wallpaper: two live wallpapers. One is a rotating wire cube. One has a settings page to allow to to choose between a rotating wire cube and a rotating wire dodecahedron. On Android home screens that support it (not HTC Sense 3.0 or later, or Android 4.0 or later) the wallpaper responds to offset messages, meaning that the shape will rotate as you move from home screen to home screen.
  • JetBoy: a scroller game that demonstrates Android’s JET audio engine. Destroy a certain number of asteroids in a certain amount of time by firing in time to the music.
  • Lunar Lander: use direction and thrusters to safely land on the landing pad. I’ve mapped touches on areas of the screen (as per the app description on Google Play) to the D-Pad controls, but apparently it’s still a but tricky. This could just be a function of the game implementation, or maybe the D-Pad to screen mappings. Any thoughts, let me know!
  • Snake: The classic snake game. Swipe the screen to change the snake’s direction and reach the apples. Mind yourself and mind the walls!
  • Wiktionary ‘Word of the Day’ Widget: install on your home screen and you get Wiktionary’s word of the day displayed, along with the word’s definition. Touch the widget to open up a Wiktionary browser where you can follow links to other words, search for words or go to a random word.

The full set of Oxygene-ported Android SDK apps can be found at this link.

Alternatively all my Oxygene-powered Android apps can be found at this link.

4 comments:

  1. I am always getting to learn something from your post, your posts has made me learn to deal with Android in a exact way on different subject.

    ReplyDelete
  2. Thanks for the Bluetooth Chat program!

    Does it use the same UUIDs that the SDK sample does? I would like to try communicating with it from a non-Android platform.

    Thank you

    ReplyDelete
    Replies
    1. As far as I recall it was a straight port, but I can't be 100% positive until I can get back and look at the source I compiled up.
      Do give it a whirl and let me know if you have problems.

      Delete