Android Arduino Handbag by follower
Make Arduino accessories for Android without writing Android code with the HandBag App for Android.
Handbag: the Android Arduino Accessorizer
See HandbagDevices.com for up to date details.

If you can write a sketch for your Arduino, you can accessorize your Android with Handbag!
-
Do you want to make Arduino-based accessories for your Android phone or tablet like the Android Open Accessory Development Kit?
-
Don't know how to write Java code for Android?
-
Don't want to install Eclipse and the Android SDK?
Use the Handbag™ for Android app and those Android coding days are gone...
With Handbag you can:
-
Create an Android User Interface and display it on a phone or tablet.
-
Display buttons on the Android device and respond to button presses from the Arduino.
-
Enable a user to provide information to your accessory by displaying text entry fields on the Android device and receive the entered text on your Arduino. (For example to provide configuration information.)
-
Display labels on the Android device screen of different sizes and update their content to provide feedback to the user.
Example code
This sample code extract would display the text "Hello, World!" on screen and also a button that would call the buttonCallback function on the Arduino when it is pressed:
Handbag.addLabel("Hello, World!", 20);
Handbag.addButton("LED on/off", buttonCallback);
The button callback could toggle an LED on and off, for example.
Installation
See HandbagDevices.com for the current, easier, installation guide.
[These are the 003 and below installation instructions.]
-
Install the Android Handbag App binary linked below onto your phone/device.
-
Install the Arduino Handbag library linked below into the library folder for the IDE.
-
Install the
AndroidAccessorylibrary from http://developer.android.com/guide/topics/usb/adk.html#installing. -
Install the USB Host library release linked from http://www.circuitsathome.com/mcu/programming/android_adk_compatible_usb_host_library_release
-
Try out the
SimpleHandbagArduino example included with the library. (Ignore the reference to Handbag001 in the example's comments--the correct binary is linked below in the 002 directory.) -
Try out the other examples.
Releases
-
29 June 2012 - 004 - "In my thoughts"
- Handbag Arduino library source (Backward compatible with 003 application release.)
-
7 March 2012 - 003 - "Been too long"
-
Android Handbag App binary (Backward compatible with 002 release and libraries)
-
-
13 June 2011 - 002 - "See you today"
-
Library & Binary: Arduino library source + Android Handbag App binary
-
Source repository
See also
-
http://www.labradoc.com/i/follower/p/android-arduino-accessory
-
Featured on the Make Blog.
-
Older, probably no longer relevant details: Handbag: the Android Arduino Accessorizer
-
linux.conf.au 2012 talk video: Android Accessories Made Easy With Arduino - Philip Lindsay
Status
- In progress.
20 May 2011
5 June 2011¶
Troubleshooting #1
If you encounter the error message
[INSTALL_FAILED_OLDER_SDK]it means the version of the OS on your tablet, phone or other device does not match the version required by the Handbag application.The Android Open Accessory protocol currently requires version 2.3.4 or above of Android, or (I think) version 3.1 or above.
Very few devices currently support this version--at least according to Comparison of Android devices which doesn't correctly list 2.3.4 for the Nexus One but at least shows 2.3.3.
.
Did test callback code.
Function pointer for a callback, for reference (via):
returntype (*variablename)(arg1, arg2, ...)e.g.
void (*somefoo)(); void foo(void){ } void acceptfoo(void (*thefoo)()) { somefoo = thefoo; } acceptfoo(foo); void callfoo() { somefoo(); }Make it easier to use with:
#define FOO_CALLBACK(varname) void (*varname)()then:
FOO_CALLBACK(somefoo); void acceptfoo(FOO_CALLBACK(thefoo)) { // .... }And for an array of callbacks:
FOO_CALLBACK(callbacks[10]); // .... callbacks[0](); // To call the callback.
7 June 2011¶
Worked on separating Arduino code into a library and examples. Improved the API considerably (I think :) ).
Adding UI widgets now looks more like this:
void toggleFirstLed() { // ... } Handbag.addLabel(""); Handbag.addButton("Toggle Digital Pin 4", toggleFirstLed);This required no changes to the Android App.
For future reference, to export an archive from the Mercurial repository I used:
hg archive -t zip -I README.txt -I "library/*" handbag-arduino-library-20110607a.zipAnd to check the result I used:
unzip -l handbag-arduino-library-20110607a.zipUploaded first release of the Handbag library for Arduino-based Android accessories to:
It is still compatible with the Handbag001 release of the Handbag App for Android.
Working on adding the ability to style the text size & alignment of labels.
Using the Android "gravity" values as alignment values. See here for valid values:
Completed label text size & alignment work in development version. Not yet released.
Working on ability to modify the content and style of labels in development version. Have both a counter and ADC value example working.
Learned a couple of things along the way:
To create an alert dialog box (in the code for an Activity):
import android.app.AlertDialog; AlertDialog alertDialog = new AlertDialog.Builder(this).create(); alertDialog.setMessage("labelText: " + labelText); alertDialog.show();(See: How to Show Alert Dialog in Android, How to display an AlertDialog in your Android application and AlertDialog.Builder)
[Update: Actually, this is quicker:
new AlertDialog.Builder(this).setMessage(labelText).show();]
If you want better debugging tools than a dialog box (cough) (Hey, I don't have passthrough working for ADB...) then you can read a log with "Force Close" and other messages:
- Start the
ddmstool and look at the "Log" section in the bottom of the window when it's connected to your device.
(Semi-via: Is there a log for Force Close?)
- Start the
11 June 2011¶
Finished implementing examples in development version:
-
SimpleHandbag
- Demonstrates the general requirements of a Handbag compatible sketch.
-
ToggleHandbag
- Matches the original demo released for Handbag and toggles the lighting of two LEDs connected to the board.
-
MultimeterHandbag
- Demonstrates use of the
setText()method to update the value of a label. It is a "multimeter" that only measures voltages from 0 to 5.0V DC. It also has an incrementing counter.
- Demonstrates use of the
-
TextInputHandbag
- Demonstrates entering text on Android device and sending it to the Arduino.
-
LcdHandbag
- Demonstrates entering text on an Android device and sending it to the Arduino, then storing it and displaying it on an LCD. This approach shows how the Android can be used to supply configuration information to a sketch. (I'd really like to see this done with a scrolling LED matrix or a Persistence of Vision kit like MiniPOV and use it to change the message text.) Also demonstrated is performing a time-based task on the Arduino whether or not an Android device is connected.
-
12 June 2011¶
Implemented the version-based protocol handshake to handle when either the App or library have been compiled with incompatible versions.
Created an empty repository at GitHub (sorry BitBucket) in preparation for a code push. Available at: https://github.com/follower/android-arduino-handbag
Pushed the local Mercurial repository to GitHub with (via):
hg bookmark -r default master hg push git+ssh://git@github.com/follower/android-arduino-handbag.gitNote: The hg-git instructions said to do
hg pushat the end of the above sequence. I'm not sure what it was supposed to do but because I'd based the repo on the local TinyAccessory repo it pushed all the HandBag changes back to the TinyAccessory repo which isn't what I wanted to do. Argh!To "fix"? this I changed into the TinyAccessory repo and did the following (after making a backup of everything first):
hg rollbackApparently in the target repository (presumably requiring it to be a local repo) a
rollbackwill undo the push transaction (see). (Also, that man page seems to say that there's a--dry-runoption which the local docs didn't seem to mention.)Hopefully I haven't broken anything in the process. I'm planning to pull from the github repo anyway so hopefully it's a moot point.
Created new github.com directory and then cloned the remote repository like this (via "Usage" section):
hg clone git+ssh://git@github.com/follower/android-arduino-handbag.gitThis allows ssh push access as well.
Changed the symbolic link for the Arduino library directory to point to the new location:
cd <path to sketchbook>/libraries ln -s <path to repo>/github.com/android-arduino-handbag/library/HandbagOpened the app project from GitHub in Eclipse, took some repeated "Build All" calls to get it to generate/recognise the generated resource files.
Well, the good thing about annoying things with Eclipse & Android development is that it reinforces that something like Handbag is needed. Remember: I do Android programming so you don't have to. :)
For future reference:
-
(Re-)setting up some of the development environment (this is required because the some of the settings in Eclipse are per-Workspace not per-project--why, I don't know): http://developer.android.com/sdk/eclipse-adt.html
-
See Import existing Android project in Eclipse: no gen source folder? for fixing the error messages like (I seemingly needed to also restart to remove the first one):
-
Android requires compiler compliance level 5.0. Please fix project properties -
Project ... is missing required source folder: 'gen'
-
-
Ran into a bunch of issues around device orientation, the activity detail in the manifest file needed to be modified. For possible settings including
screenOrientationsee: http://developer.android.com/guide/topics/manifest/activity-element.html
21 June 2011¶
Hmm, thought I'd mentioned this already, but: registered HandbagDevices.com the other day for future use (currently links back to here). Also signed up for @HandbagDevices.
Looked into implementing background Android services/tasks--from looking into the manifest setup it seems like it should be possible to trigger a service on device connection.
22 June 2011¶
After attempting to get a service launched on USB accessory connect, without success, I noticed "If you want your application to be notified of an attached USB accessory, specify an
<intent-filter>and<meta-data>element pair for theandroid.hardware.usb.action.USB_ACCESSORY_ATTACHEDintent in your main activity" (see) which suggests a service can't receive this intent.Changed to use an activity instead. Got this to start successfully on accessory connection but then had the issue of it displaying the activity launch "animation" and then a flash of black screen when all I did was exit (by calling
finish()in the activity'sonCreatemethod.In order to avoid the black screen flash at start of an activity with no user interface (UI) (i.e. sort of a "background activity") I discovered two approaches:
The first approach I found is to put this in an
activityorapplicationtag in the manifest file:android:theme="@android:style/Theme.Translucent.NoTitleBar"The second variation (and presumably more correct) is to use:
android:theme="@android:style/Theme.NoDisplay"I don't know why it was so difficult to find how to do this: "I want to start a service but my intent filter only works with an activity how do I start the service without displaying a UI?"
If you wanted to display some sort of overlay the first option might be preferable otherwise with the second option you could show a notification instead (probably from the service though).
This is the way to create a "toast" (a "pop-up" message) and display it immediately:
Toast.makeText(this, "your message", Toast.LENGTH_LONG).show();It worked for me when
thisis anActivitysubclass and the call was in theonCreate()method (I don't know if it will work in other situations or not). Don't forget theshow()call at the end or it won't work. :)Encountered an error when trying to instantiate the service with:
Intent intent = new Intent(this, MyService.class); startService(intent);The full traceback of the exception gets cut off in the log (and I couldn't work out how to get it to display the longer complete traceback) but part of the error is:
ERROR/AndroidRuntime(18355): Caused by: java.lang.InstantiationException: com.rancidbacon.TestAndroidService.MyServiceIt turns out this error is due to the fact I allowed Eclipse to create a constructor that accepts an argument and apparently for a service to be successfully instantiated requires a no-argument constructor like (see):
public MyService() { super("MyService"); }(Now that I know the error, I do recollect the no argument constructor being mentioned as a requirement here.)
Attempted to get a toast displayed from an
IntentServiceonHandleIntentmethod but got an error including:Handler <foo> sending message to a Handler on a dead threadApparently the issue is due to the normal "not the UI thread" and can be "solved" with this.
Probably a notification is more suited in this case anyway, so will try that.
Couldn't get Eclipse to recognise
Notification.Builderfrom here until I noticed the very subtle "Since: API Level 11" which means it won't work on a 2.3.4 device. So, have to go back to using the more convoluted method shown here.A notification is required to have an icon which is annoying. I found a list of default Android system icons found in
android.R.drawable.*and ended up usingic_menu_info_detailswhich is a light grey info icon in a circle icon.The page above links to further lists but the links are broken. Some icons are (currently) displayed: here & here. There is also a list of all the available drawables icon resource IDs.
There is also code for Listing Androids drawable Resources and an online application that lists Android R Drawables.
Apparently it is "inadvisable" to use the icons via the resource id because they may change in appearance between OS releases. The suggestion is to copy the icons into your app directly. (see)
With API Level 10 if you just create an notification with:
Notification notification = new Notification(icon, text, when);And then call the
NotificationManagernotify()method you'll get a runtime exception along the lines of:IllegalArgumentException ... contentView requiredYou have to actually use the
setLatestEventInfo()method to supply the required items.The behaviour that I wanted (initially, anyway) was for the notification to be dismissed when you clicked on it but there didn't appear to be a way to specify a null notification intent.
To get something working, I just supplied the activity that started the service as the intent--this has the side effect of repeatedly send the notification but I could at least tell it was working:
Intent notificationIntent = new Intent(this, MyServiceActivity.class);(This comment suggests sending an empty
Intentwith aNotification.FLAG_AUTO_CANCELflag but that didn't seem to work for me. (See immediately below for update.))Update: The
Notification.FLAG_AUTO_CANCEL&Notification.FLAG_ONGOING_EVENTflags are a lot more effective when you set them on theflagsfield of the notification object rather than theflagsparameter of thegetActivity()method of thePendingIntent. D'oh. :) e.g.:notification.flags = Notification.FLAG_ONGOING_EVENT;So, in fact this does work for an auto-clearing notification with no associated intent:
... notification.flags = Notification.FLAG_AUTO_CANCEL; ... Intent notificationIntent = new Intent(); ...Finally got the "show notification on connect, run as service, remove notification on disconnect" process working!
A few different issues intersected to cause problems but the primary one was a bit of a brain-o on my part: I was exiting the
onHandleIntentmethod immediately which meant the broadcast receiver never had a chance to receive the usb disconnect message.This manifested itself as errors along the lines of:
Service <foo> has leaked IntentReceiver <bar> that was originally registered here. Are you missing a call to unregisterReceiver()?What I should have done (and, now have) was have a sleep loop (maybe should be something else--but in reality would be handling USB traffic) in the
onHandleIntentmethod and wait for a flag set by the broadcast receiver indicating that usb accessory has been detached. When the flag is set then exit as normal.Also, the code now uses the
startForeground()/stopForeground()calls in the service which is also connected to the notification stuff.Well, that took a while...
23 June 2011¶
Moved Android background service USB accessory work/code/log to: http://www.labradoc.com/i/follower/p/android-background-service-usb-accessory
8 February 2012¶
So, it's been a while...
Been doing a lot of presentations about Handbag but now it's time to get back to some coding...
Thanks to someone at HTC I now have an HTC Jetstream tablet on which to develop & test Handbag.
This will enable me to test Handbag with the tablet form-factor and with Android OS 3.1 (and HTC Sense).
First test result: it doesn't work. :)
The failure to run was a little surprising as I'd had success running on an HTC Flyer tablet out-of-the-box but it turns out the Flyer was actually running on 2.3 not 3.x.
It was about then that I recalled removing all the 3.x support from the DemoKit app when I was hacking it to a simpler form. :)
It also explains why at my recent linux.conf.au presentation an Ice-Cream Sandwich based device failed to work with Handbag.
Check the Choosing the Right USB Accessory APIs section of the documentation for more details.
Enabled
Settings > Applications > Development > USB Debuggingon the tablet.Initially I couldn't build the project in Eclipse due to the error:
The project cannot be built until build path errors are resolvedI found an issue which included a comment that suggested it could be worked around by:
- Ensure the Java compiler is in the bottom of the builders list located in PROJECT -> Properties -> Builders.
Changing this seemed to fix things so the project could be built.
Uploading to the tablet results in:
02-08 14:45:44.244: ERROR/AndroidRuntime(3303): FATAL EXCEPTION: main 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): java.lang.NoClassDefFoundError: com.android.future.usb.UsbManager 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at com.rancidbacon.Handbag.HandbagActivity.onCreate(HandbagActivity.java:147) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at com.rancidbacon.Handbag.BaseActivity.onCreate(BaseActivity.java:38) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1072) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1908) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1965) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at android.app.ActivityThread.access$1500(ActivityThread.java:134) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1110) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at android.os.Handler.dispatchMessage(Handler.java:99) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at android.os.Looper.loop(Looper.java:152) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at android.app.ActivityThread.main(ActivityThread.java:4606) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at java.lang.reflect.Method.invokeNative(Native Method) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at java.lang.reflect.Method.invoke(Method.java:491) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599) 02-08 14:45:44.244: ERROR/AndroidRuntime(3303): at dalvik.system.NativeStart.main(Native Method)I thought that the DemoKit code showed how to use exceptions to automatically use the right classes etc in 2.3.4 & 3.1 but I can't see it in the the code nor anywhere else. Odd.
So, the easiest way to find it is to re-invent the wheel & then I'll find it. :D
Also, the docs say in reference to the
futurenamespace "Android 3.1 also supports importing and calling the classes within this namespace to support applications written with the add-on library" but the error I'm getting suggests that's not the case here.Updated the SDK & Platform tools in the process...
For some reason I couldn't get the
Android SDK and AVD Managerplugin running from Eclipse to let me download the 3.1 SDK so I used the command lineandroidtool instead.Hey, I've got nothing better to do with my day, let's update Eclipse too so things will actually run!
Ummm, now, where was I...?
Hmmm, I've just realised from looking at the original Android ADK presentation it actually says:
Link against com.android.future.usb.accessory.jar
And, if you right-click on the Handbag project, there's an option
Android Tools > Add compatibility library...how convenient!But of course selecting it results in:
Android Compatibility JAR not found:] Eclipse.app/Contents/MacOS/v4/android-support-v4.jarOh, look, it's a known issue.
Hmmm, although it seems like the USB stuff isn't actually in the compatibility library...
So, I added the
add-ons/addon_google_apis_google_inc_10/libs/usb.jarviaBuild Path > Add External Archives...which ended up with:Caused by: java.lang.RuntimeException: stub at com.android.future.usb.UsbManager.getInstance(Unknown Source)And changing it for the file in the version 12 directory didn't correct the error either.
Changing the Project Build Target to 3.1/12 didn't make anything work either (went back to
NoClassDefFoundError.)Well, that was educational.
On a whim I decided to test the project in a 3.1 virtual device and it worked straight away.
Looking in
/system/frameworkI see that there is acom.android.future.usb.accessory.jarfile there in the emulator. So, this makes me think that the fact that it's missing on the HTC is the issue--despite the fact that the documentation makes it sound like it's always included in 3.1.(Although oddly enough the file
/etc/permissions/android.hardware.usb.accessory.xmlon the Jetstream does include:<permissions> <feature name="android.hardware.usb.accessory" /> <library name="com.android.future.usb.accessory" file="/system/framework/com.android.future.usb.accessory.jar" /> </permissions>)
Argh...
Okay, so, much reading of supporting multiple versions etc etc but I can't find an instance of referring to a static (?) class/object in a package--the fully qualified name of which differs which is the case here.
[Retro-Update:
Links related to implementing backward compatibility:
-
http://android-developers.blogspot.com/2009/04/backward-compatibility-for-android.html
-
http://www.google.com/events/io/2010/sessions/casting-wide-net-android-devices.html (Slides & video)
-
http://developer.android.com/resources/articles/backward-compatibility.html
-
http://www.higherpass.com/Android/Tutorials/Working-With-Android-Contacts/4/ (For its use of
Class<? extends ContactAPI> realClass = Class.forName(apiClass).asSubclass(ContactAPI.class);which I don't think we can use in this case.) -
http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html
-
http://devtcg.blogspot.com/2009/12/gracefully-supporting-multiple-android.html ("The Eclipse target platform is the specific version that Eclipse will be compiling against, this is what permits us to compile code that actually does link specifically against the newer platform features.")
-
http://stackoverflow.com/questions/6513252/how-to-target-multiple-api-levels
And here are some general Java reflection-related links:
]
So, I'm going to try just chucking the
com.android.future.usb.accessory.jarfile on the Jetstream and see what happens...-
I pulled the
com.android.future.usb.accessory.jarfile off the emulator with:./platform-tools/adb pull /system/framework/com.android.future.usb.accessory.jarThen attempted to push it to the HTC Jetstream with:
./platform-tools/adb push com.android.future.usb.accessory.jar /system/framework/But of course then got the error:
failed to copy '/Users/phil/Code/android/fix_htc_jetstream_usb/com.android.future.usb.accessory.jar' to '/system/framework//com.android.future.usb.accessory.jar': Read-only file systemAnd of course this:
./platform-tools/adb remountfails with:
remount failed: Operation not permittedbecause:
./platform-tools/adb shell getprop ro.securereturns:
1So, the end result being, no, that's not going to work. Either I have to target 3.1 or use a work around (reflection or wrapper classes) to still target 2.3.4.
4 March 2012¶
(Gah, I need to make Labradoc not discard an entry if you've been logged out... :/)
I've managed to get an app compiled that will run on 2.3.4 and 3.1+ (especially on HTC Jetstream that doesn't support
com.android.future.usb.*). I achieved this by pulling the relevant code into two separate classes (one for Gingerbread--usingcom.android.future.usb.*; one for Honeycomb and up--usingandroid.hardware.usb.*) and creating an interface class.There's a bunch of duplicate code as a result so there's probably some better way but I don't know it yet.
I still wonder if I could just ship the
com.android.future.usb.*jarfile--if that was legitimate.It still seems like there should be an easier way to use all the same code but just change the imported package--but if there is I don't know it either. I should probably ask someone who knows Java. :)
I could probably reduce some of the duplication by using reflection/invoking by name but I don't think the increased complication of the code would make it worth it at this stage.
Still, it's cool to have one application file actually work on 3 different devices--yay for compatibility work arounds!
Oh, yeah, so it seems there's some sort of interface for the
UsbManagerclass but it didn't seem to be visible to anything I did in Eclipse--and was more complicated for what I needed anyway. But anyway: IUsbManager.java (2.3.4) & IUsbManager.java (4.0.1)Along the way I encountered other possibilities:
-
Generics
-
Various casting approaches
-
Wrapper class (from here)
5 March 2012¶
Pushed the new code to the
branch-tablet-fixbranch in the repository.
6 March 2012¶
Merged the tablet fix branch & master together.
Exported a
003release of Handbag app (listed as version 0.2--one day I'll make the version/release numbers the same...) and linked it. This version should support 2.3.4+, 3.1+ and 4.0+ for devices with the USB Accessory functionality.Rather than using the
.apkfile generated for debugging I used[Right-click on project] > Android Tools > Export Unsigned Application Package...to create a.apkfile.But when I tried to download and install the resulting
Handbag.apkthe install failed (despite having a big green tick--nice UI there) with the error:<...> has no certificates at entry <...>Apparently the solution is to do this (via--but note that the filename is not specified in that example):
jarsigner -verbose -keystore ~/.android/debug.keystore <application.apk> androiddebugkeyThen, when prompted with:
Enter Passphrase for keystore:Enter the following:
android(This error seems to be related to either Issue: Some APKs get created with partial signatures or JDK 7 - INSTALL_PARSE_FAILED_NO_CERTIFICATES but I didn't investigate any further.)
7 March 2012¶
Updated the Arduino Handbag library to use the new (currently unreleased) version of
AndroidAccessorywhich inherits fromStreamand make use ofreadByteswhich includes timeouts etc.There has been a minor cosmetic issue that results in two instances of the Handbag app appearing in the app history view (long press on home button or equivalent). I've now removed this.
I remembered doing something similar previously and thought it was adding a flag to:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);in the activity that responds to
USB_DEVICE_ATTACHEDevents. But it turns out this wasn't the case.Instead, you need to modify the
AndroidManifest.xmlfile to addandroid:excludeFromRecents="true"to the<activity>stanza for the activity that responds to theUSB_DEVICE_ATTACHEDevents, like so:<activity android:name="com.rancidbacon.Handbag.UsbAccessoryActivity" android:label="Handbag" android:taskAffinity="" android:launchMode="singleInstance" android:screenOrientation="portrait" android:excludeFromRecents="true">
9 March 2012¶
Thinking about re-architecting Handbag to have greater separation between: UI Display/Event handling; message parsing; and data communication (e.g. over network rather than ADK).
Looked at a variety of approaches but it's looking like using a bound service might be the best approach, as it allows two-way communication and optionally IPC. The plan would be to initially have the UI/Event front end to communicate directly with the message parsing code in the same application but eventually the UI/Event activity and message parsing service could be separated in different applications.
(Either the activity or the service would need to start a second service to handle the data transmission.)
Here is an Android SDK example showing how to implement the two sides of the Bound Service approach:
MessengerServiceexample andMessengerServiceActivitiesexample
11 March 2012¶
So, I started hacking on a split architecture version of Handbag...
Eventually I ran into a bunch of what turned out to be not very helpful error messages, including:
Activity com.handbagdevices.handbag.HandbagUI has leaked ServiceConnection...This seemed to be "solved" by always calling
unbindService()whether or not we had bound. (This error seemed to happen when using the back key to exit the app but not when using the home button.)After some time I also discovered that in fact the call to
bindService()always failed: it always returned false. (Google juice: bindService always returns false.)The
bindService()documentation says:false is returned if the connection is not made so you will not receive the service object.
and
This function will throw
SecurityExceptionif you do not have permission to bind to the given service.Which is all very well and good, but nowhere does it say why the attempt to bind/make the connection would fail and thus return false.
I found a few related posts but none of them solved the problem:
Just so I had a "known good" base to work from I tried the
MessengerService(mentioned above) demos in theApiDemossamples that can be downloaded with the SDK.Not surprisingly they worked fine.
It was somewhere about here that I realised what turned out to be the main cause: I hadn't put the service in the manifest file!
For some reason I had thought I had read somewhere that if you were only using a service locally then you didn't need to include it in the manifest. Apparently I was wrong. :)
Still, I would think it would be helpful to include that as a possible reason for
bindService()to return false:If
bindService()returns false, one possible cause is that you haven't included the service in the manifest file.As it is, the Bound Services documentation doesn't mention the manifest file at all other than indirectly by saying:
...you should also refer to the Services document for additional information about services in general...
Of course, the linked Services document does say:
Like activities (and other components), you must declare all services in your application's manifest file.
But, hey, who reads documentation... :-p
[Retro-update: Here's some more documentation to not read: Remote Messenger Service Sample.]
Anyway, once I added the service to the manifest the
bindService()operation completed successfully. So, there's that.Here's what I added to the
<application>section:<service android:name=".HandbagParseService" android:process=":remote" />It doesn't need to be a remote service but I just copied that from the demo so left it in.
However if I include the expected
unbindService()call now it generates the error:Unable to stop activity {com.handbagdevices.handbag/com.handbagdevices.handbag.HandbagUI}: java.lang.IllegalArgumentException: Service not registered: com.handbagdevices.handbag.HandbagUI$1@41337d80So, yeah, that's probably not ideal.
I couldn't find much useful on that error, so at the moment I'm going back to not calling unbind until some error is generated by not doing so.
One comment I noted from a Google engineer was:
It means that ServiceConnection is not bound to the service. Just because you can call methods on the IBinder you got at some previous point doesn't mean you are still bound to it.
Yeah. :/
I don't know if there's a way to check if the connection is still bound.
So, it mostly works now and I can send messages from the activity to the service and vice versa.
But looking at it now it seems things don't resume properly so I'll have to look further into that...
Oh, yeah, but for the record, change command-R (or ctrl-r) to be the key shortcut for Run in Eclipse instead of the bizarre function key combo it is by default. Makes much more sense!
(Actually, I changed my mind and removed the
android:process=":remote"part from the manifest.)Have got things resuming properly now.
I needed to ensure that what I wanted to run on connection in
onServiceConnected()was manually executed in the Activity'sonStart()method so that the service would be "woken up" when the activity did.
23 March 2012¶
Been working on other things and haven't updated this for a while.
A while back I managed to successfully retrieve a response from a web server as a test for socket communications. Still need to write up a bunch of discoveries from that.
Today I've written a trio of hacky Python scripts to generate, parse and serve up a new-style of Handbag data packet.
I figured I'd start with Python because that's easier than Java & C for exploring and testing with... :)
I then successfully got the new app to retrieve a packet from the Python server and display the raw content. (No parsing in the app yet.)
The new packet format can be summarised as:
"abc;123;[5]a;b\nc\n" --> "abc", "123", "a;b\nc"Clear as mud really. :)
Specification:
-
fields are separated by semicolons (i.e. ";")
-
packets consist of fields and end with a newline (i.e. "n")
-
if a field contains or might contain one or more field or packet separator (i.e. ";" or "n") it must be stored as a safe string.
-
a safe string is a field that starts with an opening square bracket (i.e. "["), then the length of the following string as a decimal number in ASCII (e.g. "12" for a string of length twelve), then a closing square bracket (i.e. "]"), then the string itself. The field must still be closed with a field or packet separator.
-
all input from a user or other untrusted/unknown source must be passed as a safe string. Note that this does not sanitise the input for any further use, only for transmission.
-
TODO: Oh, whoops, just realised I need to add "a string with '[' at its beginning" to the list of safe string required fields. :) [Update: Now fixed.]
-
24 March 2012¶
Added "Starts with
[" to the "needs to be safestring-ified" list.Added two widgets to hacky UI to enable setting host name & port for connecting to the test data server.
Enabled the name & port to be stored in persistent app storage so they are preserved across app use. See Saving Persistent State for details on how to use getSharedPreferences to achieve this.
Note, however, the above page neglects to include the filename and mode setting in the example. It needs to be specified like:
SharedPreferences appPrefs = getSharedPreferences("handbag", MODE_PRIVATE);Also, apparently
MODE_MULTI_PROCESSis needed after Gingerbread to ensure cross-process access (within the same app). But it appears not to be an available constant in Gingerbread itself--so I assume this means you need to add the constant to the code if you port to support Honeycomb+ directly.Popping the stack a bit (or, rather the browser tabs):
I tested my Python
SocketServerbased server by running it on my OS X machine and then using telnet to access it--from the same machine. This worked fine usinglocalhostor127.0.0.1as the host name.But when I tried to access it from my Android device I received a "Connection refused" message.
It turned out this was because I was specifying
localhost/127.0.0.1as the interface that the socket should be listening on--not the network-visible IP address.I would think this isn't the first time I've made this mistake. :)
The solution is, of course, to listen on the network-visible ("external" but not really because it's not internet visible) IP address.
Unfortunately apparently finding the IP address is not straight-forward to achieve in a cross-platform manner. This is discussed in Finding local IP addresses using Python's stdlib which lead me to this approach:
socket.gethostbyname(socket.gethostname())Apparently it's not perfect but works for me for now. :) (Also, if you have a machine with multiple IP addresses you should see the above thread for other approaches.)
While testing the server script I also encountered the following error if I killed the server & restarted it:
socket.error: [Errno 48] Address already in useSomewhere on StackOverflow I found the solution is to use:
server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)Where
serveris the object returned bySocketServer.TCPServer(...)or whatever you're using.
26 March 2012¶
For future reference, here's a link about creating activity based tabs: How to: Android Tab Layout.
More retro-documentation stack-popping from pre-23 March:
The first data transfer service I decided to use was something WiFi-based so I needed to learn to use Sockets on Android.
Some relevant links:
As noted in the second link, you need to make sure you add the following permission to the manifest:
<uses-permission android:name="android.permission.INTERNET"/>Unfortunately when trying to connect to the network the following error was triggered:
NetworkOnMainThreadExceptionApparently this mostly occurs on Honeycomb+ because before then network access in the UI thread wasn't considered fatal.
But... you might ask (as I did) if I'm making a network call in a remote process (and a message handler, at that) how can that still be considered the main thread? Good question, I say.
And I don't really know the answer. I'm not sure if the
NetworkOnMainThreadExceptionoccurs because even services aren't supposed to do that or something...Although one of the answers to this "NetworkOnMainThreadException on a service entirely on a different process?" post states "call backs [run] on the UI thread in a Service".
Anyway, the solution is to use something that uses a separate thread.
One suggestion is to start a new thread manually but I decided to use an
AsyncTaskclass instead as described here.Some relevant notes on
AsyncTaskfrom the previous link:-
"
AsyncTaskmust be used by subclassing it. It is also very important to remember that anAsyncTaskinstance has to be created on the UI thread and can be executed only once". -
"You can specify the type, using generics, of the parameters, the progress values and the final value of the task".
-
"
onPreExecute(),onPostExecute()andonProgressUpdate()are all invoked on the UI thread". -
"The value returned by doInBackground() is sent to onPostExecute()".
Generics are a bit weird particularly if you don't want to specify one or more of the parameters. In the end I used Eclipse to generate the signatures I needed:
private class TestSocketTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... params) { return "Foo"; // A string to return } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // The result is returned to this method... } }-
For debugging purposes this answer suggests logging the following (in the main thread and the callback) to ensure they're different:
android.os.Process.myTid()But in my case they were different but I still got the
NetworkOnMainThreadExceptionerror.For possible future use in relation to handling orientation changes and long running (using
AsyncTask) tasks: "Background task, progress dialog, orientation change - is there any 100% working solution?"So, once I had successfully made a socket connection I needed to read the response into a string--simple, right? Lol, you haven't been paying attention, have you?
My test connection was to
google.comon port 80. I sent any old data in order to trigger an error response from the server--but that worked fine for test purposes.The example I was following along with used the
readUTF()method of aDataInputStreaminstance so I thought I'd use the same thing.Unfortunately this tended to produce
EOFExceptionerrors, something like (not the actual error, just copied from somewhere else):... java.io.EOFException at java.io.DataInputStream.readFully(DataInputStream.java:...) at java.io.DataInputStream.readUTF(DataInputStream.java:...) at java.io.DataInputStream.readUTF(DataInputStream.java:...)It turned out that the
readUTF()method should only be used to read a data stream that has been written withwriteUTF()which was not the case with the response from the Google server.The thread Java DataInputStream read operations throwing Exceptions is where I found all this out.
As a side note, the DataInput class reference makes the note:
When encoding strings as UTF, implementations of DataInput and DataOutput use a slightly modified form of UTF-8, hereafter referred to as MUTF-8.
So, it's not really UTF anyway...
Anyway, back to the task at hand...
Somewhere along the way I picked up on the existence of the
StringBuilderclass so started with:dataInStream = new DataInputStream(socket.getInputStream()); // ... StringBuilder wowJavaSucksForStrings = new StringBuilder(); while (dataInStream.available() > 0) { wowJavaSucksForStrings.append((char) dataInStream.read()); }Note: The particular variable name used is optional but I thought it was a suitable addition. :)
That worked but eventually I found a slightly more succinct, albeit more esoteric, solution:
wowJavaSucksForStrings = new java.util.Scanner(dataInStream).useDelimiter("\\A").next();The above post also mentions you should catch
java.util.NoSuchElementExceptionerrors when handling an empty string.I think it was around this time that I attempted to use my now "standard" approach to quick dialog creation with:
new AlertDialog.Builder(this).setMessage(labelText).show();But (this is now from memory and browser tab forensics... :) ) I think this resulted in:
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an applicationFrom memory this was as a result of using this approach from within the
handleMessage()method of aHandlersub-class.It was able to work by changing
thisto first have a reference to the parent activity class (in this caseHandbagUI), like so:new AlertDialog.Builder(HandbagUI.this).setMessage("Message text").show();Here's some possibly related StackOverflow threads:
I'm not sure if that's where I actually got the solution from though.
As always, it's better (when it's easier to remember) to document things in a development log at the time they are encountered but that's not very exciting at the time. :) So, it's a balance between doing it then, doing it later or not documenting them at all.
Hopefully these retro-documented items will be useful to me or someone else in the future. (And not just because it enables me to clear a bunch of tabs. :) )
Nearly caught up...
The next step was to link two parts together: take the retrieved data from the comms service and display it in the UI activity. (In reality the data would go through the parser first but we're skipping that part initally.)
As before we're using a
Messageinstance to communicate with handlers.Conveniently there's a
objfield on the object (no, no, don't read the documentation yet, you'll spoil the surprise...).Unfortunately setting
objto our String instance results in:java.lang.RuntimeException: Can't marshal non-Parcelable objects across processes.Two possible solutions are presented--but because I want to have the services running as separate processes I couldn't use this option:
- "remove [the]
android:processattribute"
so I needed to take this approach instead:
- "package [the] data in a
Bundleand attach it to theMessageviasetData()".
Apparently
Stringsaren'tParcelableso we have to use the Bundle approach instead. The documentation forobjas linked above actually mentions this:When using
Messengerto send the message across processes this can only be non-null if it contains aParcelableof a framework class (not one implemented by the application). For other data transfer usesetData(Bundle).Although you'd think a
Stringwould be a "framework" class...- "remove [the]
The final code I used is of the following form:
Message msg = Message.obtain(null, HandbagUI.MSG_UI_TEST_STRING_MESSAGE); Bundle bundle = new Bundle(); bundle.putString(null, result); msg.setData(bundle);Then in the UI activity it's retrieved with (you could use an actual name rather than
null):msg.getData().getString(null)Who knows what the difference is between
Parcel,Bundle& other forms of Java serialisation but that's how it's done. (Okay, so it's probably all overhead etc related...)And...that's this batch of tabs cleared. :)
27 March 2012¶
Committed bunch of uncommitted app-related code.
28 March 2012¶
Added a connect button and disabled the auto-connect code.
Added a test for network availability before attempting to connect. (This may have been because I kept wondering why the connection didn't work & forgetting I didn't have WiFi enabled. :) )
Based on this approach I made this routine:
private boolean isOnline() { NetworkInfo netInfo = ((ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); return (netInfo != null) ? netInfo.isConnected() : false; }Note: You also need the ACCESS_NETWORK_STATE permission in your manifest.
I'm not 100% sure that the
getSystemService()call never returnsnull.Another related result:
Had an issue when trying to pick a theme from the "Resource Chooser" for the Manifest.
The dialog would appear but you couldn't interact with it.
Turns out that hitting "OK" would close it and then re-opening it would allow you to interact with it.
But apparently the actual fix was found and made and has now been released. Although I haven't updated yet I just used the above work around.
I've discovered that apparently the "use the back button to exit causes leak errors" issue has reappeared (or is still there).
One work around would be to stop the back button from exiting. The other is to try calling
onStopwhen the back button is pressed.I was actually looking into overriding the back button so that I could move from showing the "main stage" layout back to showing the initial configuration view.
According to "Back and other hard keys: three stories" overriding the Back key behaviour is as easy (modulo backward-compatibility) as overriding the
onBackPressed()method in your activity.
30 March 2012¶
Indeed, overriding
onBackPressed()enabled me to change the active view back to the configuration view--if the "main stage" was the currently active view.I could keep the existing "back to exit" behaviour by calling:
super.onBackPressed();But as I've still got the leak problem, I don't call the super and so the app stays active and no errors occurred. (So, you need to press the home button to exit.)
Unfortunately, pressing home to exit caused another exception if you were on the "main stage" view.
This was because the
onPause()code assumed the configuration view with its associated edit fields were visible so it could save their content. I had wanted to move this code to be more closely attached to the edit fields themselves (so the content would be saved on edit rather than on app exit).I assumed there would be an equivalent to
onClick()that I could associate in the XML file, but alas it wasn't that simple.I'm not even convinced this is the proper approach, as it would seem to be better to wait until the input area loses focus rather than saving partially edited text.
The approach I found suggested was to create an anonymous
TextWatcherinstance and override theonTextChanged()method. (Although there are other options for methods to override.)You then add this instance to the
EditTextwidget with itsaddTextChangedListenermethod.This leads to a lot of boilerplate:
widget.addTextChangedListener(new TextWatcher() { public void onTextChanged(CharSequence s, int start, int before, int count) { // Code here } public void beforeTextChanged(CharSequence s, int start, int count, int after) {} public void afterTextChanged(Editable s) {} });Ideally you'd use one
TextWatcherfor multipleEditTextwidgets. I considered this approach and then decided to just have a customised method that attached aTextWatcherand saved the content as a preference:private void attachPrefsSaver(final String prefsName, int id) { EditText widget = (EditText) findViewById(id); if (widget != null) { widget.addTextChangedListener(new TextWatcher() { public void onTextChanged(CharSequence s, int start, int before, int count) { // TODO: Only save valid values? (e.g. support a supplied validation function?) // And/or only save on "Done"/lose focus instead? appPrefs.edit().putString(prefsName, s.toString()).commit(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) {} public void afterTextChanged(Editable s) {} }); } }This is then called like:
attachPrefsSaver("network_host_name", R.id.hostName);I think an alternate approach would be to override the
onTextChanged()method of theTextView(parent ofEditText) class itself--but that would require building the UI on the fly.I also wondered about using
OnEditorActionListener()but will stick with the current solution for the moment.
1 April 2012¶
I had been thinking about how to handle adding widgets to the main stage. In the existing Handbag implementation details of widget configuration get handed to a routine that handles adding them to the view.
For the moment I've decided to change that approach an instead require all widget configuration instances to be able to display themselves on a view that is provided to them.
The idea was to: take an array of widget configuration detail from the comms service; feed it to a static/class method which returned a configured widget of the correct kind; then call a
displaySelf()instance method and provide it with the view on which to place itself."Hey, let's make Interface for that then," I said to myself...
Alas, Java had other ideas...
Long story short, apparently Java can't have static (i.e. class) methods in an interface definition.
2 April 2012¶
Also relevant, you can't override a static method when inheriting from an abstract class either. (It hides it instead.)
You also can't annotate a static method that's hiding another static method either, and if you try to use
@Overrideinstead, you'll get an error like (with Java 1.6?):The method ... of type ... must override or implement a supertype methodor (with Java 1.5?):
The method ... of type ... must override a superclass method(Note that changing compiler compliance level seems to change nothing but the error message text.)
What you can find is people from other language realms thinking "Wow, that's stupid". (see, see & see.)
So, my current thinking is to use one of the following options:
-
Have a static method in an abstract base class that has to know all the subclasses and calculate (from something in the array) which it should use.
-
Have a static method in an abstract base class that has to be hidden and if it has not been hidden it should throw an exception.
-
There's a bunch of answers that suggest using some sort of Factory pattern (which might almost be where option 1 is heading) but they look pretty butt ugly IMO.
Some other related links:
-
"Extension of 'Interface' definition to include class (static) methods." -- A "closed, will not fix" bug requesting Java change this.
-
"Why can't abstract methods be declared static?" -- "The rule against static abstract methods is fundamentally a Java language design decision, which is not explained in the Java Language Specification."
-
"Java abstract static Workaround?" -- request for a workaround plus flameage.
-
Err, so, where was I?
Yeah, so anyway, my current implementation uses option 2 from above and throws
UnsupportedOperationExceptionif the static method in the base abstract class hasn't been overridden or is called directly...It's an unchecked exception because "Here's the bottom line guideline: If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception".
And, after that particularly unproductive excursion, we can return to our already in progress coding...
So, while I was doing all the above I ran into a problem when doing something in the form of:
newWidget.displaySelf((ViewGroup) findViewById(R.layout.mainstage)); // DON'T DO THIS!For some reason my existing layouts didn't have
ids associated with them, so, for example,R.id.mainstagedidn't work (i.e. no autocomplete in the IDE)--not really thinking too much about it I usedR.layout.mainstageinstead. This doesn't trigger any warnings or errors so everything's cool, right?Unfortunately this results in
findViewById()returningnull--I guess it could be worse and return the wrong thing instead...The problem being that
R.layout.mainstageandR.id.mainstagearen't the same thing--I think they should be, but who am I to argue...(See: "findViewByID(R.layout.skeleton_activity) returns null"--I think there might've been another post that mentioned the
idvslayoutissue too...)Anyway, I needed to add this to the outer layout in the
mainstage.xmlfile:android:id="@+id/mainstage"Then I could do this which worked:
newWidget.displaySelf((ViewGroup) findViewById(R.id.mainstage));I should probably pay some attention to whether
ids get added by default in future...Oh, yeah, so I was wondering if instead of treating data packets as an array if I should treat them as a hash/dictionary instead. e.g. something like:
name:value;another_name:[5]valueetc etc. Or:
:name:value;:another_name:[5]valuewhich would be more backwardish compatible.
It might be too demanding on the microcontroller side of things though. But it would allow optional dropping of unneeded fields.
Not going to implement it for the moment...
Wooo,
Scanners... Bleh.
3 April 2012¶
Next up I was working on Java parsing code.
Initially I did a roughly hacky "port" of the Python parsing code: https://github.com/follower/android-arduino-handbag/blob/master/dev-utils/ParseHandbagPacket.java
Then I looked into other options for parsing text in Java.
Given the fairly simple parsing needs I had wanted to avoid using Regular Expressions.
From initial glances
StringTokenizerlooked promising but its documentation warns:StringTokenizeris a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split method ofStringor thejava.util.regexpackage instead.Then I considered
StreamTokenizerbut that seemed overkill given:The parsing process is controlled by a table and a number of flags that can be set to various states. The stream tokenizer can recognize identifiers, numbers, quoted strings, and various comment styles.
Finally I decided on using a
Scannerbecause CB still rules the roads... :)The current (also hacky)
Scannerbased version isn't the best it could be but it seems to work: https://github.com/follower/android-arduino-handbag/blob/master/dev-utils/ParseHandbagPacketBetter.javaIt should be possible to handle extracting both normal delimited fields and safe string fields in one step with regexes but for the moment it does them a character at a time because...regexes. (For starters I'd need to enable dotall mode) when trying to extract safe strings by length.)
The current delimiter initial regex usage is:
<Scanner>.useDelimiter("(?=\\[|;|\n)?")This looks for a safestring start delimiter (
[), end of field delimiter (;) or end of packet delimiter (\n) .Because I needed to know which delimiter was matched, I went looking for someone else who wanted to specify a delimiter for a scanner that splits on some pattern, but doesn't remove that pattern from the tokens.
The helpful Java scanner delimiter look-ahead answer was to use the
(?=...)"look ahead" notation--as seen above. The poster mentions:Look aheads (and behinds) are not included in the match, so they won't be "eaten" by the Scanner.
This might be relevant at some stage: (via)
Notice [the example] invokes Scanner's close method when it is done with the scanner object. Even though a scanner is not a stream, you need to close it to indicate that you're done with its underlying stream.
One downside to using a
Scanneris that there is nonextChar()equivalent tonextInt()etc.One comment suggests the workaround of:
<Scanner>.findWithinHorizon(".",0).charAt(0)For my purposes I temporarily change the delimiter to
""and use the scanner'snext()method.(Note that
nextByte()would only work if there was a single byte before the next delimiter.)On the subject of regular expressions, I found a couple of nice tables with examples of the difference between the different Java regular expression quantifiers (Greedy, Reluctant, Possessive).
Also, along the way I had been using an
ArrayListofStringswhich had a usefultoString()method (not just[Foo@0xdeadbeef) but when I then returned an array of strings instead it wasn't as pretty.Turns out the simplest way to print an array in Java is:
Arrays.toString(arr)The results aren't quite as useful as, say, Python (as strings aren't quoted and the like), it's still better than anything that includes an @ symbol.
But of course Android uses a different regex engine than standard Java... (Oh, yeah, did I mention I developed the parser on the desktop to make it less hassle...)
5 April 2012¶
So, yeah, after developing the Handbag data packet parser on the JVM I foolishly thought I could just import the class into my Android project and continue my life happily...
But, no.
Instead I was greeted with a
PatternSyntaxException:FATAL EXCEPTION: AsyncTask #1 java.lang.RuntimeException: An error occured while executing doInBackground() ... Caused by: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 6: (?=a)? ^ at java.util.regex.Pattern.compileImpl(Native Method)(Okay, so that wasn't the actual pattern--it's a simplified version. The actual pattern was:
(?=\\[|;|\n)?)
(Interestingly enough that error was from Android 4.0.3, on Android 2.3.x the error includes the
U_REGEX_RULE_SYNTAXsymbol.)It turns out according to the Android regular expression docs that the "regular expression implementation used in Android is provided by ICU". The docs also note:
The notation for the regular expressions is mostly a superset of those used in other Java language implementations. This means that existing applications will normally work as expected, but in rare cases Android may accept a regular expression that is not accepted by other implementations.
(My highlighting. :) )
After some poking around I found the solution was to add a set of parentheses. In the case of our simplified example:
((?=a))?And our actual pattern:
((?=\\[|;|\n))?So, the situation seems to be that the Android regular expression implementation treats "look-around assertions" differently to the standard JVM implementation. A look-around assertion such as a "zero-width positive look-ahead" (i.e.
(?=...)) seems to be treated as a single "unit" (and/or "group"?) on the JVM but not the Android/dalvik implementation. Thus when a look-around is combined with a quantifier such as?(i.e. "zero or one") the implementation considers it a syntax error.Adding the parentheses presumably turns the look-around quantifier into a group which apparently then gets treated properly.
(Side note: Standard Java regex implementation docs.)
It would seem this is buggy behaviour.
After I conducted a search for
U_REGEX_RULE_SYNTAXas reported by Android 2.3.4 I finally discovered someone else encountering the same error: "Regex pattern syntax error with forward lookaheads on Android".Further investigation lead to finding the following comment in the ICU regular expression implementation:
Look-ahead can't have quantifiers, but paren stack compile time conventions require the slot anyhow
So it would seem this is an intentional behaviour in the ICU implementation but it doesn't match the JVM behaviour. (And it seems like both approaches occur in other languages.)
In light of this I added a ticket in the Android issue tracker: Regular expression that works on JVM raises SyntaxError on Dalvik
6 April 2012¶
Finished off the previous entry with notes on finding the ICU source etc.
Err, so, once again, where was I...?
Now add widget to display with content from the data packet. Remarkably trouble free. (Although needed to use a Linear Layout rather than a Frame Layout to allow for multiple widgets to be displayed...)
Need to handle a bunch of states when objects that are expect no longer exist. e.g. orientation change, pause app, etc etc.
7 April 2012¶
Fixed some bugs/improved stability in some error situations.
Change orientation in manifest to be:
android:screenOrientation="nosensor"I would've preferred to use
userbecause that seems to allow the "current" orientation of a tablet to set the portrait/landscape status but it also allows the orientation to be changed after the initial status--which I'm avoiding for the moment so I don't have to deal with the destroy/recreate process with my network connections, handlers etc.As a result on a phone the app should be portrait and on a tablet it should be landscape.
It might be possible to force no further changes by setting that state programmatically after the app is started but I'd need to look into that. (Thus the only issue is that sometimes portrait might be better than landscape for a tablet.)
Related documentation links:
Hmmm, looks like using
publishProgressmight be a good option for sending received packets in the comms service.[Retro-update: BTW this post "onProgressUpdate misses some publishes" points out a possible issue with using
publishProgress()related to using local variables and pass by reference. It might be something to keep in mind if you see similarly odd behaviour. (Note: I'm using the first approach mentioned and don't seem to have encountered any issue so far.)]
8 April 2012¶
Made some code formatting changes via Eclipses's functionality.
Started to work on setting up a network connection "properly" by sending a message from the UI service to the comms service--including the required parameters of host & port.
9 April 2012¶
Implemented
AsyncTaskbased method of creating and maintaining a network connection to the target--including passing on received data packets via thepublishProgress()method.Initial version has room for improvement but it now receives & roughly displays 5 widgets in response to the target server.
Still running into issues with the "
socket.error: [Errno 48] Address already in use" error. Tried a bunch of things that didn't appear to make any difference.The following may have helped things slightly:
socket.setdefaulttimeout(0.5)But I'm not really sure--didn't really test it thoroughly.
I was expecting the data packet parser to block on read but it doesn't appear to.
Need to switch to some sort of non-blocking but
select-based approach to reduce CPU usage and (possibly) also enable sharing between both the in and out streams.
11 April 2012¶
Looked into some possible new features (mostly widgets) linked here for posterity:
Changed from the initial hacky direct-to-UI packet handling to having the comms service passing received packets to the (now somewhat misnamed) parse service.
Currently the parse service only knows how to handle "widget" type packets--by dispatching them to the UI.
The UI looks at the widget type field to help it determine the
WidgetConfig-based subclass to instantiate and ask to display itself.I had one problem with getting a
ClassNotFoundExceptionwhen usingClass.forName(String).It turned out that the problem was that the
forName()method requires a Fully Qualified Name if used inside a package. (Also)The static method I call for a subclass of
WidgetConfighas the following signature:fromArray(String[] theArray)When I use reflection to obtain the class and then invoke a call on the method I got the following warning in Eclipse:
The argument of type String[] should explicitly be cast to Object[] for the invocation of the varargs method invoke(Object, Object...) from type Method. It could alternatively be cast to Object for a varargs invocation
This StackOverflow answer translated the message in the process of answering a different question. Essentially you have to specify if you want the array to be treated as one argument (which I did) or as list of multiple arguments.
To quiet the warning I cast to
Objectlike so:<method>.invoke(null, (Object) packet);(The
nullis because it's a static/class method.)I decided I would use a
HashMapto convert from the widget type specified in a packet (e.g.label) to the class name used to display it (e.g.LabelWidget).This hash map can (currently) be created once as a constant as it does not change during runtime.
It seems that there are multiple, not that great ways to initialise a hash map but I initially chose to use the "anonymous subclass" approach.
Unfortunately this approach led to an error:
The serializable class does not declare a static final serialVersionUID field of type long.
From reading about this error message I decided that although I don't particularly like the style it was probably best to switch instead to the "static initialiser" approach.
Added dialog and progress bar widgets. Added ability to modify existing label.
Because the ProgressBar documentation talks about theming only in the context of an XML defined progress bar I was unsure how to handle theming when the progress bar is created programmatically.
This is important because the default progress bar theme (oddly) sets the appearance to be an "indeterminate" circular progress indicator.
There was a StackOverflow answer which showed how to theme a programmatically created Android progress bar like so:
bar = new ProgressBar(<context>, null, android.R.attr.progressBarStyleHorizontal);Added base support for "Features" i.e. non-UI things like sensors, speech etc.
Added rough initial support for
TextToSpeech. Need to handle initialisation better.Need to provide a way for Feature classes to be instantiated once and then re-used.
Woooo, talkies! :D
Added SMS message sending feature. The destination can be given as either a number or a contact name.
Getting, for example, a mobile phone number for a particular contact name is far too convoluted for my liking.
This Android contact number retrieval example was very helpful for getting an idea of how to proceed.
At one stage I was getting the following error from a call to the
ContentResolver'squery()method:java.lang.IllegalArgumentException: Invalid column 2What wasn't very clear was that it was referring to an invalid column name of
2.The reason it found a column of that name is because I used
Integer.toString(Phone.TYPE_MOBILE)instead ofPhone.NUMBERor something like that. :)The final result is:
private String getContactNumber(String contact) { // Use as a "number"/address if we don't find it as a matching name. String contactAddress = contact; // Try to get a matching name in contacts first: Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, new String[] { ContactsContract.Contacts._ID }, "DISPLAY_NAME = ?", new String[] { contact }, null); if (cursor.moveToFirst()) { // TODO: Check if "has number" is true first? String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)); Cursor phoneNumbers = resolver.query(Phone.CONTENT_URI, new String[] { Phone.NUMBER }, Phone.CONTACT_ID + " = ? and " + Phone.TYPE + " = ? ", new String[] { contactId, Integer.toString(Phone.TYPE_MOBILE) }, null); if (phoneNumbers.moveToFirst()) { contactAddress = phoneNumbers.getString(phoneNumbers.getColumnIndex(Phone.NUMBER)); } phoneNumbers.close(); } cursor.close(); return contactAddress; }Note that the function accepts a string (either a phone number or a contact/display name) and returns the first phone number of the mobile type found or the original string if nothing else was found. This allows you to supply a phone number and get it back, so you don't need to specify if you've got a contact name or a phone number.
It seems it might be possible to get the result with one query, based on an answer to "Android contacts Display Name and Phone Number(s) in single database query?".
I don't like having to add so many permissions to the app but at the moment it seems that is necessary.
There is a section Getting access with temporary permissions which suggests that in certain circumstances there might be a way around needing to ask for some permissions. It may well be that I need a support app that does "extra" functionality if people don't like granting the permissions to the main app.
12 April 2012¶
Ran into an issue with some combination of factors that meant I couldn't get the app to send a reply to the Python test server correctly.
I'm not entirely sure on the details.
(One debugging issue is that I forgot for a while that Python's
print "foo",approach doesn't flush the output--which means I wasn't actually seeing the input I was receiving while debugging! d'oh!)I did make some changes, some combination of which made the difference:
-
Wrote to the data stream in the app with
writeBytes()rather thanwriteChars(). -
Instead of only trying to send when there was no packet received by the app I changed to always checking for an available packet to send. (And had already switched from a simple thread sleep to a timeout poll what on the queue (for 250 milliseconds)).
-
Did a
flush()on the app's output stream (don't think this had an impact). -
Used the following hacky code on the Python side:
while 1: # TODO: use different parent class so we can use file interface? try: c = self.request.recv(1) except socket.timeout: print "." break continue if c == "": break print c
The issue I was running into was I kept hitting an error along the lines of
timeout: timed outon the Python end without seemingly receiving any data. But then I was also getting errors on the app side withjava.net.SocketException: Broken pipe(and/orEPIPE) exceptions. (Think the full error wasjava.net.SocketException: sendto failed: EPIPE (Broken pipe).)So, some combination of the above apparently fixed all that. :)
-
Another possible useful approach would be to use Python's
select()functionality.Wow, what are the odds of thinking "I'd like to be able to demonstrate this without an extra device at TVIC tonight, I wonder if I can add a local test server feature" and then actually getting it implemented without major hassle!?
I would think low but that's what happened.
Now I just have to avoid fiddling with it and breaking it. :)
Thus, it's now possible to demonstrate the features (and the benefit of a text-based protocol) by editing an example transmission on the phone. (There's no interactivity but that's okay in this context.)
It turned out to be pretty straightforward using a
ServerSocketbut worth remembering that by defaultServerSocketlistens onlocalhostonly.Also this article "Incorporating Socket Programming into your Applications" was helpful for getting started.
The end result looks like:
public void onClick_buttonStartTestServer(View theView) { // Note: This code uses Fully Qualified Names to avoid having // to have imports--since this code is only intended to be // temporary. android.os.AsyncTask<String, Void, Void> serverThread = new android.os.AsyncTask<String, Void, Void>() { @Override protected Void doInBackground(String... params) { java.net.ServerSocket server = null; java.net.Socket client = null; try { server = new java.net.ServerSocket(0xba9); // Note: localhost only server.setReuseAddress(true); client = server.accept(); client.getOutputStream().write(params[0].getBytes()); client.close(); server.close(); } catch (java.io.IOException e) { e.printStackTrace(); } return null; } }; serverThread.execute(((EditText) findViewById(R.id.textToSend)).getText().toString()); }(With an associated
EditTextto supply the data you want sent to clients.)As a side note about
EditText/TextViewformatting/alignment/gravity;"So gravity attribute specifies how to align the text inside the
TextView[while] layout_gravity specifies how to align/layout the
TextViewelement itself."
21 April 2012¶
Been otherwise occupied for a wee while (possibly by things such as this). :)
Moved Python parsing code into a more easily usable class and changed the code to work on streams rather than buffers.
Integrated this new parser class into test server and started to parse the dummy data received.
23 April 2012¶
Yesterday I implemented the Java packet generation code.
In the process I wanted to do the equivalent of this Python code using an array of strings:
";".join([...])There is a "Best way to convert an ArrayList to a string" question on SO but the top-voted answer doesn't actual work correctly anyway (it always appends the delimiter, even to the last item).
But further down the page an Android-specific string array join approach is given which uses the
joinmethod of theandroid.text.TextUtilspackage, like so:TextUtils.join(";", <String array>)Handy. :)
24 April 2012¶
(Aside: Looking at USB Host-compatible Atmel chips with Arduino core and/or bootloader support:
-
"AVR boards for Arduino" -- table of CPUs and Arduino support. (Also: "Alternate CORE files for Arduino") (Not updated since 2010.)
-
Micropendous -- See: LUFAduino, Micropendous (current Arduino-style form factor), MicropendousA (USB Key-style form factor), Older version variations, "Native" ADK support, "Arduino1" firmware (via here) (It's a bit confusing to tell which USB Key-style device is the latest/best.) (Sales: "Android ADK-Compatible USB Development Board 'Micropendous'; NO SHIELD REQUIRED!", "Atmel AT90USB1287-Based USB Development Board 'Micropendous'; AVR Demo Tool USA" (AFAICT it's the same board just named differently.))
-
(Linked from above) LUFA ADK/AndroidAccessory source: AndroidAccessoryHost (LowLevel), AndroidAccessoryHost (ClassDriver)
-
Atmel docs: "AVR287: USB Host HID and Mass Storage Demonstration", "Atmel AVR4950: ASF - USB Host Stack"
)
-
Been working on supporting "unexpected" disconnection on the app and in the Python test server.
Have run into a few issues along the way.
25 April 2012¶
The Java
PacketParsercode had a bug which would cause the parser to hang if a packet had been completely parsed but no additional data was available (I think). Because of the order of the check the routine hung/blocked on thehasNext()check.The fix was to ensure we check for a completed packet first:
- if ((!scanner.hasNext()) || (packetComplete)) { + if ((packetComplete) || (!scanner.hasNext())) {
24 June 2012¶
Err, so things got left in a bit of a messy state between uncommitted code and a bunch of open tabs--so, we'll see what I can work out. :)
I ran the Python test server with:
python test_handbag_packet_server.pyAnd it started up.
I then started the Handbag (app2 version) app on the Nexus S and it connected to the Python server okay.
The app displayed a label and a button; pushing the button sent click events back to the Python server.
The test server appeared to handle the remote disconnect and it was possible to reconnect okay.
Committed the outstanding code edits with sometimes helpful commit messages. :)
Most recent addition of button and connection close handling. The latter really needs to be tidied up on the Python side.
The app side really needs non-blocking IO stuff added (as the Python code is currently sending "idle" messages to avoid blocking).
Refactored Python code to be tidier and split into setup/loop/callback style.
Added a bunch of helper routines to Python test server: labels, speech, dialog, change label text.
Added a bunch of demos to the Python test server.
I was thinking I needed to use non-blocking sockets but on further reading I sure hope I don't! (Due to issues around also using
Scanner.)I think I might be able to get by with judicious use of
setSoTimeout()instead.
25 June 2012¶
I'm trying to use
setSoTimeout()but that causes the scanner to bail out without returning anything useful.I noted that the
java.util.Scannerdocs say:If an invocation of the underlying readable's Readable.read(java.nio.CharBuffer) method throws an IOException then the scanner assumes that the end of the input has been reached. The most recent IOException thrown by the underlying readable can be retrieved via the ioException() method.
So this means that when the socket timeout occurs the
Scannerassumes there's never going to be anything more to read. Which is not the case.It also means (I assume) that
hasNext()method will now only return false from that point onward.I verified the socket time out was occurring by using
Scanner'sioException()method which returnedjava.net.SocketTimeoutExceptionas expected:Log.d(this.getClass().getSimpleName(), "Last exception: " + scanner.ioException());I realised that I could handle the timeout by creating a new
Scannerinstance if a timeout occurred and nothing had been read (i.e.(currentFieldContent.length() == 0) && (fieldsInPacket.size() == 0)).I implemented this and it worked. (Note however that this means that we'd be recreating the
Scanneron every timeout which would presumably be bad--we'd be better to only create it at the beginning of the method if there was data to read...)But with further reading I realised that if partially undo the change from revision 257 "Change to use stop using
InputStreamReaderfor the parser" and go back to using anInputStreamReaderinternally then it has aready()method which should avoid the need for the timeout handling at all--at the initial read at least. (Although we'd still need to check if a subsequent (mid-packet) read failed--but it we don't use a timeout it should just block.)So, I'll commit the current approach but then change it. :)
Ok, so, now I've committed the
InputStreamReader/isReady()approach as it worked quite happily. sigh Would've been good to realise that before I removed its use a couple months back!Still, I now create the
InputStreamReaderin the parser so that's possibly tidier. (Although if necessary I could just change the parser to accept aReaderwhich would mean it would have aready()method already.)Incidentally, for the purposes of future reference:
I initially thought that I wanted to be able to do the equivalent of a
peek()/look-ahead action on the input stream in order to determine if a byte was available.This lead me to How do I peek at the first two bytes in an InputStream? which mentioned
PushbackInputStream.Along the way I also found a "Java I/O Streams" article which mentioned
PushbackReaderwith the description "Allows to 'peek' ahead in a stream by one character".Because
PushbackReaderimplementsready()andPushbackInputStreamdoes not, I thought I was going to have to look into usingPushbackReader(or make use ofread()/unread()withPushbackInputStream). But then it turned outInputStreamReaderhadisReady()anyway so I didn't need to look any further.And, as a complete aside, for some reason I'd thought that the
nioinjava.nio.*meant "non-blocking" but it just means "new" and I in fact didn't need to go there. Apparently I don't know everything. :)Ohhh... Which reminds me...
The other day when I started looking into the whole non-blocking sockets thing I also wondered how to get it working with
Scanner.This "Java non-blocking socket" question included an answer that mentioned the use of
java.nio.channels.SocketChannel. I found a post on how to use a "Java NIO Socket Channel". But then I found a post on "non-blocking channel Infinite loop in java.util.Scanner" (funnily enough posted earlier in the month) which mentioned:"One of my student find a bug in the implementation of Scanner, that allows you to use a non blocking channel as input of a Scanner".
So, that didn't seem very hopeful.
It was around that time I decided to see if I could take a different approach--rather than have to re-write things to not use a
Scanner.Changing the time out on the send packet queue has greatly improved responsiveness. I still notice the occasional latency but not sure why. I also wonder if the initial display of the UI widgets can be sped up but it's probably not really a big issue currently. :)
Also removed a bunch of very low-level logging now I've fixed some of the issues it was used for. (And had some concerns about possible overhead it might be adding.)
Now that the protocol/app is pretty stable with the Python test server I'm going to switch to re-implementing things on the Arduino side.
I need to make the Arduino Handbag library interact with the new ASCII/stream-based protocol and communicate with any arbitrary
Streamimplementing class.Because the Arduino Ethernet library doesn't work correctly unless the client (i.e. the Android app) sends data first I needed to change the app to send something on connection.
I always planned a handshake anyway, and using the same basis as the current library handshake the app now sends "
HB2\n" which happily is also a valid packet. So the Python test server parses it but ignores it for the moment.On the Arduino side I just whipped up a server that sends one response, the guts of which is:
void loop() { EthernetClient client = server.available(); if (client) { server.write("widget;dialog;Hello!\n"); client.stop(); } }I'm using a wired Ethernet Arduino-compatible (Freetronics EtherTen from some work I did for them on the TFTP bootloader) plugged directly into my laptop, rather than into the switch directly. (For various reasons I'm not using anything wireless yet nor have it plugged into the switch--which could actually make use of the remote-reset TFTP bootloader.)
Anyway, because the phone can't connect directly to the link-local connected Arduino I need some way to proxy from one IP on the laptop to the other.
(As an aside, when I had the board plugged into laptop and ran the Python test server, the test server picked up the wrong IP address--it used the link-local one--so I had to hard code the correct network-visible one.)
I thought I'd look into using
nc(a.k.a. netcat) and found some details on Proxying with it.The solution documented worked as a one-shot deal but there's probably a better way to configure things:
mkfifo pipey # Do this once nc -l 2985 0<pipey | nc 169.254.254.169 2985 1>pipeyUsing this approach I could get the Arduino to have a dialog displayed on the app. Sorta progress!
[Update: I can get a multi-shot result with:
while true; do nc -l 2985 0<pipey | nc 169.254.254.169 2985 1>pipey; done]
But, of course, it's all hardcoded...
I couldn't get it to send two widgets, not sure why...
First I tried connecting with telnet on the laptop instead of the app on the phone and it delivered all the content.
Then I tried connecting up a "manual" server with this (and connecting from the phone):
nc -l 2985When I pasted one line at a time it sent both widgets.
But when I pasted both at once only one was display/"received".
So, for some reason the app's not picking up the second widget? Hmmm...
Yeah, it works in the sketch if I add a delay--I tested with a 1 second delay and it worked.
Not sure why it doesn't work without the delay...
(Lol, so, you know that debug logging code I just removed... :-p)
With a 10 millisecond delay it work most of the time but sometimes it didn't so presumably that's on the edge of suitable values...
Oh, wait, that was with the delay in the middle of the two sends.
I just thought it might be because I'm closing the connection too early.
Nope, only having a delay at the end doesn't work.
I'm now trying with three widgets and a delay of 50 milliseconds between them works most of the time.
So, I could try to work out the actual issue... Or just stick with a delay for the moment...
With the logging in it "seemed" "more reliable" but eventually had the error.
There's nothing being logged until I hit the back button when I get:
D/NetworkConnection(8934): InterruptedException sending packet.Which is a little odd--but I think it might be because I don't have sufficient granularity in my exception catching.
It might be time to call it a night...
So, for the moment I've bumped up the delay to 100ms after every write.
Also, it never seems to work the first time after sketch upload...
Just tried it out plugged directly into the switch and it works happily, so, something... :) (No idea if it might have an issue with delays there or not...)
26 June 2012¶
Got caught out with "Note that a client is considered connected if the connection has been closed but there is still unread data" proviso of
Client.connected().Looking into generating packets on the C side of things and looks like I want
strpbrk():The strpbrk() function locates the first occurrence in the string s of any of the characters in the string accept. ... or NULL if no such character is found
Although I note that there appears not to be an equivalent on the PROGMEM side of things...
27 June 2012¶
Made a bunch of progress on initial Arduino "library" work. Can handle labels, dialogs, progress bars and speech over a network.
Still need to pull out of the sketch into a library.
Currently working on handling receiving packets and processing them.
Noticed that if the app has an active connection and I then upload the sketch, then if I go back to the connect screen and connect it works for the first connect--whereas normally the first connect doesn't show anything.
Just a data point for some future attempt to solve the problem...
28 June 2012¶
Quiet because it's been uneventful, not because nothing's happening. :)
Added button and text input handling.
Aside from the rough edges this means the non-library network code is now (I think, at least) feature-comparable with the ADK code. The network app is also at the same level.
Still need to add SMS sending that is already implemented in the app into the "library".
Also, all the protocol-related stuff is in a separate "mix-in" class which should make it easier to modify the ADK library to handle the new protocol.
As an aside, unfortunately the ADK 2.0 Google I/O session isn't being live streamed. But helpfully someone posted that the new docs are already online:
AFAICT nothing in it is yet making Handbag obsolete. :)
The AOAP over Bluetooth support could be interesting--long term I've been wanting to add Bluetooth support in addition to WiFi/Network stuff I'm working on at the moment.
The library & examples still seem to have a pretty "unfriendly" API IMO. :)
L.adkEventProcess()indeed.
29 June 2012¶
Thought I should really get around to documenting the updated 1.0 compatible (and also new
UsbHostlibrary compatible) version of the library--and release it...So, started writing up some Handbag for Android install instructions.
In the process I tried to download on the Nexus S and ran into a problem where it failed to download the apk file properly.
It seemed to download successfully but then suddenly says the download fails:
I/DownloadManager( 257): Initiating request for download 99 I/DownloadManager( 257): Initiating request for download 99 W/DownloadManager( 257): Aborting request for download 99: Trying to resume a download that can't be resumedTried killing the browser and even rebooting the phone but it didn't work.
I found a few complaints about similar errors:
And a seemingly associated issue:
- Android DownloadManager Downloads Same File Twice, Fails on Others (Error 1008, ERROR_CANNOT_RESUME)
This apparently is an issue with the Download Manager. I couldn't find a workaround online.
My workaround was to download the Firefox
.apkand use that to download the Handbag.apk.(Fortunately the Firefox
.apkfile downloaded okay. I then tried downloading the Handbag.apkfile with the Android browser again but it still didn't work.Weirdly enough when I then tried to download the Handbag
.apkin the Android browser it downloaded okay--but it used an-1appended filename.)When I cleared all the app data for the browser (from the "Manage apps" menu) it downloaded successfully--but only once... shrug
(Seriously, with everything I've done in the last day and it's downloading that's not working... sigh)
There's still multiple issues with the "download the apk" / "select app to open with" timing also... Which makes for a bit of a crappy first-run experience. It needs improvements to the library and the app, really...
Hmmm, also, seems to run on ICS much less reliably... Mainly not being able to reconnect. Because it doesn't seem to release the USB connection...
And it was all going so smoothly...
I tried to push some changes to GitHub (via the
hg-gitplugin) and got the following error:pushing to git+ssh://git@github.com/follower/android-arduino-handbag.git importing Hg objects into Git creating and sending data fatal: The same object 257d34ae75cc8630112f38434202deae9a7e2cb3 appears twice in the pack ** unknown exception encountered, details follow ** report bug details to http://mercurial.selenic.com/bts/ ** or mercurial@selenic.com ** Mercurial Distributed SCM (version 1.5+20100307) ** Extensions loaded: record, bookmarks, hggit, fetch, color Traceback (most recent call last): File "/usr/local/bin/hg", line 27, in <module> mercurial.dispatch.run() File "/Library/Python/2.5/site-packages/mercurial/dispatch.py", line 16, in run sys.exit(dispatch(sys.argv[1:])) File "/Library/Python/2.5/site-packages/mercurial/dispatch.py", line 30, in dispatch return _runcatch(u, args) File "/Library/Python/2.5/site-packages/mercurial/dispatch.py", line 47, in _runcatch return _dispatch(ui, args) File "/Library/Python/2.5/site-packages/mercurial/dispatch.py", line 466, in _dispatch return runcommand(lui, repo, cmd, fullargs, ui, options, d) File "/Library/Python/2.5/site-packages/mercurial/dispatch.py", line 336, in runcommand ret = _runcommand(ui, options, cmd, d) File "/Library/Python/2.5/site-packages/mercurial/dispatch.py", line 517, in _runcommand return checkargs() File "/Library/Python/2.5/site-packages/mercurial/dispatch.py", line 471, in checkargs return cmdfunc() File "/Library/Python/2.5/site-packages/mercurial/dispatch.py", line 465, in <lambda> d = lambda: util.checksignature(func)(ui, *args, **cmdoptions) File "/Library/Python/2.5/site-packages/mercurial/util.py", line 401, in check return func(*args, **kwargs) File "/Library/Python/2.5/site-packages/mercurial/commands.py", line 2441, in push r = repo.push(other, opts.get('force'), revs=revs) File "build/bdist.macosx-10.5-i386/egg/hggit/hgrepo.py", line 20, in push File "build/bdist.macosx-10.5-i386/egg/hggit/git_handler.py", line 148, in push File "build/bdist.macosx-10.5-i386/egg/hggit/git_handler.py", line 550, in upload_pack File "/Library/Python/2.5/site-packages/dulwich-0.6.2-py2.5-macosx-10.5-i386.egg/dulwich/client.py", line 181, in send_pack self._parse_status_report(proto) File "/Library/Python/2.5/site-packages/dulwich-0.6.2-py2.5-macosx-10.5-i386.egg/dulwich/client.py", line 104, in _parse_status_report raise SendPackError(unpack) dulwich.errors.SendPackError: unpack index-pack abnormal exitSeems like I'm not the only person to encounter this error:
- fatal: The same object [hash-id] appears twice in the pack -- This has a set of steps that worked around the issue for that person apparently. I didn't try it out directly.
There's also an open issue:
I tried only pushing a specific revision (with
hg push -r) at a time but got an error:abort: revision <rev> cannot be pushed since it doesn't have a refI found a couple of related issues but they seemed to just boil down to--don't do it--so, not a solution:
Also, a few posts with the same error message: this, this, this, this
So, going back to the original error...
The "appears twice in the pack" error can be seen in the
.hg/git-mapfilewhere there are two entries where the left value is the same and the right value is different. This appears to be a bug in the plugin code--I don't know what specifically caused it.Looking at the plugin code it seems the left-hand value is the git sha and the right-hand value is the Mercurial sha. I'm wondering if there's an issue in
export_hg_commit()where it's finding multiple parents or something toward the end... (Although why it's doing that I have no idea...) Anyway...So, I had no idea if it was a specific commit that was causing the problem with the push, I tried a few approaches but in the end I used a new repository clone (of a known-good/successfully pushed revision) and then used
bundle/unbundleto transfer commits across--until I found one that didn't commit. e.g.# in original repo hg bundle --base 361 -r 363 bundle-361-363.bz2 # in second cloned repo hg unbundle ../../../android-arduino-handbag/bundle-361-363.bz2It was the most recent commit (the result of an
hg mvrename that changed a file extension) that apparently caused the problem.I "solved" the problem by making another commit in the second clone repository to a plain text file. And then tried the rename operation and it committed & pushed happily...
rollback-ing the last transaction in the original troublesome clone and thenpull/update-ing seemed to fix things in the original clone--unless I find something's broken later... :)Now, what was I doing..?
Tidied up the last of the examples to remove extraneous library includes and use the new simplified constructor.
Also used the new file
.inoextension.Made a new library release with:
hg tag 004 hg archive -r 004 -t zip -I README.txt -I "library/*" ../releases/handbag-arduino-library-004.zipUploaded the result to: http://handbagdevices.com/files/handbag/004/handbag-arduino-library-004.zip
Updated the installation instructions on http://HandbagDevices.com/ and tidied up the page layout a smidgen.
DONE: Update the links at the top of this page. Do blank test.
2 July 2012¶
Did a test in a mostly blank account and it all worked following the instructions, so that's progress.
There's still the issue with the initial, first connect timing out waiting for the app default dialog but I added a note about that in the instructions.
Added the new links at the top of this page.
Hmmm, so, I've been doing most of the testing of the network version on an ICS phone and I've just tried it out again on my 2.3 phone and it's pretty much not working... :/
It certainly used to work. I'm partly wondering if it's something to do with the demo having too much UI specified. But while it is fractionally more working with only one label it still fails after one or two connections... argh
Guess I'll have to take another look at it...
The probably bad idea occurs to me that I could provide string storage/manipulation services from the Android device to the Arduino--hey, why not expose a Turing-complete scriptable language! Because. That's why.
Implemented the send SMS function in the Arduino sketch and added it to the demo--which has now run out of screen space. :)
(The demo ICS phone uses a 2degrees SIM and as they don't provide a free SMS balance request I send a test message to 233 instead.)
Hmmm, so, if I remove delay of 100 ms from the end of the packet sending then the 2.3 stuff seems to work again. And things are much faster.
And it does actually still seem to work--I suspect because the connection isn't being closed right away, so it may be redundant by now.
I'll stay without the delay for now and see if it breaks...
Hmmm, but now if I try to get the
SimpleHandbagequivalent sketch running it doesn't work on ICS--with I think the same symptoms I was trying to work around with the delay...Annnnd.... putting back the delay makes the
SimpleHandbagequivalent work again. I really need to work out what the deal is with the first-start issue, I think...I'm beginning to think the initial connection problem is related to the netcat (
nc) stuff... I think I need to set up the networking stuff properly...
3 July 2012¶
So, I tried again with no delay and a direct network connection (not via
nc) and nothing works reliably on 2.3 or ICS. So while the initial connection issue might bencrelated it seems all the timing stuff definitely isn't. :/Okay, so, the first failed connection issue is almost definitely due to the use of
nc--as the standardChatServerdemo via telnet exhibits the same failure when connected viancbut not when connected directly... sighUgh, so the DSL-G604T wireless ADSL router I have doesn't do wireless repeating/bridging/whatever, so that option won't work.
Discovered that I actually had
ncat(fromnmapproject) installed but that was no more reliable in terms of the initial connection with this invocation:ncat --sh-exec "ncat 169.254.254.169 2985" -l 2985Tried installing
netcatviabrewwhich compiled but wouldn't symlink the file (possibly due to existingnc?).But it turns out that it seems to be more reliable for the first connection after an upload.
I got it running continuously with:
while true; do /usr/local/Cellar/netcat/0.7.1/bin/netcat -L 169.254.254.169:2985 -p 2985 -x; doneThe
-xdumps a hex & ASCII report of data transferred.It seems to require a
^Z&killrather than^Cto stop it.Although, on further testing it seems like it's much less reliable on subsequent connections... :/
Having said that the hex dump does at least seem to show that the full messages are getting sent to the app in a variety of "packet" sizes and it doesn't seem to be any particular combination that doesn't work.
Hmmm, using
ConnectBoton the phone seems to now show that the sketch is freezing after one response...Gah, now everything seems to be unreliable...
Ok, so using ConnectBot (with Telnet) causes the library to get stuck in a (essentially) time-out loop presumably due to a malformed packet.
Next up was
socat:socat TCP4-LISTEN:2985 TCP4:169.254.254.169:2985Which also seems to work for the first and subsequent connections.
Oh, and displays the data transfer with:
socat -v -x TCP4-LISTEN:2985 TCP4:169.254.254.169:2985(The
-vand-xoptions can also be used individually.)It also seems more robust with
reuseaddr:socat -v -x TCP4-LISTEN:2985,reuseaddr TCP4:169.254.254.169:2985But whatever the proxy/tunnel being used, it sure seems like the data is being sent successfully...
Oh, so, with ConnectBot I could set a "command" to execute on connect so I could use
HB2as the command and that's a legimate packet so it should beat hitting enter...I tried everything with the Python test server (even via
socat) and it all works totally happily, even though there's more data being sent.A couple of notes:
-
The display seems to pause after the first button is sent.
-
The test server sends the UI straight away and doesn't wait for the
HB2message (which the Arduino has to) so it ends up being sent in the middle of the other setup data.
-
Anyway, somewhere along the way it occurred to me that I could still use the wireless AP as it doesn't actually need to connect to the rest of my network--I can just connect the devices to that rather than my normal AP then I don't need to worry about tunneling/proxying. Dunno if that'll make any difference.
Well, I still don't have any more definite things, but:
-
On the Arduino side, the following "problematic" because
clientwill be false if there is no data waiting--but it doesn't mean the client is disconnected!if (!client) { client = server.available(); }This I'm fairly certain has caused problems with disconnections. So, I've got a rough sketch working which will handle disconnections seemingly correctly.
-
On the Android side, with the test sketch, and using ConnectBot over Telnet (on both 2.3 & ICS) I get what appears to be reliable data reception without any delay on the sending side. So, I think this means my data receiving code in the app is broken in some manner...
-
Using this test code rather than the packet parser on the Android shows that all the bytes are received from the test sketch and the
SimpleHandbagsketch:int c; try { while ((c = dataInStream.read()) >= 0) { Log.d(this.getClass().getSimpleName(), "got: " + (char) c); } } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); }This presumably means that the issue is in the packet parser--and I suspect it's the scanner class...
So, I guess I need to try a re-write...
Wow, I think I have finally sorted all this out...
While I was part-way through re-writing the parser to no longer use a
Scannerand got back to the whole socket time-out/blocking reads etc etc issues, I thought to myself, "You know, if it's this difficult, you're probably doing something wrong.".I then got thinking about how if all the reading stuff was in a separate thread I wouldn't have to worry about the blocking side of things. (Which, in hindsight is a bit "Oh, duh", but it had mostly worked until now. :) ) Which then got me thinking about my note to use a
BlockingQueuefor received packets in the same way I was using them for packets to be sent.So then I reverted all my changes and went ahead and converted the parser to run as a separate thread and use a
BlockingQueuefor communication with the main data transfer loop.And it worked!
(Or, at least, seems to, currently. :) )
No delay is needed on the Arduino side and it seems to work with both Android 2.3 and ICS.
Yay! Long may it continue...
(As to why things were unreliable I'm not really sure but I do wonder if my dual use of the input stream
ready()and scanner on the same stream might've had something to do with it--although I did test that theory out at one stage I think by creating the scanner on the fly and I don't think it made any difference.)One somewhat unexpected thing I encountered along the way was a
NoSuchElementExceptionexception in the Parser thread when I was disconnecting (i.e. back button to main screen).The exception was being thrown by the
Scanner.next()method--I assume because it was getting interrupted or broken or something... My solution was to explicitly catch it in the main method of the thread and treat it as a reason to exit the thread. It seems to work. :)BTW the debugging/testing was still all done with using
socat:while true; do socat -v -x TCP4-LISTEN:2985,reuseaddr TCP4:169.254.254.169:2985; doneI'll look into the other AP approach eventually but it's actually handy being able to see the data being transferred at the moment anyway, and requires one less piece of hardware. :)
It's nicely responsive now too... :)
Had a slight panic when it seemed like things had stopped working again--but it turned out that
socathad exited after i'd suspended & resumed it. :DBut the
NullPointerExceptionmessage when backing out from the failure to connect reminded me that was one of the things I had also reverted--fortunately I kept a copy--but then ended up implementing it differently.So, now the app doesn't force close if you try to connect to a non-listening device--which is just as well because it prevented from ever connecting again until you left the app otherwise... :)
Although it doesn't seem to make the difference in the library that I thought it might, I did discover something important about the
EthernetClienthandling (which I think theChatServerArduino example fails to make clear.If you look at the
Server.available()docs it says:Gets a client that is connected to the server and has data available for reading.
And then says about the object returned:
a Client object; if no Client has data available for reading, this object will evaluate to false in an if-statement
Now, I did know this but the implication is that there's a weird interaction between connected/available--if there's no data waiting and the remote has disconnected there's no way to know the disconnect has occurred--unless you've held on to the
Clientobject!Unless you've drain the input before checking,
client.connected()will never return false becauseServer.available()will never return a client for which it's not true. Or something.Anyway, by way of explanation, a snippet of a re-worked
ChatServerloop I used for testing:void loop() { // wait for a new client: EthernetClient client = server.available(); // when the client sends the first byte, say hello: while (client) { if (!alreadyConnected) { // clean out the input buffer: client.flush(); Serial.println("We have a new client"); alreadyConnected = true; } if (client.available() > 0) { // read the bytes incoming from the client: char thisChar = client.read(); Serial.write(thisChar); } if (!client.connected()) { Serial.println("client disconnected"); client.stop(); alreadyConnected = false; } } }Committed all the code at the current point. Phew.
Oh, except I appear to have broken the some of the widget handling... The responses to the button push to show dialog and text input to change label don't seem to be making it through.
Oh, okay, so, it was because I removed the
delay(100);from the mainloop()of the demo sketch--I guess that means the Android (or I guess it could be the proxy or Eclipse or...?) was getting saturated. That's a relief. :)I should probably look into what the limits are and what's causing them.
Yeah, so, the problem is that there's too many progress updates so something takes ages to get to the other responses. (So, with a delay of 10ms in the loop you have a few second delay before you get a response to the button press.)
At 50ms it's pretty reasonable. Except it's pointless having that frequency of updates... :D
8 July 2012¶
Have started a page for my notes on the Android ADK 2012/2.0 which will hopefully support Handbag one day. :) Or maybe Handbag will support it. In Soviet Russia.. :D
Continued refactoring library into separate files.
3 September 2012¶
Added a link to the LCA 2012 talk video on YouTube.
15 October 2012¶
So, yesterday I thought I'd take a look at solving the problem on Android 2.3.x whereby a second connection wouldn't open up the correct view.
Yeah.
It ended up mostly being an exercise in frustration.
To recap: With the new version of Handbag on Android 2.3.x, when you connect the USB accessory for the second time one of two things will occur:
-
If you are still on the Handbag "Network config" screen, then nothing will appear to happen.
-
If Handbag is not currently running then the application will launch but it will launch to the "Network config" screen instead of the USB/"Main display" screen.
On Android 3.1, 4.0 and 4.1 re-connections are handled as expected.
-
I should probably mention here that I really don't fully grok the whole Android application/activity/task/stack razzmatazz (and, wow, the spellchecker actually had razzmatazz in it--who knew it had two z's--anyway....). It seems overly complicated and it's not always entirely clear what it gains you.
This is probably relevant. :)
I suspect the current way I'm handling things is less than ideal.
When an accessory is connected it starts an activity to setup USB communication and that activity then launches the main "display" activity, in such a way (i.e. via
startActivityForResult) that when the "display" activity is completed it returns control to the usb setup activity which then (I think, replaces itself) with the network setup screen activity (which should really be a main communications method selection activity but I don't have one yet).I discovered a few things that probably contribute to the problematic behaviour along the way:
-
The setup screen activity is started with the
FLAG_ACTIVITY_TASK_ON_HOMEandFLAG_ACTIVITY_CLEAR_TASKflags (among others) and these are not actually available in Android 2.3.x so the expected behaviour won't happen. (These flags are only available with API Level 11 and above--I didn't notice this in the IDE because the relevant SDK level setting is higher than Level 10 because otherwise I can't use the Gingerbread & Honeycomb USB handling approaches in the same app, which is a whole nutha issue because it actually appears that somewhere along the line the compatibility classes stopped being used but the IDE doesn't seem to know this...). -
One of the other flags being used to launch the network setup activity is
FLAG_ACTIVITY_NEW_TASKwhich, it turns out, has some slightly weird behaviour that I think occurs in the case I'm using--although I'm not 100% certain on that.The weird behaviour is that if an activity (let's call it
C) (which was started withFLAG_ACTIVITY_NEW_TASKset) spawns another activityDand then exits, the next timeCis started with the same intent the activity won't actually be re-started, instead activityDwill have itsonResume()method called!I found this out in this "Manipulating Android tasks and back stack" presentation on slides 91-101 (with the key point on slide 101: "No new instance of Activity C is created, since there is an existing task with matching intent. The task is brought to foreground, even though Activity C doesn't even exist!"). sigh (BTW the slide presentation has some quite good visualisations of what actually happens with activities and task stacks with different flags.)
Although as I reflect on this I'm not sure this situation does cover quite what I'm doing--I'd need to look closer to see what's happening.
-
In relation to the first issue, I looked to see if there was some way to replicate the post-2.3.x behaviour of the new flags. It would appear I'm not the first person to want to do this (I guess that's why they were added. :) ).
For example, this answer to "How to control activity stack/clear activity stack in Android" has a suggestion for implementing
FLAG_ACTIVITY_TASK_ON_HOMEby using cascadingonActivityResult()responses.(Hmmm, interesting, I've just noticed that there is actually an
IntentCompat.FLAG_ACTIVITY_TASK_ON_HOMEhelper in the v4 compatibility library. (There's also anIntentCompat.FLAG_ACTIVITY_CLEAR_TASKin it as well but it has the comment "This flag will only be obeyed on devices supporting API 11 or higher.") So, potentially I could look into that.In relation to implementing
FLAG_ACTIVITY_CLEAR_TASKbehaviour in pre API Level 11, here's a link dump of possible approaches to a solution:And in relation to activities, stacks and flag interaction:
I didn't try any of these approaches.
All of this talk of activities and stacks made me wonder if there was a tool for visualising the current stack and activity state. I had some vague recollection of a tool mentioned at the latest Google IO but didn't find anything related to that--if my memory was correct it might've been one of the still in development features.
Apparently the best you can do is the adb shell
dumpsyscommand (specificallydumpsys activity) as mentioned in this answer to "View the Task's activity stack". Even so it's not exactly a "visualisation". :)(There was a comment in that thread that mentioned a shell script someone had written to "List the activity stack for all tasks of the given package" but I haven't tried it.)
The
dumpsyscommand isn't particularly well documented but I found a few references to its functionality:Eventually I got to the stage where I decided to forget about going to network config screen when the accessory is disconnected and just return back to the home screen (or the previous application? However it worked... : ) ).
Once again (I think) this worked fine on Android 3.x+ but on 2.3.x... Oh, no.
So, the first time I connected the accessory everything would be hunky dory. But ~90% of the time, the second time you plugged in the accessory it wouldn't work. Looking at the logs there was an IO error related to
device not foundwhen the first handshake bytes were written to the output stream.
[TODO: Add actual error text.]
Of course, tantalisingly, very, very occasionally it would work a second time too but with no apparent rhyme of reason. sigh again
I do recall having a permissions issue when I didn't release the USB connection on a previous occasion in certain situations but I believe that occurred when trying to open the USB accessory device not when you tried to write to it.
The fact that on occasion it works makes me think there's some weirdness related to timing as a result of not handling something as I'm supposed to. Or that there's some weird bug due to the use of a service to do the USB handling.
If I "force stopped" the application between connections everything would work okay on re-connection which suggests there's some state that's not being handled correctly in terms of releasing resources or something.
The fact that even this bad behaviour only occurred with 2.3.x even though it was the same code when run on 3.x+ also makes me suspicious.
So, given that things "worked" once I force-stopped the application, I did what anybody would do in this situation...
...I decided to try to automatically kill the service when-ever the accessory was disconnected.
Not my finest hour of being a developer. :)
But...it worked! Sorta.
Sure enough, for multiple times (say, four to five) I could get the accessory to reconnect but then eventually it would stop working again until I force-closed that app.
It turned out it wasn't entirely straight-forward to send a kill signal from the activity that starts the USB service. I tried by put the killing in the
onStop()callback method of theActivity_SetupUsbclass but that didn't work (although (a) I didn't work out the best way to find the process ID in question; and, (b) it may have finished too early for it to happen the correct place). Anyway, this is what I tried:android.os.Process.killProcess(android.os.Process.getUidForName("com.handbagdevices.handbag:remote_usb_comms"));And from what I logged it seems like the
getUidForName()call returned-1so I could've been using it wrong.(This answer to "Android process killer" does seem to provide an approach for listing running services so might help. Also, see this answer to "Java Android Get Pid from Process Name, but dont know its full name".)
So, in the end I just got the service to cough kill itself with this code in the
cleanUp()method, which is called in the thread handling the comms:android.os.Process.killProcess(android.os.Process.myPid());Which probably does all sorts of terrible things--but it increases the number of times the reconnection works substantially!
(I didn't look into whether the
ActivityManager.killBackgroundProcesses()method might be another option but it's something to consider.)Of course, this kind of "solution" isn't really what you want in a production release so I obviously need to handle things in a different way.
I'm starting to think I'll change my approach of handling the USB connection. While I think it's best to have a separate activity started by the connection, I'm going to look at having a main communications method selection activity (which I planned to add anyway) and have that called by the "USB accessory connected" intent. Then in that activity I'll check if the intent was the USB-related one and, if so, start the USB setup activity.
Hopefully, by doing this I don't need to fluff around with the more convoluted change of task/activity stack shenanigans and that might improve things?
If not, I'll need to do some more investigation of what's happening...
It's just a bit irritating that it's only not working on 2.3.x.
I do admit that I was pretty much just shot-gunning possible fixes when I was looking at this--mainly because I was killing some time.
Debugging might be improved if I was using the over-the-net connection for ADB (since I can't use a USB one directly) but my past experience with that is that it is a bit of a pain with different behaviour on different Android versions (surprise!) as to whether or not even network ADB connections get disconnected when a USB accessory is connected. And I couldn't be bothered shaving that particular yak on this occasion--particularly when I still haven't actually gotten around to writing up that issue from when I first encountered it. :)
18 October 2012¶
Sigh
Well, I've been fighting with Android for two days and finally am back where I started but with a different implementation.
I just committed code that adds a launcher screen (just an extra button to trigger the network setup screen currently, but eventually it should be a tabbed interface) which works correctly on Honeycomb+ for reconnecting USB etc etc.
It also "works" on Gingerbread but only for the first USB connection due to the "no such device" issue mentioned above.
I don't particularly like the implementation because I ended up including a check for attached USB devices in the launcher which was supposed to have no comms-method specific code in it but because of the infuriating interaction of activity lifecycle, intents etc etc it was the most straight-forward way of getting it to work.
So, I guess that's something.
Along the way I also made use of the
FLAG_ACTIVITY_FORWARD_RESULTflag when starting an Activity. The documentation includes the following inscrutable description:If set and this intent is being used to launch a new activity from an existing one, then the reply target of the existing activity will be transfered to the new activity. This way the new activity can call setResult(int) and have that result sent back to the reply target of the original activity
Eventually I found an actually useful description with names for the activities being referenced which makes it far more obvious what's happening (see Section 3.4.1 of "The Android Intent Based APIs: Part Four – Activities And Intents" which I can't link to directly):
If Activity One starts Activity Two using the startActivityForResult() method and Activity Two then starts Activity Three, Activity Two can specify that the result from Activity Three be returned to Activity One rather to itself. This is done by setting the flag defined by the Intent class constant FLAG_ACTIVITY_FORWARD_RESULT in the Intent used to start the Activity.
I also found:
-
"How to return a result through multiple activities" which includes a pretty picture.
-
"Forwarding activity result to parent, with singleTop launch mode" which clarifies a situation when you shouldn't use the flag.
-
Oh, and I did get driven to try ADB over WiFi which as suspected was a complete failure.
Gingerbread failed to even connect.
Jelly Bean would connect but sigh disables USB accessory functionality when the TCP/IP ADB is active!!!! Way to defeat the purpose.
That seems even worse than I remember...
Along the way found "Issue 20241: Cannot get adb connecting over wifi" with almost no activity on it.
22 October 2012¶
It turns out I can get the "No such device" error on Honeycomb too:
10-22 23:54:30.492: W/System.err(13980): java.io.IOException: No such device 10-22 23:54:30.492: W/System.err(13980): at org.apache.harmony.luni.platform.OSFileSystem.write(Native Method) 10-22 23:54:30.492: W/System.err(13980): at dalvik.system.BlockGuard$WrappedFileSystem.write(BlockGuard.java:178) 10-22 23:54:30.492: W/System.err(13980): at java.io.FileOutputStream.write(FileOutputStream.java:214) 10-22 23:54:30.492: W/System.err(13980): at java.io.FileOutputStream.write(FileOutputStream.java:204) 10-22 23:54:30.492: W/System.err(13980): at java.io.DataOutputStream.writeBytes(DataOutputStream.java:156) 10-22 23:54:30.492: W/System.err(13980): at com.handbagdevices.handbag.CommsService_Usb$UsbConnection.doInBackground(CommsService_Usb.java:371) 10-22 23:54:30.492: W/System.err(13980): at com.handbagdevices.handbag.CommsService_Usb$UsbConnection.doInBackground(CommsService_Usb.java:1) 10-22 23:54:30.492: W/System.err(13980): at android.os.AsyncTask$2.call(AsyncTask.java:252) 10-22 23:54:30.492: W/System.err(13980): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) 10-22 23:54:30.492: W/System.err(13980): at java.util.concurrent.FutureTask.run(FutureTask.java:137) 10-22 23:54:30.492: W/System.err(13980): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1081) 10-22 23:54:30.492: W/System.err(13980): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:574) 10-22 23:54:30.492: W/System.err(13980): at java.lang.Thread.run(Thread.java:1028)Which is not a good thing.
My current workaround is in the
onDestroy()method of the service class:if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { android.os.Process.killProcess(android.os.Process.myPid()); }It seems as reliable as any other thing.
I'm not getting 100% reliable connections anyway--I'm thinking it's possibly device related. Should look at creating an ARM-based device for testing... cough
Nope. Now even the workaround has stopped working--all I did was commit the changes and add a "no animation" theme. sigh again
Gah, maybe it's less likely to work if the app hasn't been launched from the IDE???
Tried it with a SparkFun USB Host Shield rather than a Freetronics USBDroid and eventually it behaved as badly.
Was even having major problems with the device not being recognised on the Nexus S ICS but I'm thinking that's the faulty socket coming back to haunt me...
(On the plus side, when it does work having the "no animation" theme does eliminate all the slides/flickers from switching between multiple activities.
Could probably get away with only applying it to specific activities.
I used this approach from an answer to "switching activities without animation".
For using flags instead, see:
-
"Start a new activity with no transition animation in android 1.6"
-
"Android, how to disable the 'wipe' effect when starting a new activity?"
-
"Disable activity slide-in animation when launching new activity?"
The Styles and Themes section of the Android docs describe what file to create in order to describe the theme and how to set it up. )
24 October 2012¶
I briefly looked into using
gdbfor debugging after a suggestion but stopped because the pre-builtgdbrequired OS X 10.6 or above.These links got me started:
-
Android gdbclient command I needed to do:
./adb push <path>/android-ndk-r8b/prebuilt/android-arm/gdbserver/gdbserver /data/local ./adb forward tcp:5055 tcp:5055 ./adb shell run-as com.handbagdevices.handbag /data/local/gdbserver :5055 --attach <pid>
The
run-asusage I got from "NDK debugging without root access" which suggested as a solution to theCannot attach to lwp 15382: Operation not permitted (1)error.Even if I had got
gdbrunning using it probably wouldn't have helped much without more mucking around to deal with the spot where I hit native code in the OS. (Most guides to usinggdband Android seem to assume you're writing some C/C++ not debugging a Java app that ends up in native code.)