I found out it's just because I didn't set the receiver's category correctly.
Since I was putting all my source code in a library project and referencing it with each individual application, I need to rename 3 different places in the manifest.
1)
<permission
android:name="digital.dispatch.mobilebooker.base.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
to
<permission
android:name="digital.dispatch.mobilebooker.(my app name).permission.C2D_MESSAGE"
android:protectionLevel="signature" />
2)
<uses-permission
android:name="digital.dispatch.mobilebooker.base.permission.C2D_MESSAGE" />
to
3)
<category android:name="digital.dispatch.mobilebooker.base" />
to
<category android:name="digital.dispatch.mobilebooker.(my app name)" />
The reason why it worked in 4.0 + device is because I only did the first two but not 3).
Since 4.0+ device doesn't care about receiver's category, it worked.
However, when I am testing on 2.3.3 device, the category actually matters and causing it not receiving any response.
Hope this will help any other that encounter the same situation.