Upgrading DSL From CoffeeScript to JSON: Part.2. Redefine DSL behavior

This is the second post in this series, previous one discussed the JSON schema migration mechanism.

After finish JSON DSL implementation, the No.1 problem I need to handle is how to upgrading the configuration in CoffeeScript to JSON format.

One of the solutions is to do it manually. Well, it is possible, but… JSON isn’t a language really designed for human, composing configuration for 50+ sites doesn’t sound like a pleasant work for me. Even when I finished it, how can I ensure all the configuration is properly upgraded? The sun is bright today, I don’t want to waste whole afternoon in front of the computer checking the JSON. In one word, I’m lazy…

Since most of the change of DSL is happened on representation instead of structure. So in most cases, there is 1-to-1 mapping between v1 DSL and v2 DSL. So maybe I can generate the most of v2 DSL by using V1 DSL! Then manually handle some exceptions.

Here is a snippet of V1 DSL

Ver 1 DSL snippet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
AdKiller.run ->
@host 'imagetwist.com', ->
@clean('#myad', '#popupOverlay')
@revealImg('.pic')
@host 'yankoimages.net', ->
@clean('#firopage')
@revealImg('img')
@host 'imageback.info', 'imagepong.info', 'imgking.us', 'imgabc.us', ->
@revealA('.text_align_center a')
@host 'imgserve.net',
'imgcloud.co',
'hosterbin.com',
'myhotimage.org',
'img-zone.com',
'imgtube.net',
'pixup.us',
'imgcandy.net',
'croftimage.com',
'www.imagefolks.com',
'imgcloud.co',
'imgmoney.com',
'imagepicsa.com',
'imagecorn.com',
'imgcorn.com',
"imgboo.me",
'imgrim.com',
'imgdig.com',
'imgnext.com',
'hosturimage.com',
'image-gallery.us',
'imgmaster.net',
'img.spicyzilla.com',
'bulkimg.info',
'pic.apollon-fervor.com',
'08lkk.com',
'damimage.com',
->
@click('#continuetoimage input')
@clean('#logo')
@safeRevealImg('#container img[class]')

In version 1 implementation, @host defines the sites. And in the block of @host method, @click, @clean, @revealImg methods define the actions for the sites. The @host method instantiate new instance of Cleaner. The code block is invoked when cleaner is triggered, which does the actually cleaning.

Now I want to keep this file, since it shares the configuration between version 1 and version 2. And I redefine the behaviors of the cleaning method, such as @clean, @click, etc., I generate the JSON data when it is invoked instead of really altering the DOM. So I got this:

seed_data_generator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/env coffee
fs = require('fs')
AdKiller =
run: (block) ->
@scripts = {}
@hosts = {}
block.call(this)
fs.writeFileSync 'seed_data.json', JSON.stringify({version: 1, @hosts, @scripts})
host: (hosts..., block) ->
@currentScript = []
scriptId = hosts[0]
@scripts[scriptId] = @currentScript
for host in hosts
@hosts[host] = scriptId
block.call(this)
remove: (selectors...) ->
selectors.unshift 'remove'
@currentScript.push selectors
clean: (selectors...) -> # Backward compatibility
selectors.unshift 'remove'
@currentScript.push selectors
hide: (selectors...) ->
selectors.unshift 'clean'
@currentScript.push selectors
click: (selector) ->
@currentScript.push ['click', selector]
revealA: (selector) ->
@currentScript.push ['revealA', selector]
revealImg: (selector) ->
@currentScript.push ['revealImg', selector]
safeRevealA: (selector) ->
@currentScript.push ['safeRevealA', selector]
safeRevealImg: (selector) ->
@currentScript.push ['safeRevealImg', selector]
AdKiller.run ->
@host 'imagetwist.com', ->
@clean('#myad', '#popupOverlay')
@revealImg('.pic')
# ...

So now I can easily invoking this piece of code, to convert verion 1 DSL to JSON format.

DSL behavior redefinition is a super powerful trick, we used it on JSON parsing, validation, and generation before on PlayUp project. Which saved us tons of time from writing boring code.

Chrome Extension RPC Router

When developing chrome extension, communication between background script and content scripts is very typical use case. Chrome provides messaging APIs to achieve this goal. But this API has limitation that every message goes to the same listener.

Background script in Chrome extension usually works as a function hub for the whole extension, so background scripts usually required to process different types of messages. Then the limit of Chrome messaging API become an issue we need to face.

There are several approaches to resolve the limitation. Since API allow multiple listener, a simple and cheap solution is Responsibility Chain; adding listener for each message type, and checking message type at beginning of the listener.

Responsbility Chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
if(message.type != 'reloadData')
return;
// reload data logic here
});
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
if(message.type != 'updateBrowerAction')
return;
// update browser action logic here
});

This approach works, but not that graceful. And there is potential performance issue when message types increases.

So I come up a new more graceful solution: RPC Message Router

RCPRouter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
extractResponseHandler = (args) ->
return undefined if args.length == 0
last = args.pop()
if typeof last == 'function'
return last
else
args.push last
return undefined
class @RPCRouter
constructor: ->
chrome.runtime.onMessage.addListener @handleMassage
handleMassage: (message, sender, sendResponse) =>
{method: methodName, args} = message
method = this[methodName]
unless method?
console.error "Unknown RPC method: %s", methodName
return
args.push sendResponse
console.log "RPC Call: %s, args: %o", methodName, args
method.apply(this, args)
callBackground: (method, args...) ->
responseHandler = extractResponseHandler(args)
console.log "RPC Call to Background: %s, args: %o", method, args
chrome.runtime.sendMessage {method, args}, responseHandler
callTab: (tabId, method, args...) ->
responseHandler = extractResponseHandler(args)
console.log "RPC Call to Tab \"%s\": %s, args: %o", tabId, method, args
chrome.tabs.sendMessage tabId, {method, args}, responseHandler

So this new RPCRouter wraps the chrome original messaging API and provides a more convenient way to invoke a remote method. To create a specific RPC Router for background page or content page is quite easy.

Responsbility Chain
1
2
3
4
5
6
7
8
9
10
11
12
class BackgroundRPCRouter extends RPCRouter
refreshData: (isForceUpdate, dataLoadedCallback) ->
# reload data logic here
dataLoadedCallback(data)
updateBrowerAction: (icon, hintText) ->
# update browser action logic here
return false # protection: avoid channel leak

HINT:
Using messaging API in chrome here should be careful. Coffee script will return the last executed statement result as function result, which could be potentially truthy, such as non-zero number, object. The truthy return value will make the channel become a async channel, which won’t be closed until the sendResponse callback is invoked. But just as the handler updateBrowserAction, the handler doesn’t need a sendResponse callback, the issue will keep the channel alive forever. So do add false or return false at the end of the method unless you can ensure the function will never yield truthy value in last statement.

App crash issue when inflating ViewPager

I wrote a very simple layout file with a ViewPager, but the app crashes when inflating it.

Layout file
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

Here is the error message:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
04-22 22:27:00.828 1128-1479/com.github.timnew.rubiktimer E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.github.timnew.rubiktimer, PID: 1128
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.github.timnew.rubiktimer/com.github.timnew.rubiktimer.history.HistoryActivity_}: android.view.InflateException: Binary XML file line #7: Error inflating class ViewPager
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2282)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2340)
at android.app.ActivityThread.access$800(ActivityThread.java:157)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1247)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
at dalvik.system.NativeStart.main(Native Method)
Caused by: android.view.InflateException: Binary XML file line #7: Error inflating class ViewPager
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:713)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:761)
at android.view.LayoutInflater.inflate(LayoutInflater.java:498)
at android.view.LayoutInflater.inflate(LayoutInflater.java:398)
at android.view.LayoutInflater.inflate(LayoutInflater.java:354)
at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:340)
at com.actionbarsherlock.internal.ActionBarSherlockNative.setContentView(ActionBarSherlockNative.java:134)
at com.actionbarsherlock.app.SherlockFragmentActivity.setContentView(SherlockFragmentActivity.java:262)
at com.github.timnew.rubiktimer.history.HistoryActivity_.setContentView(HistoryActivity_.java from OutputFileObject:44)
at com.github.timnew.rubiktimer.history.HistoryActivity_.onCreate(HistoryActivity_.java from OutputFileObject:34)
at android.app.Activity.performCreate(Activity.java:5389)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2246)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2340)
            at android.app.ActivityThread.access$800(ActivityThread.java:157)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1247)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:157)
            at android.app.ActivityThread.main(ActivityThread.java:5293)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
            at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassNotFoundException: Didn't find class "android.view.ViewPager" on path: DexPathList[[zip file "/data/app/com.github.timnew.rubiktimer-76.apk"],nativeLibraryDirectories=[/data/app-lib/com.github.timnew.rubiktimer-76, /vendor/lib, /system/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:67)
at java.lang.ClassLoader.loadClass(ClassLoader.java:497)
at java.lang.ClassLoader.loadClass(ClassLoader.java:457)
at android.view.LayoutInflater.createView(LayoutInflater.java:565)
at android.view.LayoutInflater.onCreateView(LayoutInflater.java:658)
at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:66)
at android.view.LayoutInflater.onCreateView(LayoutInflater.java:675)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:700)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:761)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:498)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:398)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:354)
            at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:340)
            at com.actionbarsherlock.internal.ActionBarSherlockNative.setContentView(ActionBarSherlockNative.java:134)
            at com.actionbarsherlock.app.SherlockFragmentActivity.setContentView(SherlockFragmentActivity.java:262)
            at com.github.timnew.rubiktimer.history.HistoryActivity_.setContentView(HistoryActivity_.java from OutputFileObject:44)
            at com.github.timnew.rubiktimer.history.HistoryActivity_.onCreate(HistoryActivity_.java from OutputFileObject:34)
            at android.app.Activity.performCreate(Activity.java:5389)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2246)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2340)
            at android.app.ActivityThread.access$800(ActivityThread.java:157)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1247)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:157)
            at android.app.ActivityThread.main(ActivityThread.java:5293)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
            at dalvik.system.NativeStart.main(Native Method)

According to the message, we can figure out that the issue is caused because Android cannot found the class android.view.ViewPager.

If we’re careful enough, we can figure out that there is something wrong with the ViewPager fullname. Since ViewPager isn’t a standard widget, so it is provided in Support Library instead in the core SDK. So it is not in the package of android.view, it is in the pacakge android.support.v4.view.

To fix the issue, we should not reference the ViewPager with plain name but with the full name android.support.v4.view.ViewPager.

So this is the updated layout xml:

Fixed Lyaout
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

Start Acitivity in background on Android 4.4 KitKat

For some reason, Android 4.4 KitKat has changed the implementation while starting activity.

In Android 4.3, Activity started from inactive Activity is also inactive.
But in Android 4.4, Activity started form inactive Activity will be brought to front.
Here, Inactive Activity means the activity paused by user pressing Home button or switch to another app.

This change won’t be felt in most sceanrios. But for activities that started from AsyncTask, it does have some significant impact on user experience.

I’m working on a app, that shows a Splash Screen when user logged in. And the Splash screen is implemented with activity.

It takes some time for app to communicate with backend server during the loggin in, and user might press “Home” button to temporarily leave the app during the time.

I don’t want the my splash screen to interrupt the user if the user returned to the home screen. So I wish to start the splash screen activity in the background if current acitivity has been brought to background.

It isn’t an issue on Android 4.3, but on Android 4.4 KitKat, it causes problem.
I googled this issue and tried the FLAG_ACTIVITY_MULTIPLE_TASK, and comfirmed that it is not helping to this issue.

So I have to come up some kind of “hacking” solution as described below:

I add a isPaused property to BaseAcitity, which is the base class of all activities in my app.

Add isPaused to BaseActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BaseActivity extends FragmentActivity {
public static final String START_FROM_PAUSED_ACTIVITY_FLAG = "START_FROM_PAUSED_ACTIVITY_FLAG";
protected boolean paused = false;
@Override
protected void onPause() {
super.onPause();
paused = true;
}
@Override
protected void onResume() {
super.onResume();
paused = false;
}
public boolean isPaused() {
return paused;
}
}

Then when I start the start the SplashActivity, I’ll put the isPaused value into the intent.

Add flag when start activity
1
2
3
4
5
6
7
8
Intent intent = new Intent(activity, SplashActivity.class);
boolean isStartingFromBackgroundActivity = activity.isPaused();
intent.putExtra(BaseActivity.START_FROM_PAUSED_ACTIVITY_FLAG, isStartingFromBackgroundActivity);
startActivity(intent);

And check isPaused value in onCreate callback in SplashAcitivity, and push the SplashAcitivty to background if the value is true.

Check flag when activity is created
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (isStartedFromBackgroundActivity())
moveTaskToBack(true);
}
private boolean isStartedFromBackgroundActivity() {
return getIntent().getBooleanExtra(START_FROM_PAUSED_ACTIVITY_FLAG, false);
}

NPMethod called on non-NPObject wrapped JSObject

I’m working on a “hybrid” android appilcation. In the app, part of the UI was written in HTML and hosted in a WebView. And I exposed several Java objects to JavaScript as Java Script Interface.

Setup WebView
1
2
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(irBlaster, "irBlaster");

The irBlaster object contains several methods, and js will invoke the specific method according to the data-attribute bound to the element.

Script that handles the button click in HTML
1
2
3
4
5
6
7
8
9
10
onIrButtonClicked: (e) =>
$button = $(e.currentTarget)
type = $button.data('irType')
sendFunc = irBlaster[type]
code = @parseCode($button.data('irCode'))
length = $button.data('irLength')
sendFunc(length, code)

The previous coffee-script works fine in my Jasmine tests with javascript mock version of irBlaster. When the button clicked, the proper method was invoked with proper arguments.
But when I run this code with real android app, WebView yields error says “NPMethod called on non-NPObject wrapped JSObject”.

The error message looks quite hard to understand the meaning, so I spent quite time to diagnose the code.

After several try, I found the following code works fine, but original one doesn’t:

Code works
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
onIrButtonClicked: (e) =>
$button = $(e.currentTarget)
type = $button.data('irType')
switch irBlaster[type]
when 'NEC'
code = @parseCode($button.data('irCode'))
length = $button.data('irLength')
irBlaster.NEC(length, code)
.
.
.

So I realize the issue was occurd in the javascript contextual binding.

Javascript is function-first-citizen language, and method call on object was simulate by invoking the function with specific context, and the code behavior won’t change if there is no reference to this pointer in the method. So it is quite normal that fetch a method from a object than invoke it without context.

That is why the Jasmine tests passed successfully. But the irBlaster isn’t real or simple javascript, but a native java object provided by js binding interface, so there is a limitation that the method on it cannot be invoked without context. Or it causes error.

So the issue can be resolved as following code by invoking the method in a “reflection” flavor:

Invoking native binding object with context provided in HTML
1
2
3
4
5
6
7
8
9
10
onIrButtonClicked: (e) =>
$button = $(e.currentTarget)
type = $button.data('irType')
sendFunc = irBlaster[type]
code = @parseCode($button.data('irCode'))
length = $button.data('irLength')
sendFunc.call(irBlaster, length, code)

In previous code, I invoke the sendFunc with call, and provids irBlaster as the context as this. So the problem solved, the code runs smoothly without issue.

Dynamically inflates UI in Android App

There is a fascinating idea that inflates UI according to an android layout xml downloaded from server. According to the Android API, it looks quite feasible.

One of LayoutInflate.inflate method overloads accept Layout Xml as XmlPullParser.

And XmlPullParser can wrap around an input stream, so as consequence, following code seems to be working:

Inflate view on the fly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DynamicView extends FrameLayout {
public DynamicView(Context context, InputStream layoutData) throws XmlPullParserException {
super(context);
createView(context, layoutData);
}
private void createView(Context context, InputStream layoutData) throws XmlPullParserException {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser parser = factory.newPullParser();
parser.setInput(layoutData, "UTF-8");
inflater.inflate(parser, this, true);
}
}

The code looks great, compiling looks fine, but when the code excuted, an exception is thrown by the inflater.

According to the LayoutInflater document, this approach won’t work(at least for now, it won’t).

For performance reasons, view inflation relies heavily on pre-processing of XML files that is done at build time. Therefore, it is not currently possible to use LayoutInflater with an XmlPullParser over a plain XML file at runtime.

Actually, Android compiler compiles the layout xml files into binary xml block, which has convert attributes into some special format. And in Android SDK, the LayoutInflater uses XmlResourceParser instead of plain XmlPullParser, which is created by XmlBlock.

XmlBlock is an internal class used in Resources to cache the binary xml document.

And there is no way to create XmlResourceParser or other classes to inject custom behavior into this process. Personally assume that it is pretty much related to the Android Resource and Theming mechanism, there are quite a number cross references between resources. To make it works in a efficient way, Android Runtime did a lot work, such as cache and pre-processing. To override this behavior require quite a number of work and need to aware of potential performance issue. Since the inflating could happen quite often during navigation.

As a not that fansy alternative, UI based on HTML hosted in WebView could be considered.

Asset path in Android Stuidio

According to Android guideline document, the assets folder is created automatically and is positioned under the root of the project folder.

So the assets folder should be located at <project root>/assets

But according to my experience, I found it is not true, or is partially true.

The path to assets folder varies according to the build system being used. So it could be very confusing and sometimes make things too complicated.

ADT

For the very traditional ADT build system, then the assets folder is located at <project root>/assets

Gradle

For new Gradle build system, it changed the project structure definition, which is slightly different to the ADT one.

Gradle build system requires the assets folder is part of the “source code” so the asset folder should located at <project root>/src/main/assets/.

For more detailed information, check out this document.

Android Studio

In Android Studio (also apply to IntelliJ IDEA), things get a little bit more complicated. The assets path could be configured in project.

Android Studio store this path in project file (*.iml), which is an xml file. In the project file, under the XPath /module/component@name="FacetManager"/facet@type="android"/configuration, there could be a <option> node with name ASSETS_FOLDER_RELATIVE_PATH to descript the path.

1
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/assets" />

If the option element with specific name doesn’t exist, please manully create it.

Conclusion

  • Using Eclipse + ADT, place the assets at <project root>/assets

  • Using Android Studio (or IntelliJ) with Ant or Maven, place the assets at <project root>/assets. And set ASSETS_FOLDER_RELATIVE_PATH to /assets

  • Using Android Studio with Gradle, place the assets at <project root>/src/main/assets/. And set ASSETS_FOLDER_RELATIVE_PATH to /src/main/assets. And invoke mergeAssets during build.

Reuse Android built-in Bluetooth Device Picker

Most Android devices support Bluetooth, and most Android ROMs has built-in bluetooth device picker, which is available to other system apps to select a bluetooth device. Theoretically the Bluetooth Device Picker could be reuse in any apps. But for some reasons, the API is not documented, and not published to everyone.

But it is possible to reuse such resources, so I wrote the following code. But due to the using of undocumented API, so it is not garenteed to work on all android devices.

Code is available as Gist

In the code I uses the Android Annotations, but it should be easy to remove the dependency on Android Annotations by adding a constructor to BluetoothDeviceManager that accepts Context.

Arduino IDE 1.5.3 is too buggy to use and work arounds

The Arduino IDE 1.5.3 introduced some new features, such as support latest board Yun, or introduces new libraries and samples. But I found it is too buggy to use.

1. Compile code against Arduino Nano fails due to parameter mcu passed to avrdude is missing.

Reason:
The issue seems caused because the new IDE merged the menu items for Nano board. But for some reason, the configuration haven’t been updated accordingly.

Workaround:
Choose Arduino Duemilanove and Diecimila instead of Arduino Nano. Nano uses same chip as Duemilanove(ATmega328) and Diecimila(ATmega168), but uses a different PCB design. So the binary should be compatible.

2. Compile String(100, DEC) throws ambiguous matching error

Reason:
The issue is caused because the API signature updated to String(*, unsigned char), but constants are still declared as int.

Workaround:
Add force cast DEC, HEX, BIN to byte instead of int.

Pitfall in isEnum method in Java

I found a very interesting phenomenon in Java type reflection when building an Android app.
I’m trying to build a common mechanism to serialize Enum into integer when writing it into database. To make it more flexible so I fetch the value type dynamically by using reflection. So I have the following code to check whether the value to be written is an enumeration:

Code to check enumeration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CONVERTERS.add(new ValueConverter() {
@Override
public boolean match(Object value) {
Class type = value.getType();
return type.isEnum(); // Doesn't work
}
@Override
public String convert(Object value) {
return String.valueOf(((Enum) value).ordinal());
}
});

I built the converters into a responsibility chain. The converter is applied only when match method returns true.

In the converter, I check the type with isEnum method. I expects the method yields true when the value is a enumeration. But later I found it doesn’t work as expected. And the behavior of this method is really confusing!

Here is how it works:

How isEnum works
1
2
3
4
5
6
7
8
9
10
11
public enum ServiceStatus {
NOT_COVERED,
PARTIAL,
FULL
}
assertThat(ServiceStatus.class.isEnum()).isTrue();
assertThat(ServiceStatus.FULL.class.isEnum()).isFalse();
assertThat(ServiceStatus.FULL.getType().isEnum()).isFalse();

Due to the implementation of Java Enumeration, the definition of enumeration value could be understood as the following code:

Java Enumeration psudo-code
1
2
3
4
5
6
class ServiceStatus$2 extends ServiceStatus {
}
public static final ServiceStatus FULL = new ServiceStatus$2();

So the value FULL has a different type than ServiceStatus as I expected, the type of FULL is actually a subclass of ServiceStatus. And the enumeration value FULL is a singleton instance of the anonymous sub-class.

The the most unexpected behavior is that the isEnum method only returns true on Enumeration class itself, not its subclass!

To resolve this issue gracefully, I changed my implementation a little bit. Here is the updated implementation:

Updated implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CONVERTERS.add(new ValueConverter() {
@Override
public boolean match(Object value) {
Class type = value.getType();
return Enum.class.isAssignableFrom(type);
}
@Override
public String convert(Object value) {
return String.valueOf(((Enum) value).ordinal());
}
});

I uses isAssignableFrom to check whether value is the subclass of Enum or could be casted into Enum. I found this approach solved the issue gracefully.