<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8214401912480503366</id><updated>2012-01-28T18:51:18.826+01:00</updated><category term='instrumentation'/><category term='chart library'/><category term='invoke-virtual-quick'/><category term='meetup'/><category term='tools'/><category term='junit'/><category term='tablelayout'/><category term='UI'/><category term='assembler'/><category term='cwac'/><category term='android ui development'/><category term='imagebutton'/><category term='programmatic layout'/><category term='location'/><category term='push'/><category term='resources'/><category term='acceleration sensor'/><category term='android market'/><category term='expandable list'/><category term='assets'/><category term='spinner'/><category term='expandable'/><category term='aicharts'/><category term='checkbox'/><category term='rpc'/><category term='background sampling'/><category term='theme'/><category term='synchronization'/><category term='link list'/><category term='separation'/><category term='XML'/><category term='hello android'/><category term='livefolder'/><category term='philosophy'/><category term='service interfaces'/><category term='android'/><category term='annotation'/><category term='styles'/><category term='mac'/><category term='book review'/><category term='optimization'/><category term='accelerometer'/><category term='dex jar libs'/><category term='middleware'/><category term='plugins'/><category term='widget'/><category term='json'/><category term='c2dm'/><category term='aidl'/><category term='gyroscope'/><category term='list'/><category term='class loader'/><category term='droidcon 2011'/><category term='remoteviews'/><category term='XML layout'/><category term='unlocking android'/><category term='empty groups'/><category term='app widgets'/><category term='Dalvik'/><category term='key press'/><category term='unit test'/><category term='sensors'/><category term='sdk'/><category term='animation'/><category term='motion recognition'/><category term='foreca weather'/><category term='services'/><category term='dedexer'/><category term='opcodes'/><category term='focus'/><category term='hack'/><category term='android video tutorial'/><category term='feed'/><category term='intent'/><category term='views'/><category term='softpedia'/><category term='disassembler'/><category term='simple'/><category term='wavelet'/><category term='button'/><category term='dex'/><category term='widgets'/><category term='bluetooth'/><category term='content providers'/><category term='battery cost'/><category term='londroid'/><category term='baksmali'/><category term='telephony'/><category term='odex'/><category term='adapter'/><category term='sensor'/><category term='smali'/><category term='command line'/><category term='iwfar'/><category term='asynchronous communication'/><category term='simpleadapter'/><category term='oneway'/><title type='text'>My life with Android :-)</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>88</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-835406612154756375</id><published>2012-01-25T21:38:00.001+01:00</published><updated>2012-01-25T21:41:15.237+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gyroscope'/><category scheme='http://www.blogger.com/atom/ns#' term='accelerometer'/><title type='text'>Compensating accelerometer data with the gyroscope</title><content type='html'>&lt;a href="http://www.sfonge.com/forum/topic/measuring-movement-accelerometer-and-gyroscope"&gt;In the previous post &lt;/a&gt;we  have seen, how we can simulate the rotations of the gravity vector  (thus measuring the exact tilt) with the help of the gyroscope. During  that measurement we moved the device only slowly to validate the claim  that the gyroscope is able to track the gravity vector for a certain  period of time. We were aware of the fact that measurement errors for  this type of measurement will eventually accumulate and therefore we  have to pick the correct gravity vector time to time.&lt;br /&gt;&lt;br /&gt;In this  post we go one step further. We will use the gyro-based simulated  gravity vector only if the accelerometer does not provide us with  reliable gravity vector measurement (because the device is subject to  motion acceleration too). But how to figure out if the gravity  measurement of the accelerometer is reliable or not? Let's see the  picture below which is shows the absolute value (the length) of the  accelerometer's output vector as a function of sample count when the  device is subject to a "tennis-like" movement. This means that the  device is held in one hand and I simulated as if it was a tennis  racquet. The device rotates but is also subject to a significant motion  acceleration.&lt;br /&gt;&lt;br /&gt;&lt;a style="font-weight: bold;" href="http://www.sfonge.com/forum/topic/compensating-accelerometer-data-gyroscope"&gt;Click here to read the post further.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-835406612154756375?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/835406612154756375/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=835406612154756375' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/835406612154756375'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/835406612154756375'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2012/01/compensating-accelerometer-data-with.html' title='Compensating accelerometer data with the gyroscope'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-8363726132364972601</id><published>2012-01-21T01:00:00.002+01:00</published><updated>2012-01-21T01:03:24.260+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gyroscope'/><category scheme='http://www.blogger.com/atom/ns#' term='accelerometer'/><category scheme='http://www.blogger.com/atom/ns#' term='sensor'/><title type='text'>Measuring movement with accelerometer and gyroscope</title><content type='html'>Santa Claus brought me a present and that sadly means retiring &lt;a href="http://mylifewithandroid.blogspot.com/2010/03/progressively-loading-listviews.html"&gt;of my trusty Nexus One.&lt;/a&gt; Not that the phone has any problem - it still functions perfectly. As Google does not update the Nexus One anymore with new software release, I had to change. And the winner is - well, not the Galaxy Nexus, that's too expensive. I chose a Nexus S because of its attractive price, its  update path toward Android 4.x (it is actually the cheapest option today of an Android 4 phone) and its built-in gyroscope.&lt;br /&gt;&lt;br /&gt;I wanted to put my hand on a gyroscope-equipped phone for a long time. I discussed in length the problems of using only the accelerometer when identifying movements &lt;a href="http://mylifewithandroid.blogspot.com/2011/10/my-presentation-about-motion.html"&gt;in my Droidcon 2011 presentation&lt;/a&gt; and I hinted that additional sensors could be used to compensate for the motion acceleration that is added to the gravity acceleration and is impossible to separate in the general case. That's what I am aiming to do with the gyroscope in this series of posts.&lt;br /&gt;&lt;br /&gt;&lt;a style="font-weight: bold;" href="http://www.sfonge.com/forum/topic/measuring-movement-accelerometer-and-gyroscope"&gt;Click here to read the post further.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-8363726132364972601?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/8363726132364972601/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=8363726132364972601' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8363726132364972601'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8363726132364972601'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2012/01/measuring-movement-with-accelerometer.html' title='Measuring movement with accelerometer and gyroscope'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4313642736106085728</id><published>2011-10-22T22:02:00.003+02:00</published><updated>2011-10-22T23:06:03.094+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='acceleration sensor'/><category scheme='http://www.blogger.com/atom/ns#' term='battery cost'/><title type='text'>Workaround for minimizing sensor sampling battery cost</title><content type='html'>In my &lt;a href="http://mylifewithandroid.blogspot.com/2011/10/my-presentation-about-motion.html"&gt;Droidcon 2011 presentation&lt;/a&gt; I tried to highlight the battery cost of the continuous sensor sampling which is necessary for detecting motion patterns. While the general case still requires some sort of improvement over the current Android sensor architecture e.g. &lt;a href="http://mylifewithandroid.blogspot.com/2011/10/battery-cost-of-sensor-sampling.html"&gt;the use of the "wake on motion" feature of the acceleration sensors&lt;/a&gt;, it is possible to decrease the battery consumption if the motion to be detected is longer than 5-10 seconds. This is still not suitable for recognising very short events like fall, tap, shake, etc. but can be suitable to "wake up" a step counter when the motion starts. Some steps would be missed but this may be acceptable if the battery life is increased.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/alarmsleep.zip"&gt;Click here to download the example program&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The essence of the workaround is to use the AlarmManager and to generate periodic wakeup events. When the wakeup event is received, sensor sampling is started and some samples are collected. Then we have to figure out using some heuristics whether there is some movement. This implementation calculates the average value of the acceleration sample absolute values and looks for values deviating from the average. If there is movement detected, the sampling continues. If there is no movement detected, the program gives up the wake lock and goes back sleeping.-&lt;br /&gt;&lt;br /&gt;I have made some measurements using my Nexus 1. I switched the phone into airplane mode and ran the program during the night, for 7-8 hours. The battery percentage consumed during an hour can be seen below as a function of wakeup period (check out WAKEUP_PERIOD constant in AlarmSleep.java).&lt;br /&gt;&lt;br /&gt;5 sec - 1.14%/hour&lt;br /&gt;10 sec - 1.10%/hour&lt;br /&gt;20 sec - 0.56%/hour&lt;br /&gt;30 sec - 0.27%/hour&lt;br /&gt;&lt;br /&gt;The battery consumption with the last timeout value - 30 sec - is very close to the standby consumption, 0.25%. If you can tolerate that long "dead period", then you can bring the battery consumption in "no motion" state very close to the phone's normal standby consumption. For a general "tap" or "shake" detector, however, this is not an adequate solution. I have received encouraging mails that a proper solution relying on the sensor's low-power mode may be deployed soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4313642736106085728?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4313642736106085728/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4313642736106085728' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4313642736106085728'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4313642736106085728'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/10/workaround-for-minimizing-sensor.html' title='Workaround for minimizing sensor sampling battery cost'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-1203653155642921460</id><published>2011-10-11T20:20:00.008+02:00</published><updated>2011-10-13T05:54:00.750+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='foreca weather'/><category scheme='http://www.blogger.com/atom/ns#' term='android market'/><title type='text'>Foreca Weather released</title><content type='html'>Let me advertise an application that has just appeared on Android Market.&lt;br /&gt;&lt;br /&gt;Why? Because I was the Android programmer on this project working in cooperation with the designers of &lt;a href="http://www.foreca.com/"&gt;Foreca&lt;/a&gt;, a Finland-based weather forecast company. As you may remember, I am aesthetically challenged so the good looks of this application are all due to the thorough polishing of the Foreca guys. Foreca's vast weather network makes this application handy anywhere in the world. Weather stations can be selected by incrementally searching list or from a map, the application even finds the current location. Locations can be favourited and Foreca offers 10 days of detailed forecast. There is a widget too.&lt;br /&gt;&lt;br /&gt;Click on the button and go to Android Market to fetch it!&lt;br /&gt;&lt;a href="http://market.android.com/details?id=com.foreca.android.weather"&gt;&lt;br /&gt;&lt;img src="http://www.android.com/images/brand/60_avail_market_logo2.png" alt="Available in Android Market" /&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Some screenshots.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-3-OKW4NbIRs/TpSLy_frwmI/AAAAAAAABoA/ok5PCms8T0k/s1600/fw_homescreen.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 192px; height: 320px;" src="http://1.bp.blogspot.com/-3-OKW4NbIRs/TpSLy_frwmI/AAAAAAAABoA/ok5PCms8T0k/s320/fw_homescreen.png" alt="" id="BLOGGER_PHOTO_ID_5662304339665601122" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-yLGzlFVFfoA/TpSL77uSKAI/AAAAAAAABoM/VmQcgpx9hTI/s1600/fw_map.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 192px; height: 320px;" src="http://4.bp.blogspot.com/-yLGzlFVFfoA/TpSL77uSKAI/AAAAAAAABoM/VmQcgpx9hTI/s320/fw_map.png" alt="" id="BLOGGER_PHOTO_ID_5662304493271918594" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-zpHhhcL0j3I/TpSMCWke8CI/AAAAAAAABoY/AYgkDXLx170/s1600/fw_search.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 192px; height: 320px;" src="http://2.bp.blogspot.com/-zpHhhcL0j3I/TpSMCWke8CI/AAAAAAAABoY/AYgkDXLx170/s320/fw_search.png" alt="" id="BLOGGER_PHOTO_ID_5662304603557785634" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-N8T4C7jPsRk/TpSMKpad9pI/AAAAAAAABok/yu11q1IJSwk/s1600/fw_fav_small.png"&gt;&lt;br /&gt;&lt;/a&gt;&lt;a href="http://3.bp.blogspot.com/-c-eFghRRYqo/TpSMrDcAMKI/AAAAAAAABow/9rwq9awJ2TI/s1600/fw_favouries.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 192px; height: 320px;" src="http://3.bp.blogspot.com/-c-eFghRRYqo/TpSMrDcAMKI/AAAAAAAABow/9rwq9awJ2TI/s320/fw_favouries.png" alt="" id="BLOGGER_PHOTO_ID_5662305302796578978" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-1203653155642921460?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/1203653155642921460/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=1203653155642921460' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1203653155642921460'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1203653155642921460'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/10/foreca-weather-released.html' title='Foreca Weather released'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-3-OKW4NbIRs/TpSLy_frwmI/AAAAAAAABoA/ok5PCms8T0k/s72-c/fw_homescreen.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4415062594014202019</id><published>2011-10-10T08:56:00.002+02:00</published><updated>2011-10-10T09:02:34.271+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='acceleration sensor'/><category scheme='http://www.blogger.com/atom/ns#' term='background sampling'/><category scheme='http://www.blogger.com/atom/ns#' term='battery cost'/><title type='text'>Battery cost of sensor sampling</title><content type='html'>While at &lt;a href="http://uk.droidcon.com/"&gt;Droidcon UK 2011&lt;/a&gt;, I was asked to elaborate my claims about the battery cost of sensor sampling in a blog post. These claims can be found &lt;a href="http://mylifewithandroid.blogspot.com/2011/10/my-presentation-about-motion.html"&gt;in my conference presentation&lt;/a&gt; but we thought it would help to describe them more in detail.&lt;br /&gt;&lt;br /&gt;Continuous accelerometer sensor sampling introduces significant battery cost in Android devices. For example if you want to write an application that samples the sensor in the background and figures out, whether somebody double-tapped the body of the phone (not the active touch screen but anywhere on the phone's body), then the CPU of the phone can never sleep. You need to grab a partial wake lock to ensure continuous sampling otherwise the processing of the samples will stop when the device goes to sleep - typically some minutes after the keyguard activates. If you obtain partial wake lock, however, then you have to calculate with 1.5-4% battery consumption per hour (depending on sampling speed) which does not look like a lot but if you multiply it with 24 hours, you can see that you cannot sample the accelerometer continuously without spoiling the phone's usability.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://research.microsoft.com/pubs/120316/littlerock_tr.pdf"&gt;Microsoft proposes a low-power co-processor&lt;/a&gt; for these background processing jobs with low computational complexity (accelerometer is typically sampled around 10-30 samples per second - you don't need a supercomputer to do that kind of processing). While this approach definitely solves the battery problem, there is the issue of an additional programming model (those low-power microcontrollers don't have full-blown programming environments) and it is very likely that application programmers will not be able to insert pieces of code to run on this microcontroller.&lt;br /&gt;&lt;br /&gt;My proposal is to exploit low-power features of the accelerometer sensors widely used in Android devices. For example the very popular Bosch Sensortec BMA150 accelerometer sensor which can be found in variety of HTC devices (and probably others) has a wake up on motion mode.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.bosch-sensortec.com/content/language1/downloads/BST-BMA150-DS000-07.pdf"&gt;In its data sheet&lt;/a&gt;, this mode is described like the following.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;In general BMA150 is attributed to low power applications and can contribute to the system power management.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul style="font-style: italic;"&gt;&lt;li&gt;    Current consumption 200μA operational&lt;/li&gt;&lt;li&gt;    Current consumption 1μA sleep mode&lt;/li&gt;&lt;li&gt;    Wake-up time 1ms&lt;/li&gt;&lt;li&gt;    Start-up time 3ms&lt;/li&gt;&lt;li&gt;    Data ready indicator to reduce unnecessary interface communication&lt;/li&gt;&lt;li&gt;    Wake-up mode to trigger a system wake-up (interrupt output when motion detected Low current consumption in wake-up mode to master)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;The BMA150 provides the possibility to wake up a system master when specific acceleration values are detected. Therefore the BMA150 stays in an ultra low power mode and periodically evaluates the acceleration data with respect to interrupt criteria defined by the user. An interrupt output can be generated and trigger the system master. The wake-up mode is used for ultra-low power applications where inertial factors can be an indicator to change the activity mode of the system.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This would allow the main CPU to go into sleep mode and to be woken up by the sensor only if there are movements. So if the device is laying on the table, there would be basically no power consumption due to sensor sampling. This would enable production-quality implementation of a range of applications, for example the &lt;a href="http://www.qol.unige.ch/research/ALE.html"&gt;Activity Level Estimator&lt;/a&gt; which is being researched at the University of Geneva.&lt;br /&gt;&lt;br /&gt;The attractive property of this approach is that even though implementing it in Android devices and in the framework is not trivial, it is not very complicated either. The hardware is already in the devices, maybe the sensor's interrupt pin has to be wired up with the main processor. SensorManager needs to be extended with some functions that allows applications to activate this wake up on motion feature. Application model would remain consistent with the current Android application model, no need to fiddle with low-level microcontroller code.&lt;br /&gt;&lt;br /&gt;Now there just need to be a device manufacturer that carries this through.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4415062594014202019?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4415062594014202019/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4415062594014202019' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4415062594014202019'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4415062594014202019'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/10/battery-cost-of-sensor-sampling.html' title='Battery cost of sensor sampling'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3914573831975459079</id><published>2011-10-07T18:11:00.002+02:00</published><updated>2011-10-07T18:17:35.368+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='droidcon 2011'/><category scheme='http://www.blogger.com/atom/ns#' term='motion recognition'/><category scheme='http://www.blogger.com/atom/ns#' term='android'/><title type='text'>My presentation about motion recognition at Droidcon 2011</title><content type='html'>I finished on time!&lt;br /&gt;&lt;br /&gt;Per popular demand, here is the presentation.&lt;br /&gt;&lt;br /&gt;&lt;div style="width:425px" id="__ss_9596036"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/paller/motion-recognition-with-android-devices" title="Motion recognition with Android devices"&gt;Motion recognition with Android devices&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse9596036" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=motionrecognition-111007111503-phpapp02&amp;stripped_title=motion-recognition-with-android-devices&amp;userName=paller" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse9596036" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=motionrecognition-111007111503-phpapp02&amp;stripped_title=motion-recognition-with-android-devices&amp;userName=paller" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/paller"&gt;Gabor Paller&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3914573831975459079?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3914573831975459079/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3914573831975459079' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3914573831975459079'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3914573831975459079'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/10/my-presentation-about-motion.html' title='My presentation about motion recognition at Droidcon 2011'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-810244253704856809</id><published>2011-09-02T17:56:00.004+02:00</published><updated>2011-09-02T18:20:25.532+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='droidcon 2011'/><category scheme='http://www.blogger.com/atom/ns#' term='motion recognition'/><title type='text'>Returning to Droidcon London</title><content type='html'>It was almost 2 years ago that I presented my beloved garage project,&lt;a href="http://mylifewithandroid.blogspot.com/2009/12/understanding-dalvik-bytecode-with.html"&gt; Dedexer at Droidcon 2009&lt;/a&gt;. A lot of things has happened since. First and foremost I returned to my home country, Hungary. Those Londroid meetups and Droidcon are not a Tube-ride away anymore. Then there was the step count cooperation with the University of Geneva folks that resulted&lt;a href="http://mylifewithandroid.blogspot.com/2011/05/workshop-paper.html"&gt; in a fine paper&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In short, it is just about time to return to Droidcon.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://uk.droidcon.com/sites/default/files/droid.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 640px; height: 150px;" src="http://uk.droidcon.com/sites/default/files/droid.gif" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;My presentation will be a &lt;a href="http://uk.droidcon.com/content/programme-0#paller-motion"&gt;developer-centric introduction to the world of accelerometer signal processing&lt;/a&gt; on Android devices, representing my second employer,&lt;a href="http://www.sfonge.com/"&gt; Sfonge Ltd&lt;/a&gt;. Have you ever thought about introducing features into your apps that can be activated by body movements? Maybe you haven't so it is about time you start thinking about it. Let's meet at Droidcon 2011 and I will explain to you, how simple it is to add such a feature.&lt;br /&gt;&lt;br /&gt;Well, that was a lie. Motion recognition is not trivial but exactly that's why it is worth learning about it.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-810244253704856809?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/810244253704856809/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=810244253704856809' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/810244253704856809'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/810244253704856809'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/09/returning-to-droidcon-london.html' title='Returning to Droidcon London'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-6849360822400468410</id><published>2011-08-19T21:30:00.001+02:00</published><updated>2011-08-19T21:33:39.464+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='imagebutton'/><category scheme='http://www.blogger.com/atom/ns#' term='focus'/><category scheme='http://www.blogger.com/atom/ns#' term='list'/><title type='text'>Focus problems with list rows and ImageButtons</title><content type='html'>There has been lot of discussions on this blog about some widgets that steal focus from others - creating behaviour that sounds completely illogical for the uninitiated. So far we have seen how &lt;a href="http://mylifewithandroid.blogspot.com/2009/10/lists-and-focuses.html"&gt;Buttons steal the focus from list rows&lt;/a&gt;. The same monster rears its ugly head in this slightly different example. Here we have a simple ListView with list rows. Each list row has a text widget and an ImageButton in it. Now if we code this example innocently without being aware of the tricky focus-stealing effect of ImageButton, list rows cannot be selected.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/listimagebutton.zip"&gt;Click here to download the example program.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/--I1dX7wGNp4/Tk66SNdz2wI/AAAAAAAABn4/gyVmTEqX4yo/s1600/listimagebutton.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 183px;" src="http://4.bp.blogspot.com/--I1dX7wGNp4/Tk66SNdz2wI/AAAAAAAABn4/gyVmTEqX4yo/s320/listimagebutton.png" alt="" id="BLOGGER_PHOTO_ID_5642652205156915970" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The example program as it is works nicely. If you click the row, you will get one sort of Toast message. If you click the ImageButton within the row, you will get another Toast message. Now let's observe this XML attribute in res/layout/item.xml:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;android:descendantFocusability="blocksDescendants"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Try removing this attribute and notice, that selecting list items does not work anymore. What happened? &lt;a href="http://mylifewithandroid.blogspot.com/2009/10/lists-and-focuses.html"&gt;As explained previously&lt;/a&gt;, ImageButton steals the focus from the view group (the list row) it is in. This means that when you click the list row, it is always the ImageButton that captures the event but as it is outside of its screen area, the event is discarded. Therefore we prevented the ImageButton to capture the focus.&lt;br /&gt;&lt;br /&gt;Previously I recommended preventing the capture of the focus by making the button non-focusable (android:focusable="false"). Curiously, it does not work in this example so I had to find another way. That's why I shared this example.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-6849360822400468410?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/6849360822400468410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=6849360822400468410' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6849360822400468410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6849360822400468410'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/08/focus-problems-with-list-rows-and.html' title='Focus problems with list rows and ImageButtons'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/--I1dX7wGNp4/Tk66SNdz2wI/AAAAAAAABn4/gyVmTEqX4yo/s72-c/listimagebutton.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-8954715756743733762</id><published>2011-07-06T19:08:00.003+02:00</published><updated>2011-07-06T19:33:04.943+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='book review'/><category scheme='http://www.blogger.com/atom/ns#' term='unit test'/><title type='text'>Book on Android testing</title><content type='html'>Oh, those were the days, when Android was pre-1.0! Documentation and training material was scarce, platform sources were not yet released, no device in sight. It was then when Diego Torres Milano &lt;a href="http://dtmilano.blogspot.com/2008/01/test-driven-development-and-gui-testing.html"&gt;first published a post about unit testing on Android&lt;/a&gt;. 3 short years later Android is the dominant smartphone platform but there has been no book dedicated to Android testing. Who else could have written that book than Diego?&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-vvN4AKLjKIo/ThSYflZDLTI/AAAAAAAABnw/fFW6JbHz2U8/s1600/androidapplicationtestingguide.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 259px; height: 320px;" src="http://4.bp.blogspot.com/-vvN4AKLjKIo/ThSYflZDLTI/AAAAAAAABnw/fFW6JbHz2U8/s320/androidapplicationtestingguide.jpg" alt="" id="BLOGGER_PHOTO_ID_5626289502873136434" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;a href="http://www.packtpub.com/android-application-testing-guide/book"&gt;Android Application Testing Guide from Packt Publishing&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Let's start with a disclaimer: I was a reviewer of this book. I almost met Diego once when we both presented at &lt;a href="http://skillsmatter.com/event/os-mobile-server/droidcon-london-2009"&gt;Droidcon London 2009&lt;/a&gt; but I had to leave early and we didn't. Believe or not, at the end of 2009 Android enthusiasts could still fit into a medium-sized presentation hall.&lt;br /&gt;&lt;br /&gt;The book starts with a general introduction on testing then it starts to focus on Android testing. Android has JUnit integrated but the Android-specific classes are not over-documented. The Android test case classes are explained one by one with examples then we learn, how the principles of Test Driven Development can be applied to our Android projects. The second half of the book deals with useful techniques like automatic test execution, ways of performance and coverage testing and different tools for Behaviour Driven Development and Continuous Integration.&lt;br /&gt;&lt;br /&gt;All in all, there is plenty of information in this book that is difficult to find or figure out. I can warmly recommend this book to everyone who builds production-quality software for the Android platform.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-8954715756743733762?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/8954715756743733762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=8954715756743733762' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8954715756743733762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8954715756743733762'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/07/book-on-android-testing.html' title='Book on Android testing'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-vvN4AKLjKIo/ThSYflZDLTI/AAAAAAAABnw/fFW6JbHz2U8/s72-c/androidapplicationtestingguide.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7701402360206248253</id><published>2011-06-09T22:08:00.005+02:00</published><updated>2011-06-09T22:32:01.281+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='empty groups'/><category scheme='http://www.blogger.com/atom/ns#' term='expandable list'/><title type='text'>Hiding group indicator for empty groups</title><content type='html'>Another topic that keeps returning on this blog is the expandable list view. It seems those troublesome expandable lists always have some surprises in store. This time I ran into the issue of group indicators and empty groups.&lt;br /&gt;&lt;br /&gt;Group indicators are those small arrows in front of the group rows that indicate, whether the group is expanded or collapsed. Trouble is that those indicators are always visible, even if the group is empty. This misleads users who think that they can expand the group. The group indeed expands but as it is empty, no child rows appear which can be extremely annoying.&lt;br /&gt;&lt;br /&gt;Some internet sources propose&lt;a href="http://stackoverflow.com/questions/4145090/expandablelistview-hide-indicator-for-groups-with-no-children"&gt; wizardry with the variable drawable&lt;/a&gt; that backs the group indicator. If you follow the advice, the group indicator will disappear for all the collapsed groups (even for those which have child rows). This surprising behaviour is by design. Comment in the source of ExpandableListView says: "&lt;span style="font-style: italic;"&gt;[if] the group is collapsed so we consider it empty for performance reasons&lt;/span&gt;". The method where this behaviour is wired in is private, it is not possible to overload it by subclassing ExpandableListView.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/explistemptygroup.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Fortunately the solution is very simple, even if a bit tricky. We will completely disable the group indicator logic in ExpandableListView and provide our own indicator from the adapter backing the expandable list. The group indicator is disabled by setting it to a transparent color in main.xml.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;android:groupIndicator="@android:color/transparent"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Then we override getGroupView in the adapter (this time it was a SimpleExpandableListAdapter but the method works for any adapter). We had to manually add an ImageView to the group layout - this is normally done automagically by ExpandableListView but we have just disabled that functionality. Once getGroupView returns the group row, we post-manipulate the ImageView that acts as group indicator considering the child count for the group beside its expanded/collapsed state. This unfortunately means that you have to have custom expanded/collapsed icons in your program - those icons are extremely ugly in the example program, I leave it to you as a homework to beautify them.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-Vkt6dMeda54/TfEtTL8NyyI/AAAAAAAABng/PiDijiGINnA/s1600/expemptygroup.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 215px;" src="http://1.bp.blogspot.com/-Vkt6dMeda54/TfEtTL8NyyI/AAAAAAAABng/PiDijiGINnA/s320/expemptygroup.png" alt="" id="BLOGGER_PHOTO_ID_5616320017953835810" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7701402360206248253?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7701402360206248253/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7701402360206248253' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7701402360206248253'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7701402360206248253'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/06/hiding-group-indicator-for-empty-groups.html' title='Hiding group indicator for empty groups'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-Vkt6dMeda54/TfEtTL8NyyI/AAAAAAAABng/PiDijiGINnA/s72-c/expemptygroup.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-1885679796479531655</id><published>2011-05-29T22:47:00.004+02:00</published><updated>2011-06-13T00:36:15.987+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iwfar'/><category scheme='http://www.blogger.com/atom/ns#' term='acceleration sensor'/><title type='text'>Workshop paper</title><content type='html'>Last year I blogged about my experiences with acceleration signal processing (&lt;a href="http://mylifewithandroid.blogspot.com/2010/06/shake-walk-run.html"&gt;here&lt;/a&gt; , &lt;a href="http://mylifewithandroid.blogspot.com/2010/05/analysing-acceleration-sensor-data-with.html"&gt;here&lt;/a&gt; and &lt;a href="http://mylifewithandroid.blogspot.com/2010/05/movement-patterns.html"&gt;here&lt;/a&gt;) then the topic disappeared from this blog. Disappeared from the blog but not from my life because the research continued with some partners from the University of Geneva. The &lt;a href="http://www.sfonge.com/epaper/using-digital-phase-locked-loop-pll-technique-assessment-periodic-body-movement-patterns-mobi"&gt;paper&lt;/a&gt; (free registration is needed for access) will be presented at the &lt;a href="http://di.ncl.ac.uk/iwfar/"&gt;1st International Workshop on Frontiers in Activity Recognition using Pervasive Sensing&lt;/a&gt;, a workshop of Pervasive 2011. The prototype was created on Android,&lt;a href="http://pallergabor.uw.hu/androidblog/steps.zip"&gt; you can access it here&lt;/a&gt;. Be warned: this is just a research prototype and is not guaranteed to work on anything else than on Nexus One and even on that phone it has some issues. If you happen to be on Pervasive 2011, I am happy to explain its operation personally.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update:&lt;/span&gt; the workshop presentation has been done, the slideset is also available at the same link where the paper is.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-1885679796479531655?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/1885679796479531655/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=1885679796479531655' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1885679796479531655'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1885679796479531655'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/05/workshop-paper.html' title='Workshop paper'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-1377539538083889533</id><published>2011-04-29T23:02:00.002+02:00</published><updated>2011-04-29T23:05:25.816+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='android ui development'/><category scheme='http://www.blogger.com/atom/ns#' term='book review'/><title type='text'>Book on Android UI development</title><content type='html'>Packt Publishing has just released this book about &lt;a href="https://www.packtpub.com/android-user-interface-development-beginners-guide/book"&gt;Android User Interface Development from Jason Morris&lt;/a&gt; and I got a mail from them whether I could write something about the book in this blog. Why not, responded I and there came an e-book. Of course, I had a hidden agenda too. I am aesthetically challenged, so to say and therefore UI design is not really my strength. I intended to learn from a master.&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-XplKvedQ00g/Tbsn5hEWJOI/AAAAAAAABnI/PHTC34AnsdY/s1600/androidui.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 248px; height: 320px;" src="http://2.bp.blogspot.com/-XplKvedQ00g/Tbsn5hEWJOI/AAAAAAAABnI/PHTC34AnsdY/s320/androidui.jpg" alt="" id="BLOGGER_PHOTO_ID_5601114430647968994" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;First and foremost, as the subtitle says, this book is for beginners and the author takes this mission seriously. The book follows a rigorous, step-by-step methodology. The reader is expected to start from an empty project and extend it line by line while the book explains, what is going on. It is best to demonstrate with an excerpt:&lt;br /&gt;&lt;br /&gt;&lt;ol style="font-style: italic;"&gt;&lt;li&gt;Start by creating an empty Button element below our answers ViewGroup LinearLayout (but still within the root LinearLayout element). Assign itthe ID skip, so that we can reference it in Java:&lt;br /&gt;   &lt;span style="font-family:Courier New,Courier,monospace;"&gt;android:id="@+id/skip"&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Create some padding between the answers and the new button by using a margin:&lt;br /&gt;   &lt;span style="font-family:Courier New,Courier,monospace;"&gt;android:layout_marginTop="12sp"&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Give it the display label Skip Question:&lt;br /&gt;   &lt;span style="font-family:Courier New,Courier,monospace;"&gt;android:text="Skip Question"&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Like all of the previous widgets, the width should be fill_parent and the height should be wrap_content: &lt;span style="font-family:Courier New,Courier,monospace;"&gt;android:layout_width="fill_parent"&lt;/span&gt;      &lt;span style="font-family:Courier New,Courier,monospace;"&gt;android:layout_height="wrap_content"&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt; And so on, and so on. There are books and trainings that call themselves hands-on but this book works really the best if you type the code while you are reading. Using this style, the book gets surprisingly far, starting from the simplest Hello, World type of applications it discusses lists and their relations to data, widgets (even the most exotic ones), layouts, including custom layouts and finally animations. Meanwhile it never gives up its practical approach, e.g. it teaches you, how to create images with transparent backgrounds with Gimp. I really appreciated that bit because I know from my own experience that this detail can be hard to figure out.&lt;br /&gt;&lt;br /&gt;The author made some decisions that I found a bit strange. First and foremost, even though the title is Android User Interface Development, it does not discuss 2D (Canvas and co.) and 3D (OpenGL) at all. I am sure that some readers will miss these parts. I am big fan of command-line development environments but maybe Eclipse is better for beginners.&lt;br /&gt;&lt;br /&gt;All in all, I enjoyed this book and I even learnt from it. The book does not cover all the aspects of Android development but it addresses a very important application type. It is a good place to start with Android development.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-1377539538083889533?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/1377539538083889533/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=1377539538083889533' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1377539538083889533'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1377539538083889533'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/04/book-on-android-ui-development.html' title='Book on Android UI development'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-XplKvedQ00g/Tbsn5hEWJOI/AAAAAAAABnI/PHTC34AnsdY/s72-c/androidui.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4005437048709263469</id><published>2011-02-22T15:27:00.002+01:00</published><updated>2011-02-22T15:30:04.643+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='expandable list'/><title type='text'>3-level expandable lists</title><content type='html'>I got a question in a comment whether 3-level expandable list would be possible. I responded that the ExpandableListView implementation is wired to have two levels but then another comment mentioned that it may be possible by embedding one ExpandableListView into another. I was busy then but now I found time to take the challenge and to implement a test program.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/expandablelist3.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Our three-level list is a variation of an &lt;a href="http://mylifewithandroid.blogspot.com/2008/05/expandable-lists.html"&gt;earlier example program&lt;/a&gt; but I inserted another level (somewhat without reason :-)).&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-tF39Q4p797s/TWPIVdYWYPI/AAAAAAAABnA/CaY4d3SA-LM/s1600/level3list.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 244px; height: 320px;" src="http://1.bp.blogspot.com/-tF39Q4p797s/TWPIVdYWYPI/AAAAAAAABnA/CaY4d3SA-LM/s320/level3list.png" alt="" id="BLOGGER_PHOTO_ID_5576521034604372210" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The principle of the implementation is simple. The first-level list is backed by an &lt;a href="http://mylifewithandroid.blogspot.com/2010/12/expandable-list-and-checkboxes.html"&gt;ExpandableListAdapter&lt;/a&gt;. The child views generated by this adapter are ExpandableListAdapters themselves that provide the second-level groups and the eventual child elements. Listeners are set for the group events (expand/collapse) of the second-level lists. When a group event occurs on a second-level list, the layout of the first-level list is also recalculated to accomodate for the size changes of second-level expandable lists.&lt;br /&gt;&lt;br /&gt;This sounds simple but there are some tricky points that made me spent more time on this prototype than I expected. The first issue is with the individual rows of the second-level lists and the view recycler. Check out getChildView() in ColorExpListAdapter.java. When the layout of the list is recalculated, the old views are offered to the list adapter for reuse. The getChildView() method gets the old view instance in the convertView parameter. If convertView is not null, the adapter has the option of reusing the old view instance. In this case the adapter does not instantiate a new view but sets the old view instance appropriately, decreasing garbage (&lt;a href="http://pallergabor.uw.hu/common/mac_increasing_java.html"&gt;Click here&lt;/a&gt; if you want to read more about the importance of less garbage collection in mobile environments). Now our problem is that we cannot just set up an ExpandableListView instance without destroying its internal state, i.e. the expanded/collapsed state of the groups. For this reason, it is extremely important to preserve the views and to prevent the first-level ExpandableListView of rearranging the second-level lists (handing out a second-level ExpandableListView in a certain position as a convertView at a different position). For this reason I implemented a cache in ColorExpListAdapter that makes sure that only one second-level ExpandableListView is generated for each child position in the first-level list and that second-level ExpandableListView instance is consistently returned for the same position. This guarantees that the collapsed/expanded state of the second-level views are preserved when the layout of the first-level list is recalculated.&lt;br /&gt;&lt;br /&gt;The other tricky issue is the size of the second-level lists. The layout recalculation of the first-level list is triggered by a group event of a second-level list. Due to the way expandable lists are implemented, when this event occurs, the items of the second-level list which was clicked are not yet added/removed according to the expand/collapse action. This means that the size of the list will be incorrect when the first-level list layout is recalculated.&lt;br /&gt;&lt;br /&gt;To solve this problem, observe the row count calculation in ColorExpListAdapter's calculateRowCount method and the way the row count is used in DebugExpandableListView's onMeasure method. DebugExpandableListView originally started to exist so that I can observe the layout of the second-level lists then it turned out that it has an important function: override the onMeasure method of the original ExpandableListView (actually inherited from ListView). Lazily, I kept the class name and the debug messages in it so that you can observe the rather complex operation of the layout process if you feel like.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4005437048709263469?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4005437048709263469/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4005437048709263469' title='17 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4005437048709263469'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4005437048709263469'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/02/3-level-expandable-lists.html' title='3-level expandable lists'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-tF39Q4p797s/TWPIVdYWYPI/AAAAAAAABnA/CaY4d3SA-LM/s72-c/level3list.png' height='72' width='72'/><thr:total>17</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4644798249256440191</id><published>2011-01-26T19:01:00.004+01:00</published><updated>2011-01-26T19:09:46.462+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugins'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>Plugins with user interface</title><content type='html'>A while ago I wrote about &lt;a href="http://mylifewithandroid.blogspot.com/2010/06/plugins.html"&gt;plugins&lt;/a&gt; and now I received a mail from somebody asking about plugins that extend the UI of an application. From one side, these "plugins" are the cornerstone of Android application design: activity invocation can be considered a "UI plugin mechanism". But what if somebody wants to plug in UI elements into an application's screen so that the UI elements provided by the plugin appear on the application's own screen? Limited screen estate makes these sorts of plugins less relevant than in case of PC applications but there is a precedent: &lt;a href="http://mylifewithandroid.blogspot.com/2010/01/remoteviews-as-hypertext-document.html"&gt;widgets&lt;/a&gt; plug into the Launcher's screen this way. I decided therefore to play with the idea and created a prototype.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/resplugin.zip"&gt;Click here to download the application.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You will find 3 subdirectories in the package (respluginapp, resplugin1, resplugin2), each is a separate Android project. Compile and install respluginapp and launch the application. You will see something like this:&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_K2nCeh0MHDY/TUBiVKLqeOI/AAAAAAAABms/itjevHbYiw8/s1600/resplugin1.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 139px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/TUBiVKLqeOI/AAAAAAAABms/itjevHbYiw8/s320/resplugin1.png" alt="" id="BLOGGER_PHOTO_ID_5566557255079983330" border="0" /&gt;&lt;/a&gt;Now compile and install resplugin1 and resplugin2. You will notice that the screen of respluginapp dynamically changes as you install the plugin packages. Eventually it will look like this:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_K2nCeh0MHDY/TUBiuuJxFxI/AAAAAAAABm0/nuXThWipxN0/s1600/resplugin2.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 197px;" src="http://2.bp.blogspot.com/_K2nCeh0MHDY/TUBiuuJxFxI/AAAAAAAABm0/nuXThWipxN0/s320/resplugin2.png" alt="" id="BLOGGER_PHOTO_ID_5566557694232434450" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The prototype idea is simple. We have 4 rows on the screen. Each row has a default content (some text) but if a plugin is installed, the default row content is replaced by the UI elements provided by the plugin.&lt;br /&gt;&lt;br /&gt;Sounds simple but it isn't.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Getting the layout from the newly installed plugin package and recognizing the plugin package is the easy part. The plugin package is recognized because it exposes a service with aexp.intent.action.PICK_RESPLUGIN intent action. That service will be used to handle plugin events. There is already a hack here: the name of a layout is available only to the application in the package because the resource compiler generates R.java that maps layout names to layout IDs. As the plugin host application cannot access this R.java, it has no access to layout names. Our plugin host application accesses the plugin's row layout by ID (0x7f030000 is hardcoded). This ID is allocated to the layout whose name is the first alphabetically among the layouts. As our plugins have only one layout in the package, it is not really an issue but this hack can cause quite a nightmare in larger projects.&lt;/li&gt;&lt;li&gt;The next hack happens with widget IDs. The resource compiler assigns IDs sequentially. Unfortunately it uses the same ID base for the plugin host application and the plugins so chances are that the IDs in the plugin host application and in the layouts of the plugins overlap. The plugin host application solves it by adding a row-specific offset to the IDs of the UI elements when they are loaded from the layout provided by the plugin package. But the downside is that from this moment, IDs used by the plugin host application and IDs used by the plugin will be different and the plugin host application has to constantly convert back and forth when it sends events to the plugin event handler service.&lt;/li&gt;&lt;li&gt;While the UI elements reside in the plugin host application's screen, the event handling is provided by a service exposed by the plugin. The event handling looks like the following: the event is intercepted in the plugin host application, the state of the plugin UI elements is serialized, the plugin event handler service is invoked that processes the event using the serialized plugin UI element state and spits out actions, how the plugin UI elements should be updated. Only some basic functionality is implemented in this prototype. Only button click events are intercepted and the state capture/update only works for TextViews and descendants. But you get the idea how complex it would be to do it properly. While fiddling with this part, I ran into a dead end street because I thought &lt;a href="http://mylifewithandroid.blogspot.com/2010/01/remoteviews-as-hypertext-document.html"&gt;RemoteViews&lt;/a&gt; could be used to implement the serialization/deserialization. RemoteViews, however, does not support EditText so this approach had to be abandoned.&lt;/li&gt;&lt;/ul&gt; Is this something you should do? Well, I am not sure. Pulling UI events between host applications and services are not trivial and also it can be slow because by default the plugins are in a different package therefore in a different Linux process (see&lt;a href="http://mylifewithandroid.blogspot.com/2009/06/controlling-application-separation.html"&gt; further explanation here&lt;/a&gt;). Our button click handlers probably do not consume so much CPU time but it can be worse with complex and frequent events. Also, the serialization mechanism presented here is kind of naive, check out the &lt;a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob_plain;f=core/java/android/widget/RemoteViews.java;hb=HEAD"&gt;RemoteViews source&lt;/a&gt; to check, how deep you can go.&lt;br /&gt;&lt;br /&gt;On the other hand, applications that change their screen layout when you install plugins are kind of cool as the Android widgets demonstrate. Decide yourself.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4644798249256440191?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4644798249256440191/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4644798249256440191' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4644798249256440191'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4644798249256440191'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2011/01/plugins-with-user-interface.html' title='Plugins with user interface'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/TUBiVKLqeOI/AAAAAAAABms/itjevHbYiw8/s72-c/resplugin1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-1951233629244783500</id><published>2010-12-24T13:56:00.001+01:00</published><updated>2010-12-24T13:57:55.318+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rpc'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><title type='text'>JSON-RPC with a client-side library</title><content type='html'>A while ago &lt;a href="http://mylifewithandroid.blogspot.com/2010/10/from-homegrown-json-protocol-to-json.html"&gt;I posted about my experiences with JSON-RPC&lt;/a&gt;. The conclusions were both positive and negative - JSON-RPC is a very promising protocol. Unfortunately I found that it is not so easy to find good client- and server-side libraries that support it. In particular, the JSON-RPC protocol handler in Android client needed to be implemented from scratch, without any library support.&lt;br /&gt;&lt;br /&gt;Then one commenter proposed to check out &lt;a href="http://code.google.com/p/android-json-rpc/"&gt;this JSON-RPC library&lt;/a&gt;. Many things happened since but eventually I was able to get some first-hand experience with this library.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/jsonrpclib.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;First, deploy the server part (in the server/ directory) as described &lt;a href="http://mylifewithandroid.blogspot.com/2010/10/client-server-communication-with-json.html"&gt;at this post&lt;/a&gt; (actually, the server is same as the previous JSON-RPC example server). Then deploy the client part and you get the same old batch calculator functionality.&lt;br /&gt;&lt;br /&gt;Under the hood, however, a lot of things have changed. Instead of fiddling with the JSON tokenizer and the Apache HTTP library, the JSON invocation is just one line.&lt;br /&gt;&lt;br /&gt; &lt;span style="font-family: Courier New,Courier,monospace;"&gt;              double d = client.callDouble( &lt;/span&gt;&lt;br /&gt; &lt;span style="font-family: Courier New,Courier,monospace;"&gt;                    entry.opMethod(),&lt;/span&gt;&lt;br /&gt; &lt;span style="font-family: Courier New,Courier,monospace;"&gt;                    entry.getV1(),&lt;/span&gt;&lt;br /&gt; &lt;span style="font-family: Courier New,Courier,monospace;"&gt;                    entry.getV2() );&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This simplicity comes with a price, however. The library only supports JSON-RPC 1.0 which means that there are no batch invocations and standardized exception handling. The client actually pumps the elements of the batch operation one by one into the server which could create hairy transaction consistency problems were the application a real data-intensive system. I could have refactored the server side and I could have sent the entire batch in one request (representing each operation as a JSON array sub-list) but then I would have lost JSON-RPC's simplicity on the server side.&lt;br /&gt;&lt;br /&gt;The right way is to implement JSON-RPC 2.0 support in the library. Let's see if I or anybody else has some spare cycle in the new year for this.&lt;br /&gt;&lt;br /&gt;That's about the valuable content, now the advertisements. First, please note the LinkedIn share button on the right panel. Try it, I am really curious what it does. Second, my very competent ex-colleague started a business &lt;a href="http://www.vanroecompacts.com/"&gt;of selling vintage powder compacts. &lt;/a&gt;Weird idea, isn't it? If you find it as weird as I did, try out the site, maybe you get some gift idea.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-1951233629244783500?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/1951233629244783500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=1951233629244783500' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1951233629244783500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1951233629244783500'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/12/json-rpc-with-client-side-library.html' title='JSON-RPC with a client-side library'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4398748054285682865</id><published>2010-12-05T12:50:00.004+01:00</published><updated>2010-12-05T13:03:06.189+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adapter'/><category scheme='http://www.blogger.com/atom/ns#' term='expandable list'/><title type='text'>Expandable list and checkboxes revisited</title><content type='html'>Once upon a time I wrote a &lt;a href="http://mylifewithandroid.blogspot.com/2010/02/expandable-lists-and-check-boxes.html"&gt;nice little post about checkbox focus problems&lt;/a&gt;. The guy who originally asked the question was satisfied and went away. While I was looking the other way, a heated discussion started in the comments because the example program had an annoying property: it did not preserve the state of the check boxes when the groups were opened/collapsed but reordered the check marks randomly. Eventually I got a mail whether I could put my example program right.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/elistcbox2.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;So I did. The cause of the trouble was the adapter backing the expandable list view. It was a SimpleExpandableListAdapter which, as its name implies, is simple. In particular, it is not able to feed data to check boxes because they require boolean state while SimpleExpandableListAdapter supports only Strings (&lt;a href="http://mylifewithandroid.blogspot.com/2009/10/spinner-and-its-data-behind.html"&gt;here is a posts that explains the relationship between views and adapters&lt;/a&gt;). The solution was to write a custom ExpandableListAdapter and the random check mark reordering disappeared.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_K2nCeh0MHDY/TPt_N6UXTPI/AAAAAAAABmg/htbCtkSN_0M/s1600/elistcbox2.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 233px; height: 320px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/TPt_N6UXTPI/AAAAAAAABmg/htbCtkSN_0M/s320/elistcbox2.png" alt="" id="BLOGGER_PHOTO_ID_5547167243006594290" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4398748054285682865?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4398748054285682865/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4398748054285682865' title='35 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4398748054285682865'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4398748054285682865'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/12/expandable-list-and-checkboxes.html' title='Expandable list and checkboxes revisited'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_K2nCeh0MHDY/TPt_N6UXTPI/AAAAAAAABmg/htbCtkSN_0M/s72-c/elistcbox2.png' height='72' width='72'/><thr:total>35</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3534146758592856469</id><published>2010-10-30T16:17:00.003+02:00</published><updated>2010-10-30T16:25:45.236+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rpc'/><category scheme='http://www.blogger.com/atom/ns#' term='json'/><title type='text'>From homegrown JSON protocol to JSON-RPC</title><content type='html'>As I discussed &lt;a href="http://mylifewithandroid.blogspot.com/2010/10/client-server-communication-with-json.html"&gt;in the previous post&lt;/a&gt;, JSON now starts to become fashionable, mostly because of its more compact encoding compared to XML. If we are commited to communicate over JSON with the server, the need for the most common client-server communication pattern, Remote Procedure Call (RPC) arise.&lt;br /&gt;&lt;br /&gt;JSON has such a solution, called JSON-RPC. JSON-RPC is a lightweight client-server protocol. Beside the usual stuff (remote method identification, parameter and result encoding, exceptions) it has some remarkable properties that make it particularly suitable for mobile communication.&lt;br /&gt;&lt;br /&gt;There are two JSON-RPC specifications out there, &lt;a href="http://json-rpc.org/wiki/specification"&gt;JSON-RPC 1.0&lt;/a&gt; and &lt;a href="http://groups.google.com/group/json-rpc/web/json-rpc-2-0"&gt;2.0&lt;/a&gt;. While 2.0 in most aspects can be seen as a natural extension of 1.0, there are differences too. JSON-RPC 1.0 was designed to be peer-to-peer with the main transport protocol being TCP. HTTP was also supported but with limitations. JSON-RPC 2.0 is explicitly client-server even though the change is only in the terminology used in the specification. The client-server nature of JSON 2.0 ensures, however, that HTTP as a transport is more naturally supported.&lt;br /&gt;&lt;br /&gt;Let's start with the basics.&lt;br /&gt;&lt;br /&gt;This is a JSON 1.0 request:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;{"method":"add","params":[3,4],"id":0}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;"method" attribute specifies the remote method, "params" attribute has a value of an array with the RPC arguments. The order matters and has to be the same as the arguments on the remote side. JSON-RPC requests and responses are connected with the "id" attribute because requests and responses does not have to be ordered. The response following a request - or over a full duplex bearer like TCP arriving at any time - does not have to be the response to the request just sent. It may be a response to an earlier request and the "id" parameter guarantees that requests can be coupled with their responses.&lt;br /&gt;&lt;br /&gt;This is a JSON 1.0 response:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;{"error": null, "result": 5, "id": 0}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;"result" is the return value of the RPC call, "id" is the same as the corresponding request and "error" carries the error object if there was an error during the RPC processing. RPC 1.0 does not define the error value beside stating that it is an object.&lt;br /&gt;&lt;br /&gt;JSON-RPC 2.0 extends JSON-RPC 1.0 in many important ways.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;In order to provide backward compatibility, JSON-RPC 2.0 requests and responses must all have "jsonrpc": "2.0" attribute.&lt;br /&gt;  &lt;/li&gt;&lt;li&gt;Error object is now specified. It has "code", "message" and "data" attributes (more about that in the specification).&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;&lt;li&gt;It specifies &lt;a href="http://mylifewithandroid.blogspot.com/2010/01/oneway-interfaces.html"&gt;oneway&lt;/a&gt; messages that JSON-RPC 2.0 calls notifications. Notifications are requests without "id" attribute. No response can be sent to notifications.&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;&lt;li&gt;There are batched requests and responses. Batch is formed by having an array as top-level element and adding JSON-RPC request or response objects tothis array. E.g. the following is a batched request: &lt;/li&gt;&lt;/ul&gt; &lt;span style="font-family: courier new;"&gt;[{"method":"add","params":[2,3],"id":0,"jsonrpc":"2.0"},&lt;br /&gt;{"method":"mul","params":[4,5],"id":1,"jsonrpc":"2.0"}]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;and the following is a batched response:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;[{"error": null, "jsonrpc": "2.0", "result": 5, "id": 0},&lt;br /&gt;{"error": null, "jsonrpc": "2.0", "result": 20, "id": 1}]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The following is a JSON-RPC 2.0 error response:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;{&lt;/span&gt;&lt;span style="font-family: courier new;"&gt;"jsonrpc": "2.0",&lt;/span&gt;&lt;span style="font-family: courier new;"&gt;"error": {"message": "ServiceRequestNotTranslatable", "code": 1000}, "result": null, "id": 12}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That was a lot of explanations, let's see something that works!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/jsonrpc.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This is the same client-server batch calculator that you have seen in the previous post. Use the deployment instructions of the &lt;a href="http://mylifewithandroid.blogspot.com/2010/10/client-server-communication-with-json.html"&gt;previous post&lt;/a&gt; to try out the example program. If you want to try the example on a real phone, I recommend deploying the server part on the real Google App Engine infrastructure as described in the previous post.&lt;br /&gt;&lt;br /&gt;The example program uses JSON-RPC 2.0 as  communication protocol between the client and server instead of our homegrown protocol. The biggest change is on the server side, which is implemented as a Python application for Google App Engine. Instead of obscure parsing code, you see remote methods defined like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;@ServiceMethod&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;def add(self, v1,v2):&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;    return v1+v2&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;and the rest is done by the server-side Python JSON-RPC 2.0 framework which I hacked out of &lt;a href="http://json-rpc.org/wiki/python-json-rpc"&gt;this JSON-RPC 1.0&lt;/a&gt; implementation. The client side does not use any framework at all which is a major problem with this example program. So far I have not been able to find any decent JSON-RPC 2.0 client-side framework suitable for Android. If you have an idea, please comment!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3534146758592856469?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3534146758592856469/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3534146758592856469' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3534146758592856469'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3534146758592856469'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/10/from-homegrown-json-protocol-to-json.html' title='From homegrown JSON protocol to JSON-RPC'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7392478535114551385</id><published>2010-10-21T22:05:00.004+02:00</published><updated>2010-10-21T22:17:04.043+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='json'/><title type='text'>Client-server communication with JSON</title><content type='html'>A long time ago, XML was the emerging, fashionable technology. Those times have clearly passed and nowadays XML is just another boring piece of basic technology. If you want to be trendy, you have to go for something else. Luckily, there is a new &lt;span style="font-style: italic;"&gt;in&lt;/span&gt; thing, JavaScript Object Notation, JSON. In this post, I show you a simple client-server application based on Android, JSON and Google App Engine.&lt;br /&gt;&lt;br /&gt;JSON is based on JavaScript object literals and is a textual representation of complex data instances. It has 4 basic types: strings, numbers, booleans and null. Objects and arrays can be constructed out of these 4 types, object is really just the JSON slang for key-value pairs. The entire thing is specified in &lt;a href="http://www.ietf.org/rfc/rfc4627.txt"&gt;RFC 4627&lt;/a&gt; so I stick to just some simple examples so that you have that JSON feeling if you don't want to read the RFC.&lt;br /&gt;&lt;br /&gt;This is a JSON number: &lt;span style="font-family:courier new;"&gt;-4.7e12&lt;/span&gt;&lt;br /&gt;This is another JSON number:  0&lt;br /&gt;This is NOT a JSON number:&lt;span style="font-family:courier new;"&gt; 00012&lt;/span&gt; (no leading zeros!)&lt;br /&gt;This is a JSON string:  &lt;span style="font-family:courier new;"&gt;"JSON"&lt;/span&gt;&lt;br /&gt;This is another JSON string: &lt;span style="font-family:courier new;"&gt;"\u005C"&lt;/span&gt; (always 4 hexa digits after the \u, just one character, the \)&lt;br /&gt;This is not a JSON string: &lt;span style="font-family:courier new;"&gt;'notJSON'&lt;/span&gt;&lt;br /&gt;These are JSON literals: true, false, null&lt;br /&gt;This is a JSON array:&lt;span style="font-family:Courier New,Courier,monospace;"&gt; [1,"hello",null]&lt;/span&gt;&lt;br /&gt;This is a JSON array having two other JSON arrays as elements: &lt;span style="font-family:Courier New,Courier,monospace;"&gt;[[1,"hello"],[2,"hallo"]]&lt;/span&gt;&lt;br /&gt;This is a JSON object: &lt;span style="font-family:Courier New,Courier,monospace;"&gt;{"a":1,"b":"hello"}&lt;/span&gt;&lt;br /&gt;This is a JSON object having another object associated with "a" key and an array with "b" key:&lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;{"a":{"aa":1,"bb":2},"b":[3,4]}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I think this is enough for starter because it is evident that JSON has quite powerful data structures. Arrays are equivalent of Lisp lists while objects can be represented as lists of dotted pairs.&lt;br /&gt;&lt;br /&gt;How does JSON compare to the incumbent serialization standard, XML? JSON is fashionable in mobile communication because it is somewhat more bandwidth-efficient than XML. E.g. the last example can be written in (ugly) XML like this (XML tag mangling due to blog engine limitations):&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;[l]&lt;/span&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;  [a]&lt;/span&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;    [aa]1[/aa]&lt;/span&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;    [bb]2[/bb]&lt;/span&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;  [/a]&lt;/span&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;  [b]&lt;/span&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;    [k]3[/k]&lt;/span&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;    [k]4[/k]&lt;br /&gt;  [/b]&lt;br /&gt;[/l]&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Not counting the white spaces, this is 58 characters while the JSON representation is just 32 characters, about 45% less. The situation is worse if we consider many "well-designed" XMLs out there (like e.g. SOAP documents) which are full of namespace declarations, long tag names, etc. JSON with its simple yet powerful syntax definitely has a space in mobile application design.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/json.zip"&gt;Click here to download the example program.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;I will demonstrate the power of JSON with a simple client-server example application. This application is a batch-mode calculator. The user first enters operations on the device's UI. These operations are not calculated immediately. When the user selects the "Process list" menu item, the collected operations are sent to the server in one batch. The server calculates the operations that are sent back to the client which displays the results. As you might have guessed, the client and the server communicates with JSON data structures.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_K2nCeh0MHDY/TMCfOJ4STbI/AAAAAAAABmY/77Eru1KpXuk/s1600/jsoncalc.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 159px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/TMCfOJ4STbI/AAAAAAAABmY/77Eru1KpXuk/s320/jsoncalc.jpg" alt="" id="BLOGGER_PHOTO_ID_5530595407929953714" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The client part needs to be deployed on Android emulator (let's start with the emulator, I will return to this) like usual. The server part, however, requires the &lt;a href="http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Python"&gt;Google App Engine SDK for Python&lt;/a&gt;. Download the SDK, edit server/ae_jsoncalcserver.sh so that it suits your directory layout then start the shell script (Windows users will have to turn this one-line shell script into a .bat file). If you access localhost:8080 with your browser, you get a short message that GET is not supported. If you get there, you installed the server part properly.&lt;br /&gt;&lt;br /&gt;Now you can start the client on the emulator and you can start to play with it. The most interesting part is the process() method in JsonCalc.java. I structured the program so that the generation of JSON request and the parsing of the response is all there. The org.json package has been part of Android from the beginning and this package is really easy to use. For the server part, I used the &lt;a href="http://code.google.com/p/simplejson/"&gt;simplejson&lt;/a&gt; JSON parser for Python.&lt;br /&gt;&lt;br /&gt;The Android client logs the JSON requests and responses in the Android log (adb logcat). I represented the batch operation and its result as array of objects. Request and response objects have different keys.&lt;br /&gt;&lt;br /&gt;Request example:&lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;[{"op":2,"v1":2,"id":1,"v2":3.3},{"op":3,"v1":4,"id":2,"v2":6},{"op":4,"v1":6.6,"id":3,"v2":2.2}]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Response example:&lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;[{"id": 1, "result": -1.2999999999999998}, {"id": 2, "result": 24}, {"id": 3, "result": 2.9999999999999996}]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;In case you want to try it out on a real phone, you'd better &lt;a href="http://code.google.com/appengine/docs/python/gettingstarted/uploading.html"&gt;deploy the server part on Google infrastructure&lt;/a&gt;. Then edit Config.java in the client source so that APP_BASE_URI points to your server's address and you are ready to go. When you recompile, make sure that you delete the client/bin/classes directory - chances are that javac will not notice your changes you made to Config.java because of its broken dependency resolution.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7392478535114551385?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7392478535114551385/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7392478535114551385' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7392478535114551385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7392478535114551385'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/10/client-server-communication-with-json.html' title='Client-server communication with JSON'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/TMCfOJ4STbI/AAAAAAAABmY/77Eru1KpXuk/s72-c/jsoncalc.jpg' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3861317101199316888</id><published>2010-10-11T21:09:00.006+02:00</published><updated>2010-10-22T22:29:14.418+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='c2dm'/><category scheme='http://www.blogger.com/atom/ns#' term='push'/><title type='text'>Push service from Google</title><content type='html'>&lt;span style="font-weight: bold;"&gt;Update:&lt;/span&gt; the first version of this post contained factual errors. Thanks to &lt;a href="http://www.blogger.com/profile/07649049782959178493"&gt;Tejas&lt;/a&gt; for pointing these out, &lt;a href="http://techtej.blogspot.com/2010/10/android-c2dm-messaging-server-push-for.html"&gt;you can find his experiences with C2DM here&lt;/a&gt;. The example program was also updated to fix these errors.&lt;br /&gt;&lt;br /&gt;Android 2.2's most important improvement is without doubt the significantly faster virtual machine. The API, however, got some new features too. For me, the most intriguing new feature is the access to Google's experimental push services.&lt;br /&gt;&lt;br /&gt;I have already posted about push feature in relation to &lt;a href="http://mylifewithandroid.blogspot.com/2009/02/asynchronous-communication-in-android.html"&gt;iBus//Mobile asynchronous communication package &lt;/a&gt;so push is not new to Android. There are also open source alternatives like &lt;a href="http://knolleary.net/arduino-client-for-mqtt/"&gt;MQTT&lt;/a&gt;, &lt;a href="http://deacon.daverea.com/"&gt;Deacon&lt;/a&gt; or &lt;a href="http://github.com/urbanairship/android-push-library"&gt;Urban Airship&lt;/a&gt;. It has also been present in certain Google applications from the beginning. Whenever Android Gmail application sends a notification when a new mail arrives on the server, you see push in operation. It is pretty fast, according to my experiences, even though time to time it takes longer time for the notification to arrive. 2.2 opened that already existing mechanism to general-purpose applications, even though the push service is still in early beta.&lt;br /&gt;&lt;br /&gt;In order to do push, either one has proper push bearer (a network mechanism able to deliver unsolicited messages to the device) or such mechanism is simulated. Currently only two real push bearers are deployed widely on mobile networks, SMS and Blackberry's proprietary push solution. SMS is costly and the Blackberry solution is available only for Blackberry so the push bearer has to be simulated. The most common simulation method relies on the device to open a TCP connection to the push server. As the TCP stream is full-duplex, the server can send the push message to the device, provided that the stream is still alive. And that's the tricky bit whose complexity should not be underestimated. TCP streams time out if there is no communication on them and the other side does not necessarily notice it. A constant ping-pong traffic needs to be generated to prevent this. Frequency, however, is critical. Too frequent ping-pongs and the data cost associated to ping-ponging will be unacceptable and the battery is drawn down quickly. Too rare ping-pongs and the device or the server will not notice that the TCP stream was closed, only after a long timeout. For the user, it means that he did not get his urgent message immediately, only after, say, an hour.&lt;br /&gt;&lt;br /&gt;There is no perfect solution to this problem, therefore it is a good feeling that Google made its best to create a simulated push bearer, along with the server that is able to keep that many TCP sockets open and shared that infrastructure with us. The Google push architecture has the following elements.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The device that authenticates and registers with the push server (Cloud to Device Messaging in Google lingo) and provides authentication ticket to the 3rd Party Application Server.&lt;/li&gt;&lt;li&gt;3rd Party Application Server that uses the authentication ticket generated by the device to send push requests to the push server.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Push server provided by Google that authenticates push requests from 3rd Party Application Servers and delivers the messages to the devices.&lt;/li&gt;&lt;/ul&gt; &lt;a href="http://pallergabor.uw.hu/androidblog/push.zip"&gt;Click here to download the example program.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;Cloud to Device Messaging (C2DM) has many tricky aspects but I wanted to create a test program that is as simple as possible. To try it out, you need the following:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;A PC connected to the Internet so that Google C2DM servers can be reached (and C2DM servers can reach us).&lt;/li&gt;&lt;li&gt;Android emulator with 2.2+Google API AVD created (API level 8).&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Python"&gt;Google App Engine SDK for Python&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;A Google account &lt;a href="http://code.google.com/android/c2dm/signup.html"&gt;that you register with Google&lt;/a&gt; for push. It is better not to use your real e-mail address because you have to insert the password for this account into the server script. Use aexp.push as package name for the application in the registration form.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; Do the following.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Unpack the download package. Start the Android emulator. Create a Google account if no such account exists (Settings/Accounts &amp;amp; sync). This account does not have to be the same that you registered for push, it can be any Google account that you know the password for.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Enter the "client" directory in the download package, update client/src/aexp/push/Config.java with the Google account you registered for push (C2DM_SENDER)&lt;/li&gt;&lt;li&gt;Open server/pushserver/pushserver.py and update it with your push Google account (lines 165 and 167). Unfortunately here you have to insert the password for your push Google account into the server script.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Enter the "server" directory, customize "ae_pushserver.sh" according to your directory layout and start it. Google App Engine SDK starts.&lt;/li&gt;&lt;li&gt;Start the Push application on the emulator, select the account using the Menu and wait for the "Registered" message to appear.  Now the server application is ready to deliver push messages.&lt;/li&gt;&lt;li&gt;Go to http://localhost:8080 with your browser and type a message. Select the account from the drop-down list that you configured on the device and click the submit button. The message should appear on the emulator screen.&lt;/li&gt;&lt;/ul&gt;&lt;a href="http://4.bp.blogspot.com/_K2nCeh0MHDY/TLNhFoSs9FI/AAAAAAAABmQ/2qbzv4yQlFU/s1600/push.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 310px; height: 320px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/TLNhFoSs9FI/AAAAAAAABmQ/2qbzv4yQlFU/s320/push.png" alt="" id="BLOGGER_PHOTO_ID_5526867917056832594" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you want to do the same trick from a real phone, you need a server with fix IP address or a fix DNS name and run the App Engine SDK there. &lt;a href="http://code.google.com/appengine/docs/python/gettingstarted/uploading.html"&gt;Or better, deploy it on the real thing, on Google infrastructure&lt;/a&gt;. In any case, make sure that you update the server address in client/src/aexp/push/Config.java (and delete client/bin/classes directory as there are occassionally troubles with javac's dependency resolution).&lt;br /&gt;&lt;br /&gt;About the application. The application reuses c2dm utility library from the &lt;a href="http://code.google.com/p/jumpnote/"&gt;JumpNote&lt;/a&gt; sample application. When the account is selected, the application registers with the push server using the phone user's credentials and the application ID that you registered with Google. Then comes the interesting bit. After the application registers with the push server, it sends the registration ID to the application server. The application server will use the registration ID to talk to the push server when it wants to do push. The server uses the Google account that you selected for push for authentication and therefore it needs the password for this account.&lt;br /&gt;&lt;br /&gt;The implementation in the example application server program is a not efficient as it always requests an authentication token before it sends the push message. In an efficient implementation the token can be requested only once and can be used to authenticate many messages. Eventually the token expires and only then should new authentication token requested.&lt;br /&gt;&lt;br /&gt;The client application also sends the client account name to the server  but it is not used for authentication, it is only needed so that you can  select the push target by account name in the web application's  drop-down menu.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3861317101199316888?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3861317101199316888/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3861317101199316888' title='47 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3861317101199316888'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3861317101199316888'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/10/push-service-from-google.html' title='Push service from Google'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_K2nCeh0MHDY/TLNhFoSs9FI/AAAAAAAABmQ/2qbzv4yQlFU/s72-c/push.png' height='72' width='72'/><thr:total>47</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7118135115951726943</id><published>2010-07-09T20:58:00.003+02:00</published><updated>2010-07-09T21:08:21.479+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='service interfaces'/><category scheme='http://www.blogger.com/atom/ns#' term='hack'/><title type='text'>Direct service invocation considered harmful</title><content type='html'>&lt;a href="http://mylifewithandroid.blogspot.com/2008/02/double-life-of-service.html"&gt;I have a more than 2 years old&lt;/a&gt; - and quite popular - blog post about service binding and I thought I knew enough of Android services. Then I ran into a service binding pattern that I was not aware of before. I would like to clarify before I start that every alternative in this post "works" - meaning it does what it is intended to do on the Android versions released so far. Still, it seems that this hack I am going to present got into tutorials and books. I think there is a risk here that a good number of coders think about it as the right thing to do and not as a hack.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/dualservice_direct.zip"&gt;Click here to download the example program.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;This is a version of the good old DualService presented in this blog entry. The main difference that the AIDL file has been removed and instead you will find this strange construct in DualService.java:&lt;br /&gt;&lt;br /&gt;    public class DualServiceBinder extends Binder implements ICounterService {&lt;br /&gt;        public int getCounterValue() {&lt;br /&gt;            return counter;&lt;br /&gt;          }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;and in onBind:&lt;br /&gt;&lt;br /&gt;    public IBinder onBind( Intent intent ) {&lt;br /&gt;          return dualServiceBinder;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;Now this DualServiceBinder "Binder derivative" is a strange thing. Binders are serialization-deserialization constructs that turn RPC invocations into byte streams and back. That byte stream can be sent through the Binder kernel driver and that's how interprocess communication works in Android. Binder derivatives are normally generated by the aidl tool. You can do it by hand but it is not for the faint of heart. Basically you have to implement transact/onTransact methods and serialize/deserialize your data by hand. If you follow my advice, you leave it to the aidl tool.&lt;br /&gt;&lt;br /&gt;DualServiceBinder does none of it. It implements a plain Java interface (ICounterService) and exposes a plain Java method. On the other side, in DualServiceClient it justs casts the IBinder to ICounterService in the onServiceConnected method and invokes the method on it.&lt;br /&gt;&lt;br /&gt;As you might have guessed, this is a hack. The author sidestepped entirely the Android IPC mechanism and abused &lt;a href="http://mylifewithandroid.blogspot.com/2009/06/controlling-application-separation.html"&gt;two properties of the Android application model&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;By default, the Android application manager loads activities/services/providers into the Dalvik VM running in the same Linux process.&lt;/li&gt;&lt;li&gt;Classes from the same DEX file (there is one DEX file per APK) are loaded by the same class loader.&lt;/li&gt;&lt;/ul&gt; These guys here do not use Binder at all. They need an IBinder implementation because of the onBind/onServiceConnected methods' contracts and Binder comes handy as it is an implementation of the IBinder interface. Other than that, the DualServiceBinder instance is just passed from the Service to the Activity without any IPC taking place. That is why it can be recasted to a simple Java interface and the method can be invoked without any IPC mechanism. The hack is actually faster than the AIDL-based implementation (as there is no serialization/deserialization, just in-memory parameter passing) but this approach is not supported in any way by the Android API contracts.&lt;br /&gt;&lt;br /&gt;Do you think I found the hack in a shady, unsupported freeware? No, I found it i&lt;a href="http://www.amazon.fr/Programmation-Android-conception-d%C3%A9ploiement-Google/dp/2212125879"&gt;n a book&lt;/a&gt; (actually, my attention was turned to this book by a fellow Android programmer). The book proposes another trick: the "service interface" of their Binder descendant exposes just a getService() method that returns a reference to the Service instance and later the Activity invokes public methods of the service directly, circumventing even their own tricky service interface.&lt;br /&gt;&lt;br /&gt;I definitely think it is a bad idea to propagate this hack even though it works on the existing Android implementations and is somewhat faster than the regular, AIDL-based invocation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7118135115951726943?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7118135115951726943/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7118135115951726943' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7118135115951726943'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7118135115951726943'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/07/direct-service-invocation-considered.html' title='Direct service invocation considered harmful'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4613856573704875996</id><published>2010-06-17T13:15:00.003+02:00</published><updated>2010-06-17T13:21:32.750+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugins'/><category scheme='http://www.blogger.com/atom/ns#' term='services'/><title type='text'>Plugins</title><content type='html'>I blogged 2 years ago that Android is a surprisingly modern application platform, I even said &lt;a href="http://mylifewithandroid.blogspot.com/2008/02/android-is-most-modern-application.html"&gt;that it was the most modern application platform.&lt;/a&gt; This is partly due to Android's advanced component features. These features are not always evident to the naked eye so I present here a standard exercise to demonstrate them.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/plugin.zip"&gt;Click here to download the example program.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;In this exercise we will implement a plugin framework for an example program. Plugins are components that connect to the main application over a uniform interface and are dynamically deployable. The download package contains two Android applications. The first, pluginapp is the example application that will use the plugins. The second, plugin1 is a plugin package that contains two plugins for pluginapp. Plugin1 package is not visible among the executable applications, it does not expose any activity that may be launched by the end user. If deployed, however, its plugins appear in pluginapp. Plugin deployment/undeployment is dynamic, if you uninstall plugin1 package while pluginapp is running, pluginapp notices the changes and the plugins disappear from the plugin list.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/TBoE9yLF1fI/AAAAAAAABl4/E2FO9CmUY6k/s1600/pluginapp.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 199px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/TBoE9yLF1fI/AAAAAAAABl4/E2FO9CmUY6k/s320/pluginapp.png" alt="" id="BLOGGER_PHOTO_ID_5483700955763824114" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/TBoFH5KQmzI/AAAAAAAABmA/7aHm_c7wIIs/s1600/invokeop.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 208px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/TBoFH5KQmzI/AAAAAAAABmA/7aHm_c7wIIs/s320/invokeop.png" alt="" id="BLOGGER_PHOTO_ID_5483701129438075698" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;In order to implement these functionalities, the following features of the Android framework were used.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Each plugin is a service. Plugin1 exposes two of them. In order to identify our plugins, I defined a specific Intent (aexp.intent.action.PICK_PLUGIN) and I attached an intent filter listening to this intent in plugin1's AndroidManifest.xml. In order to select a particular plugin, &lt;a href="http://mylifewithandroid.blogspot.com/2010/01/oneway-interfaces.html"&gt;I abused the category field again&lt;/a&gt;. One plugin can be accessed therefore by binding a service with an intent whose action is aexp.intent.action.PICK_PLUGIN and whose category equals to the category listed in AndroidManifest.xml of the package that exposes the plugin service.&lt;/li&gt;&lt;li&gt;Plugins are discovered using the Android PackageManager. Pluginapp asks the PackageManager to return the list of plugins that are bound to aexp.intent.action.PICK_PLUGIN action. Pluginapp then retrieves the category from this list and can produce an intent that is able to bindthe selected plugin. &lt;/li&gt;&lt;li&gt;Pluginapp updates the plugin list dynamically by listening to ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_REPLACED intents. Whenever any of these intents are detected, it refreshes the plugin list.&lt;/li&gt;&lt;/ul&gt; Even though Android is not as sophisticated as OSGi when it comes to component framework, it has every necessary element. It has a searchable service registry, packages can be queried for the services they expose and there are deployment events.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4613856573704875996?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4613856573704875996/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4613856573704875996' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4613856573704875996'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4613856573704875996'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/06/plugins.html' title='Plugins'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/TBoE9yLF1fI/AAAAAAAABl4/E2FO9CmUY6k/s72-c/pluginapp.png' height='72' width='72'/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-926076771143135159</id><published>2010-06-06T22:02:00.005+02:00</published><updated>2010-10-11T21:17:04.093+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='acceleration sensor'/><category scheme='http://www.blogger.com/atom/ns#' term='wavelet'/><title type='text'>Shake-walk-run</title><content type='html'>Well, anybody can draw up frightening equations like I did in the&lt;a href="http://www.sfonge.com/ipaper/algorithms-processing-accelerator-sensor-data"&gt; acceleration signal analysis report&lt;/a&gt; in my previous post and claim that they work just because of some diagrams made by a mathematical analysis program. You'd better not believe this sort of bullshit before you experience it yourself. To help you along that path, I implemented a simple application that let you try out, how these methods work in real life.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/accel_program.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you open this simple application, you can enable the sampling with a checkbox. Once the sampling is switched on, the service behind the app constantly samples the acceleration sensor and tries to detect motion patterns. It is able to distinguish 3 patterns now: shake, walk and run. Not only it distinguishes these patterns, it also tries to extract shake, step and run count. Unfortunately I tested it only on myself so the rule base probably needs some fine tuning but it works for me. The walk detector uses the w3 wavelet (read the report if you are curious, what w3 wavelet can be) and is therefore a bit slow, it takes about 2-3 seconds before it detects walking and starts counting the steps but that delay is consistent - if you stop walking, it continues counting the steps it has not counted yet.&lt;br /&gt;&lt;br /&gt;The moral for this application is that the tough part in detecting motion patterns is the rule base behind the signal processing algorithms. It is OK that wavelets separate frequency bands and signal power calculation allows to produce "gate" signals that enable/disable counting for certain motion patterns. But where are the signal power limits for e.g. shaking and how to synchronize "gate" signals with the signals to be counted? This example program is a good exercise as you can observe, how it uses delayers and peak counters synchronized with signal power calculators to achieve the desired functionality.&lt;br /&gt;&lt;br /&gt;Some notes on the implementation. The machinery behind the background service that sends events to an activity is described in &lt;a href="http://mylifewithandroid.blogspot.com/2008/01/about-binders.html"&gt;this&lt;/a&gt; and &lt;a href="http://mylifewithandroid.blogspot.com/2010/01/oneway-interfaces.html"&gt;this&lt;/a&gt; blog posts. Also, observe the debug flags in SamplingService, if you set one of these, the service spits out signal data that you can analyse with &lt;a href="http://www.sagemath.org/"&gt;Sage&lt;/a&gt; later. I did not include the &lt;a href="http://mylifewithandroid.blogspot.com/2010/04/monitoring-sensors-in-background.html"&gt;keep-awake hack&lt;/a&gt; into this implementation because it was not necessary on my Nexus One with the leaked Android 2.2. I put a public domain license on the &lt;a href="http://pallergabor.uw.hu/androidblog/"&gt;download page&lt;/a&gt; because somebody was interested in the license of the example programs.&lt;br /&gt;&lt;br /&gt;I have to think a bit, where I want to go from here. The most attractive target for me is to formalize all these results into a context framework for Android applications but I may fiddle a bit more on signal processing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-926076771143135159?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/926076771143135159/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=926076771143135159' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/926076771143135159'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/926076771143135159'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/06/shake-walk-run.html' title='Shake-walk-run'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-8269899475665991593</id><published>2010-05-14T18:44:00.009+02:00</published><updated>2011-02-07T12:02:06.448+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='acceleration sensor'/><category scheme='http://www.blogger.com/atom/ns#' term='wavelet'/><title type='text'>Analysing acceleration sensor data with wavelets</title><content type='html'>Before we get back to Android programming, we need some theoretical background on signal analysis. I uploaded a report onto &lt;a href="http://www.sfonge.com/knowledge_exchange"&gt;Sfonge.com knowledge exchange&lt;/a&gt;. The document is somewhat heavy on math. To quote &lt;a href="http://www.seas.gwu.edu/%7Ekaufman1/FortranColoringBook/ColoringBkCover.html"&gt;Dr. Kaufman's Fortran Coloring Book&lt;/a&gt;: if you don't like it, skip it. But if your teacher likes it, you failed.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.sfonge.com/epaper/algorithms-processing-accelerator-sensor-data"&gt;Click here to read the report, you need free registration to access.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you are too impatient to read, here is the essence.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;There is no single perfect algorithm when analysing acceleration signals. The analysis framework should provide a toolbox of different algorithms, some working in the time-domain, some operating in the frequency domain. The decision engine that classifies the movements may use a number of algorithms, a characteristic set for each movement type.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;It has been concluded in the medical research community that wavelet transformation is the most optimal algorithm for frequency-domain analysis of acceleration signals. This report presented concrete cases, how wavelet transformation can be used to classify three common movements: walking, running and shake. In addition, the wavelet transformation provided data series that can be used to extract other interesting information, e.g. step count.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For those who would like to repeat my experiments, I uploaded the prototype. First&lt;br /&gt;you need &lt;a href="http://www.sagemath.org/"&gt;Sage&lt;/a&gt; (I used version 4.3.3). &lt;a href="http://pallergabor.uw.hu/androidblog/accel.zip"&gt;Download&lt;/a&gt; and unpack the prototype package and enter the proto directory. Then launch Sage (with the "sage" command) and issue the following commands:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;import accel&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;accel.movements(5)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;now you will be able to look at the different waveforms, e.g.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;list_plot(accel.shake_w5)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Sage is scriptable in Python. If you know Python, you will find everything familiar, if not - bad luck, you won't get far in Sage.&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-8269899475665991593?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/8269899475665991593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=8269899475665991593' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8269899475665991593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8269899475665991593'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/05/analysing-acceleration-sensor-data-with.html' title='Analysing acceleration sensor data with wavelets'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4340788309697104838</id><published>2010-05-05T20:51:00.005+02:00</published><updated>2010-05-05T21:55:38.103+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sensors'/><title type='text'>Movement patterns</title><content type='html'>This blog entry does not have associated example program, the samples of this post were taken using the sensor monitoring application &lt;a href="http://mylifewithandroid.blogspot.com/2010/04/monitoring-sensors-in-background.html"&gt;presented here&lt;/a&gt;. This time I will give a taste what can be achieved by using the acceleration sensor for identifying movement patterns. Previously we have seen that the acceleration sensor is pretty handy at &lt;a href="http://mylifewithandroid.blogspot.com/2010/03/sensors.html"&gt;measuring the orientation of the device relative to the Earth's gravity&lt;/a&gt;. At that discussion we were faced with the problem what if the device is subject to acceleration other than the Earth's gravity. In that measurement, it caused noise. For example if you move the device swiftly, you can force change between landscape and portrait mode even if the device's relative position to the Earth's surface is constant. That's because the acceleration of the device's movement is added to the Earth's gravity acceleration, distorting the acceleration vector.&lt;br /&gt;&lt;br /&gt;In this post, we will be less concerned about the device's orientation. We will focus on these added accelerations. Everyday movements have characteristic patterns and by analysing the samples produced by the acceleration sensor, &lt;a href="http://www.sfonge.com/webfm_send/5"&gt;cool context information &lt;/a&gt;can be extracted from the acceleration data.&lt;br /&gt;&lt;br /&gt;The acceleration sensor measures the acceleration along three axes. This 3D vector is handy when one wants to measure orientation of e.g. the gravity acceleration vector. When identifying movements, it is more useful to work with the absolute value of the acceleration because the device may change its orientation during the movement. Therefore we calculate&lt;br /&gt;&lt;br /&gt;ampl=sqrt( x^2 + y^2 + z^2 )&lt;br /&gt;&lt;br /&gt;where x,y,z are elements of the measured acceleration vector along the three axes. Our diagrams will show this ampl value.&lt;br /&gt;&lt;br /&gt;Let's start with something simple.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_K2nCeh0MHDY/S-HMWzeruKI/AAAAAAAABlg/hovhaor32iI/s1600/walking.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 275px;" src="http://2.bp.blogspot.com/_K2nCeh0MHDY/S-HMWzeruKI/AAAAAAAABlg/hovhaor32iI/s400/walking.png" alt="" id="BLOGGER_PHOTO_ID_5467876114752583842" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This is actually me walking at Waterloo station. The diagram shows the beginning of the walking. It starts with stationary position then I start to walk. In order to understand the pattern, we must remember that acceleration is change of the velocity &lt;span style="font-style: italic;"&gt;vector&lt;/span&gt;. This means that if we move quickly but our velocity does not change, we have 0 acceleration. On the other hand, if we move with constant speed but our direction changes, that's quite a significant change in the velocity vector. That is the source of the gravity acceleration, the Earth's surface (and we along with it) rotates with constant speed but as the speed is tangent to the Earth's surface, its direction constantly changes as the Earth rotates.&lt;br /&gt;&lt;br /&gt;The same goes for walking. Whenever the foot hits the ground, its velocity vector changes (moved down, starts to move up). Similarly, when the foot reaches the highest point, it moved up and then it starts to move down. The maximums and the minimums of the acceleration amplitude are at these points. As these accelerations are added to the Earth's acceleration (9.81 m/s^2), you will see an offset of about 10. The sine wave-like oscillation of the acceleration vector's absolute value is unmistakable. The signal is not exactly sine wave but the upper harmonics attenuate quickly. You can see that my walking caused about 1.2g fluctuation (-0.6 g to 0.6 g).&lt;br /&gt;&lt;br /&gt;Running is very similar except that the maximum acceleration is much higher - about 2 g peak-to peak.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/S-HM9TirH8I/AAAAAAAABlo/wAQQJyaxy88/s1600/running.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 278px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/S-HM9TirH8I/AAAAAAAABlo/wAQQJyaxy88/s400/running.png" alt="" id="BLOGGER_PHOTO_ID_5467876776194285506" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And now, everybody's favourite, the shaking. Shaking is very easy to identify. First, the peak acceleration is very high (about 3 g in our case). Also, the peaks are spike-like (very high upper harmonic content).&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/S-HNGxT625I/AAAAAAAABlw/bWp8c28qToQ/s1600/shake.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 278px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/S-HNGxT625I/AAAAAAAABlw/bWp8c28qToQ/s400/shake.png" alt="" id="BLOGGER_PHOTO_ID_5467876938804288402" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;In the following, I will present some algorithms that produce handy values when identifying these movements.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4340788309697104838?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4340788309697104838/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4340788309697104838' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4340788309697104838'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4340788309697104838'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/05/movement-patterns.html' title='Movement patterns'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_K2nCeh0MHDY/S-HMWzeruKI/AAAAAAAABlg/hovhaor32iI/s72-c/walking.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7417665220419303800</id><published>2010-04-22T20:51:00.005+02:00</published><updated>2011-02-07T12:00:47.368+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='background sampling'/><category scheme='http://www.blogger.com/atom/ns#' term='sensors'/><title type='text'>Monitoring sensors in the background</title><content type='html'>Once &lt;a href="http://mylifewithandroid.blogspot.com/2010/03/sensors.html"&gt;I got the taste of making all sorts of measurements with sensors&lt;/a&gt;, I happily started to collect samples. Then I quickly ran into the background sensor monitoring problem. The use case is very simple: the phone is in idle state, keyguard is locked but it collects and processes sensor data. For example I wanted to record acceleration sensor data while cycling. I started the sensor application, started the measurement and locked the keyguard. On the Nexus One it means that there will be no further sensor data delivered to the application until the screen is on.&lt;br /&gt;&lt;br /&gt;The source of the problem is that the sensor is powered down when the screen is off. This is implemented below the Android framework (either in the kernel driver or in hardware, I would be curious if anyone knows the answer) because if you recompile the latest sources from &lt;a href="http://android.git.kernel.org/"&gt;android.git.kernel.org&lt;/a&gt;, sensor data will be delivered nicely to the application in the emulator even if the keyguard is locked (the stock 2.1 emulator's Goldfish sensor does not emit any event, that is why you have to recompile from source) . The fact remains: if you want acceleration sensor data on the Nexus One, the screen must be on. This pretty much kills all the user-friendly applications that want to analyze sensor data in the background while the phone is idle. In the worst case, the screen must be constantly on (e.g. for a pedometer that needs to measure constantly because you never know when the user makes a step) but the situation for a simple context reasoner service is not much better. Such a service (&lt;a href="http://www.sfonge.com/epaper/extracting-motion-patterns-mobile-phone-sensors"&gt;read the vision paper for background, you need free registration to access&lt;/a&gt;)  may want to sample the sensors periodically (e.g. collecting 5 sec. of samples in every minute). In order to perform the sampling, the screen should be switched on for this duration which would result in a very annoying flashing screen and increased power consumption.&lt;br /&gt;&lt;br /&gt;You can observe these effects in the improved Sensors program that is able to capture sensor data in the background.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/sensors2.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Start and deploy this program and you will see the familiar sensor list. If you click on any list item, the old interactive display comes up. The improvement here is that the application measures the sensor sampling frequency. The rate can be set in the settings menu, from the main activity. If, however, you long-press on the list item, the sampling will be started in the background. In this case there is no interactive display of the samples, they are always saved into the /sdcard/capture.csv file. The background sampler does respect the sampling rate setting, however. The background sampler is implemented as a service.&lt;br /&gt;&lt;br /&gt;So far there is nothing extraordinary. You may observe the ScreenOffBroadcastReceiver class in SamplingService that intercepts ACTION_SCREEN_OFF intent broadcasts and does something weird in response. In order to understand its operation, you must be aware that the power manager deactivates all the wake locks that are stronger than PARTIAL_WAKE_LOCK when the keyguard powers the device down. Obtaining for example a SCREEN_BRIGHT_WAKE_LOCK in the broadcast receiver would be of no use because at this point the screen is still on and the wake lock would be deactivated when the screen is turned off. Instead, the broadcast receiver starts a thread, waits for 2 sec (experimental value) and then it activates the wake lock with wakeup option. Try to push the power button when the background sampling is on, the screen will go off for 2 seconds but then it is turned on again and you will see the keyguard screen. If you check the log, you will see that the sensor sampling stops for that 2 seconds then it comes back on. Don't worry, when the background sampling is stopped (by long-pressing the sensor's name in the list again) the screen will turn off correctly.&lt;br /&gt;&lt;br /&gt;Ugly? You bet. Not only the solution is a bad hack but it also prevents the device from switching off the screen and those AMOLED displays plus the high-powered processors eat battery power like pigs. The decision that the sensors are powered down when the screen is off prevents the implementation of some of the most exciting mobile applications and significantly decreases the value of the platform.&lt;br /&gt;&lt;br /&gt;But enough of the grunting. This is the state of the art in background sensor sampling in Android (as of version 2.1 of the platform), I will continue with signal processing algorithms.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7417665220419303800?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7417665220419303800/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7417665220419303800' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7417665220419303800'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7417665220419303800'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/04/monitoring-sensors-in-background.html' title='Monitoring sensors in the background'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-6761696387457155812</id><published>2010-04-14T18:46:00.002+02:00</published><updated>2010-04-14T19:03:11.892+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='android video tutorial'/><title type='text'>Android training in video format</title><content type='html'>I received an e-mail asking me to advertise another Android training material. And I do it because I have never seen an Android training in video format before.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://android.voxisland.com/"&gt;Here it is, lo and behold.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I have to admit, I am a bit skeptical. If there is a text material and video (e.g. on a news site), I always choose the text because I read pretty quickly. But I leave the decision for you. Try the free sample (increase the resolution and make it full screen, then it becomes readable) and if it works for you, consider subscribing to the course for an - IMHO - quite modest fee.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-6761696387457155812?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/6761696387457155812/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=6761696387457155812' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6761696387457155812'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6761696387457155812'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/04/android-training-in-video-format.html' title='Android training in video format'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-1044391274541137867</id><published>2010-04-11T22:12:00.006+02:00</published><updated>2011-02-07T11:57:39.395+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='widgets'/><title type='text'>Widget technologies on different mobile platforms</title><content type='html'>I wrote this paper for the &lt;a href="http://www.sfonge.com/knowledge_exchange"&gt;Sfonge.com knowledge exchange&lt;/a&gt;. This tries to put all sorts of "widget" technologies into context, including Android widgets (you need free registration to get this page).&lt;br /&gt;&lt;br /&gt;I could not figure out, how to place a direct link, just look for widget.pdf on the page. &lt;span style="font-weight: bold;"&gt;Update:&lt;/span&gt; &lt;a href="http://www.sfonge.com/epaper/widget-technologies-different-mobile-platforms"&gt;direct link to the document&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Back to the main issue: I found a nice confusion with regards to the widget term itself. Basically there are two schools: one interprets widget from the user's point of view, as a mini-app on a default screen, e.g. home screen, the other approaches from the technology point of view and considers widgets as locally installed web applications. There are interesting corner cases. For example Android widgets are full-featured widgets from the user's point of view but not widgets from the technology point of view because Android widgets are not based on web technologies. Blackberry widgets, on the other hand, are based on quite sophisticated web technology engine but they are always full-screen. This is not that an ordinary user would call widget.&lt;br /&gt;&lt;br /&gt;Anyway, I am looking forward to your comments either here or on the &lt;a href="http://www.sfonge.com/"&gt;Sfonge.com &lt;/a&gt;website. I will also get the direct link to the document, don't worry.&lt;br /&gt;&lt;br /&gt;On a personal note, I am back to Budapest from London. The 2-year UK vacation is over. Gee, I started this blog before I moved to London and now I am back. How time flies - particularly in the Android universe.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-1044391274541137867?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/1044391274541137867/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=1044391274541137867' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1044391274541137867'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1044391274541137867'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/04/widget-technologies-on-different-mobile.html' title='Widget technologies on different mobile platforms'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-8471434106660782795</id><published>2010-03-19T09:13:00.003+01:00</published><updated>2010-03-22T10:38:00.659+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='londroid'/><category scheme='http://www.blogger.com/atom/ns#' term='livefolder'/><category scheme='http://www.blogger.com/atom/ns#' term='feed'/><title type='text'>Londroid presentation: LiveFolders as feeds</title><content type='html'>My presentation at &lt;a href="http://www.meetup.com/android/calendar/11953629/"&gt;Londroid, 2010 March 18&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div style="width:425px" id="__ss_3475120"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/paller/livefolders-as-feeds" title="LiveFolders as feeds"&gt;LiveFolders as feeds&lt;/a&gt;&lt;/strong&gt;&lt;object width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=livefoldersasfeeds-100319030529-phpapp02&amp;stripped_title=livefolders-as-feeds" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=livefoldersasfeeds-100319030529-phpapp02&amp;stripped_title=livefolders-as-feeds" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/paller"&gt;Gabor Paller&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://skillsmatter.com/podcast/os-mobile-server/gabor-paller-pipe-programming-with-livefolders"&gt;Watch the podcast here.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-8471434106660782795?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/8471434106660782795/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=8471434106660782795' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8471434106660782795'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8471434106660782795'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/03/londroid-presentation-livefolders-as.html' title='Londroid presentation: LiveFolders as feeds'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-1318796849333615935</id><published>2010-03-12T21:14:00.007+01:00</published><updated>2010-03-15T22:36:15.147+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sensors'/><title type='text'>Sensors</title><content type='html'>Ever since I heard that Android devices come with a wide array of sensors, I have been excited about the possibilities. I am a firm believer of the ubiquitous computing vision and all the consequences it brings, including sensors that can be accessed wirelessly. Some parts of the vision (e.g. self-powering microsensors embedded into wallpaint) are still futuristic but mobile phones can be equipped with such sensors easily. Google has a strategy about sensor-equipped mobile devices &lt;a href="http://googlemobile.blogspot.com/2009/12/mobile-search-for-new-era-voice.html"&gt;where the sensor values are processed by powerful data centers&lt;/a&gt;. As I did not have an Android device before, I could not play with those sensors. Not anymore! (again, many thanks to those gentle souls who managed to get this device to me).&lt;br /&gt;&lt;br /&gt;Sensors are integral part of the Android user experience. Acceleration sensor makes sure that the screen layout changes to landscape when you turn the device (open an application that supports landscape layout, e.g. the calendar, keep the device in portrait mode, move the device swiftly sideways to the right and if you do it quick enough, you can force the display to change to landscape mode). Proximity sensor blanks the screen and switches on the keylock when the user makes a phonecall and puts the device to his or her ear so that the touchscreen is not activated accidentally. In some devices, temperature sensor monitors the temperature of the battery. The beauty of the Android programming model is that one can use all these sensors in one's application.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/sensors.zip"&gt;Click here to download the example program&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;A very similar application called SensorDump can be found on Android Market. Our example program is inferior to SensorDump in many respect but has a crucial feature: it can log sensor values into a CSV file that can be analysed later on (use the menu to switch the capture feature on and off). &lt;span style="font-weight: bold;"&gt;Update: from the 0.2.0 version of SensorDump, sensor data logging into CSV file is available.&lt;/span&gt; This is not much important with e.g. the proximity sensor which provides binary data but I don't believe one can understand at a glance, what goes on with the e.g. accelerator sensor during a complex movement just by looking at the constantly changing numbers on the device screen.&lt;br /&gt;&lt;br /&gt;I can see the following sensors on my Nexus One.&lt;br /&gt;&lt;br /&gt;BMA150 - 3-axis accelerometer&lt;br /&gt;AK8973 - 3-axis Magnetic field sensor&lt;br /&gt;CM3602 - Light sensor&lt;br /&gt;&lt;br /&gt;Some sensors are projected as more than one logical sensor, for example the AK8973 is also presented as an orientation sensor and the CM3602 as the proximity sensor. This is just software, however, these duplicate logical sensors use the same sensor chip but present the sensor data in different format.&lt;br /&gt;&lt;br /&gt;Let's start with the most popular sensor, the accelerometer. This measures the device's acceleration along the 3 axis. A logical but somewhat unintuitive property of this sensor is that the zero point is in free fall - otherwise the Earth's gravity acceleration is always present. If the device is not subject to any other acceleration (the device is stationary or moves with constant speed), the sensor measures the gravity acceleration that points toward the center of the Earth. This is commonly used to measure the roll and the pitch of the device, try the excellent Labyrinth Lite game on Android Market if you want a demonstration.&lt;br /&gt;&lt;br /&gt;The graph below shows sensor data in two scenarios (note that all the data series can be found in the download bundle under the /measurements directory). The red dots show the value of the accelerometer when the device was turned from horizontal position to its side, right edge pointing to the Earth. The green dots show the sensor values when the device was tilted toward its front edge so that at the end the upper edge pointed toward the Earth.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/S5qhNyvR8WI/AAAAAAAABk4/4nuGtwJdC_Y/s1600-h/accel_turns.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 225px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/S5qhNyvR8WI/AAAAAAAABk4/4nuGtwJdC_Y/s400/accel_turns.JPG" alt="" id="BLOGGER_PHOTO_ID_5447843957588488546" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This is all beautiful but don't forget that the acceleration sensor eventually measures acceleration. If the device is subject to any acceleration other than the gravity acceleration (remember the experiment with the portrait-landscape mode at the beginning of the post), that acceleration is added to the gravity acceleration and distorts the sensor's data (provided that you want to measure the roll-pitch of the device). The following graph shows the accelerometer values when the device was laying on the table but I flicked it. The device accelerated on the surface of the table and the smaller blue dot shows the value the accelerometer measured when this happened. As if the device was tilted to the right.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_K2nCeh0MHDY/S5qhZXrZERI/AAAAAAAABlA/gTeEntO4jLA/s1600-h/accel_flick.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 225px;" src="http://3.bp.blogspot.com/_K2nCeh0MHDY/S5qhZXrZERI/AAAAAAAABlA/gTeEntO4jLA/s400/accel_flick.JPG" alt="" id="BLOGGER_PHOTO_ID_5447844156482851090" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The second sensor is the magnetic field sensor, the compass. As far as I know, this sensor is not used for anything by the base Android applications, it is all the more popular for all sorts of compass applications. The magnetic sensor measures the vector of the magnetic field of the Earth, represented in the device's coordinate system. In 3D, this points toward the magnetic north pole, into the crust of the Earth. The following graph shows the scenario when the device was laying on the table but was rotated in a full circle on the surface of the table.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/S5qhlO3X0TI/AAAAAAAABlI/TjiDMYat2iw/s1600-h/magnetic_fullrotation.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 225px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/S5qhlO3X0TI/AAAAAAAABlI/TjiDMYat2iw/s400/magnetic_fullrotation.JPG" alt="" id="BLOGGER_PHOTO_ID_5447844360275611954" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Even though the magnetic sensor is not subject to some unwanted acceleration like the accelerometer, it is subject to the influence of metal objects. The following graph shows the values of the magnetic sensor when the device was laying on the table but after a while I put a small pair of scissors on top of the device. You can see that there are two clusters of sensor values: one with the scissors, one without.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_K2nCeh0MHDY/S5qhxuqUNfI/AAAAAAAABlQ/ypseSbRiYto/s1600-h/magnetic_scissor.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 225px;" src="http://3.bp.blogspot.com/_K2nCeh0MHDY/S5qhxuqUNfI/AAAAAAAABlQ/ypseSbRiYto/s400/magnetic_scissor.JPG" alt="" id="BLOGGER_PHOTO_ID_5447844574969214450" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The third sensor is the light sensor that doubles as proximity detector. The light sensor is more evident but the proximity detector deserves some explanation. The proximity detector is really a light sensor with binary output. If blocked, it emits 0.0, otherwise it emits 1.0. The photo belows demonstrates the location of the sensor and how to block it.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/S5qh7yxYzUI/AAAAAAAABlY/LFfMHy1aaB0/s1600-h/proximitysensor.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 221px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/S5qh7yxYzUI/AAAAAAAABlY/LFfMHy1aaB0/s400/proximitysensor.JPG" alt="" id="BLOGGER_PHOTO_ID_5447844747871309122" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-1318796849333615935?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/1318796849333615935/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=1318796849333615935' title='33 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1318796849333615935'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1318796849333615935'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/03/sensors.html' title='Sensors'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/S5qhNyvR8WI/AAAAAAAABk4/4nuGtwJdC_Y/s72-c/accel_turns.JPG' height='72' width='72'/><thr:total>33</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-5727726465348046413</id><published>2010-03-05T23:01:00.003+01:00</published><updated>2010-03-05T23:05:27.195+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cwac'/><category scheme='http://www.blogger.com/atom/ns#' term='expandable list'/><title type='text'>Expandable list with CWAC</title><content type='html'>I received a comment at the &lt;a href="http://mylifewithandroid.blogspot.com/2010/03/progressively-loading-listviews.html"&gt;expandable list adapter &lt;/a&gt;post that I should take a look at the &lt;a href="http://github.com/commonsguy/"&gt;CommonsWare Android Components (CWAC) &lt;/a&gt;project. CWAC aims to provide off-the-shelf components for Android programmers which is a very interesting proposition. In particular, the EndlessAdapter does pretty much the same as my example program at the post, except that the CWAC component offers a nice API to programmers while my program is nothing more than an example from which parts can be copied into somebody else's code.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/pagelist2.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Still, I think it is interesting to share my experience with &lt;a href="http://github.com/commonsguy/cwac-endless"&gt;CWAC EndlessAdapter&lt;/a&gt; because I reimplemented my example program using the EndlessAdapter. The largest - and, IMHO, most annoying - difference is that EndlessAdapter does the caching from the slow data source and the updating of the list in two separate stages. This means that if an application decides to cache more than one item, those items will not appear in the list until all the items of the particular batch were loaded. If you execute the example program, you will see batches of 5 items appearing (because the code preloads 5 items at once). EndlessAdapter really leaves only one other option open: that only one item is cached. This is less user-friendly, however, because the user has to wait for each item when the list is scrolled down. So I think the component should definitely support list update while a larger batch of items is being fetched from the data source. This would require change of the component API.&lt;br /&gt;&lt;br /&gt;The other difference is more like a matter of taste. Personally, I found combining the loading and real data widget into the same row layout and fiddling with the visibility hard to maintain. My test program used a separate row layout for the loading widget which is easier to maintain but is less efficient.&lt;br /&gt;&lt;br /&gt;Anyway, my goal with this post was to advertise the CWAC project because I believe that the Android component market is an important. one. That's true even &lt;a href="http://mylifewithandroid.blogspot.com/2010/01/jars-on-classpath.html"&gt;if Android's component support could be better&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-5727726465348046413?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/5727726465348046413/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=5727726465348046413' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5727726465348046413'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5727726465348046413'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/03/expandable-list-with-cwac.html' title='Expandable list with CWAC'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-2070945466512368101</id><published>2010-03-04T22:04:00.002+01:00</published><updated>2010-03-04T22:08:54.940+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='list'/><category scheme='http://www.blogger.com/atom/ns#' term='styles'/><category scheme='http://www.blogger.com/atom/ns#' term='tablelayout'/><title type='text'>Two-dimensional lists</title><content type='html'>I got another question whether TableLayout can be used like a two-dimensional ListView. My initial reaction was that it cannot be done because TableLayout just arranges items, all the complicated focusing/highlighting logic in ListView is completely missing from TableLayout. I realized later on, however, that TableLayout can contain Buttons and the rest will be arranged by the general focus handling logic in ViewGroup.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/tablelist.zip"&gt;Click here to download the example program&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The only exciting part of this rather simple program is the way how an array of Buttons are made to look like selectable list items. The key is that the Buttons are styled. For example (XML fragments are mangled due to blog engine limitations):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[Button android:id="@+id/button_1"&lt;br /&gt;         style="@style/MyButton"&lt;br /&gt;         android:text="1"/]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The referred style is in res/values/styles.xml. The funny part is this line:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[item name="android:background"]@android:drawable/list_selector_background[/item]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This line was copied straight from the system style of List.View and applies the ListView selector (that defines appearance of list elements in ListView in their different states) to the Button. No wonder the Button behaves like a list element.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/S5AhLzMv2kI/AAAAAAAABko/1whV_V0_AuQ/s1600-h/tablelist.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 244px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/S5AhLzMv2kI/AAAAAAAABko/1whV_V0_AuQ/s320/tablelist.JPG" alt="" id="BLOGGER_PHOTO_ID_5444888436096883266" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-2070945466512368101?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/2070945466512368101/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=2070945466512368101' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2070945466512368101'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2070945466512368101'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/03/two-dimensional-lists.html' title='Two-dimensional lists'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/S5AhLzMv2kI/AAAAAAAABko/1whV_V0_AuQ/s72-c/tablelist.JPG' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-393636665997148936</id><published>2010-03-02T21:49:00.003+01:00</published><updated>2010-03-02T21:54:13.991+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adapter'/><category scheme='http://www.blogger.com/atom/ns#' term='list'/><title type='text'>Progressively loading ListViews</title><content type='html'>Again I received a mail from somebody who asked for lists with forward/backward paging option. These lists are not trivial to implement but I was more intrigued by the need why anyone would want such a clumsy UI construct. Then it turned out that the guy wanted to back the list with data from a web service. Obviously, he did not want to wait until the entire list is loaded through the network but wanted to present something to the user as soon as possible.&lt;br /&gt;&lt;br /&gt;Such a list can be found in Android Market when the list of applications is progressively loaded from the network server. The problem is worth a simple test program to analyse because it clearly shows the power of the Android adapter concept.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/pagelist.zip"&gt;Click here to download the test program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Our test application does not actually connect to the network but simulates the slow data source. If you check the Datasource class, you will find that even though this "data source" is a simple array, the accessor method adds a 500 ms delay to every access. This simulates well the fact that loading from certain data sources takes time. The trick is in the PageAdapter class. This class adds a View to the end of the list ("Loading ..."). Android Market animates this view but the idea is the same. Whenever this View is drawn, the adapter kicks the loading thread which progressively fetches new elements from the data source, adds them to the Adapter's data set and notifies the adapter that the data set has changed. There is the usual fiddling with a Handler to make sure that notifyDataSetChanged() is invoked in the context of the UI thread. The Adapter constantly changes the value returned by the getCount() method as the items are loaded from the data source. When the whole data set is loaded, the "Loading ..." widget is not drawn anymore therefore nothing triggers the loading thread.&lt;br /&gt;&lt;br /&gt;Here is how it looks like.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/S416ZZgY7-I/AAAAAAAABkY/WpHtpOirncs/s1600-h/pagelist.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 284px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/S416ZZgY7-I/AAAAAAAABkY/WpHtpOirncs/s320/pagelist.JPG" alt="" id="BLOGGER_PHOTO_ID_5444142101322854370" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;No, wait a minute, here is how it looks like.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/S416mlBPNlI/AAAAAAAABkg/81l2lcQyxJo/s1600-h/pagelist_nexus.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 240px; height: 320px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/S416mlBPNlI/AAAAAAAABkg/81l2lcQyxJo/s320/pagelist_nexus.JPG" alt="" id="BLOGGER_PHOTO_ID_5444142327751718482" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Yes! After having blogged for more than 2 years about Android, I finally put my hands on an actual Android phone. The story is complicated but the essence is that a friend thought that I deserved that phone and gave it to me. Thanks for everyone involved!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-393636665997148936?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/393636665997148936/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=393636665997148936' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/393636665997148936'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/393636665997148936'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/03/progressively-loading-listviews.html' title='Progressively loading ListViews'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/S416ZZgY7-I/AAAAAAAABkY/WpHtpOirncs/s72-c/pagelist.JPG' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3268997264559952483</id><published>2010-02-13T22:59:00.007+01:00</published><updated>2010-12-05T13:07:41.436+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='checkbox'/><category scheme='http://www.blogger.com/atom/ns#' term='expandable list'/><title type='text'>Expandable lists and check boxes</title><content type='html'>I got another trivial question in a comment: how to add checkboxes to an expandable list view like &lt;a href="http://mylifewithandroid.blogspot.com/2008/05/expandable-lists.html"&gt;this one&lt;/a&gt;? Nothing can be simpler, you just add CheckBox control to the child view that forms the row and that's it. Except that it does not work without some tweaks.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/elistcbox.zip"&gt;You can download the example program from here.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/S3clj66gH3I/AAAAAAAABkI/nFNRFogY_e4/s1600-h/elistcbox.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 266px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/S3clj66gH3I/AAAAAAAABkI/nFNRFogY_e4/s320/elistcbox.JPG" alt="" id="BLOGGER_PHOTO_ID_5437856374113443698" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The first bizarre thing you can notice in res/layout/child_row.xml that the CheckBox is made non-focusable. Why to do that when we want the checkbox to capture "click" events beside "touch" events? There is a complicated answer already presented in &lt;a href="http://mylifewithandroid.blogspot.com/2009/10/lists-and-focuses.html"&gt;this post&lt;/a&gt;. The ViewGroup encapsulating the list row (the LinearLayout in res/layout/child_row.xml) must retain the focus otherwise we don't get onChildClick events (see ElistCBox.java).&lt;br /&gt;&lt;br /&gt;This would solve the problem for a Button but not for a CheckBox. Actually, I don't know why the Button behaves correctly and the CheckBox does not. My experience is that even if CheckBox has the focus, clicking on the row of the CheckBox does not toggle its state. I am curious if anyone can provide an explanation, here I just record the fact. Remove the android:focusable="false" line from the CheckBox element in child_row.xml and observe, that you can click on the highlighted row as much as you like but the CheckBox does not toggle. That's why I implemented it by "hand" - I took away the focus from the CheckBox, this makes the child row deliver onChildClick events then I toggled the state of the CheckBox programmatically. If anyone has a better solution, I would be deeply interested.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update&lt;/span&gt;: a discussion started in the comment field regarding the erratic behaviour of check boxes in this example program. See&lt;a href="http://mylifewithandroid.blogspot.com/2010/12/expandable-list-and-checkboxes.html"&gt; this blog post&lt;/a&gt; for further explanation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3268997264559952483?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3268997264559952483/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3268997264559952483' title='33 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3268997264559952483'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3268997264559952483'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/02/expandable-lists-and-check-boxes.html' title='Expandable lists and check boxes'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/S3clj66gH3I/AAAAAAAABkI/nFNRFogY_e4/s72-c/elistcbox.JPG' height='72' width='72'/><thr:total>33</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4749337257486151957</id><published>2010-01-20T14:20:00.002+01:00</published><updated>2010-01-20T14:23:09.170+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dex jar libs'/><title type='text'>JARs on the classpath</title><content type='html'>Directory tree of a typical Android project (at least those created by the "android create project" command) looks like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/S1cDbtAMueI/AAAAAAAABkA/yvGyri-0WUM/s1600-h/libsexample.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 257px; height: 187px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/S1cDbtAMueI/AAAAAAAABkA/yvGyri-0WUM/s320/libsexample.JPG" alt="" id="BLOGGER_PHOTO_ID_5428811650290203106" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;As you may have seen the directory structure of countless J2SE projects, there is a directory to store the 3rd party class libraries of the project. Under the "lib" directory, you can place your Java class libraries in JAR format and they will be added to the classpath when the Android application is running.&lt;br /&gt;&lt;br /&gt;Or will they? &lt;a href="http://mylifewithandroid.blogspot.com/2009/12/understanding-dalvik-bytecode-with.html"&gt;If you remember the APK format&lt;/a&gt;, there is no such thing as directory for libs in JAR format, particularly because the Dalvik VM is simply not able to execute ordinary Java class files in those JARs. You can find JARs on the device but these JARs contain class files in DEX format (Dalvik VM's class file format) inside so the extension of these files is misleading. Then where are those JARs from the lib directory that we "added to the classpath"?&lt;br /&gt;&lt;br /&gt;You might have guessed it: these JARs are unpacked, processed by the dx tool just like the classes under the src directory and are placed into the same classes.dex file where the application resides. One application is one DEX file and JARs containing Java class files are not "added to the classpath", they are converted into DEX format and added to the DEX file of the application. Unlike in OSGi, an Android installable package cannot share code with other system components, only services.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4749337257486151957?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4749337257486151957/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4749337257486151957' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4749337257486151957'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4749337257486151957'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/01/jars-on-classpath.html' title='JARs on the classpath'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_K2nCeh0MHDY/S1cDbtAMueI/AAAAAAAABkA/yvGyri-0WUM/s72-c/libsexample.JPG' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4985035690744566847</id><published>2010-01-16T23:44:00.003+01:00</published><updated>2010-01-16T23:49:14.748+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='aidl'/><category scheme='http://www.blogger.com/atom/ns#' term='services'/><category scheme='http://www.blogger.com/atom/ns#' term='oneway'/><title type='text'>Oneway interfaces</title><content type='html'>In early betas, the Android IPC was strictly synchronous. This means that service invocations had to wait for the return value of the remote method to arrive back to the caller. This is generally an advantage because the caller can be sure that the called service received the invocation by the time the remote method returns. In some cases, however, this causes the caller to wait unnecessarily. If synchronicity is not required and the method has no return value, oneway AIDL interfaces may be used.&lt;br /&gt;&lt;br /&gt;Oneway methods are specified by addind the oneway keyword to the AIDL interface definition.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:Courier New;"&gt;oneway interface IncreaseCounter {&lt;/span&gt;  &lt;span style="font-family:Courier New;"&gt;    void increaseCounter( int diff );&lt;/span&gt;  &lt;span style="font-family:Courier New;"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Only the entire interface can be oneway and these methods must all have void return value. The stub compiled from oneway AIDL interface does not have return path for remote methods on the service side and does not wait for the method to execute on the client side. The delivery is reliable (no invocations are lost) but the timing is not guaranteed, e.g. two sequential oneway invocations may arrive at the invoked service in different order. Oneway interfaces are therefore more complicated to use (they are also faster and don't block the caller) and are used extensively by the Android framework internally to deliver event notifications.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/oneway.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/S1JB9xaV2cI/AAAAAAAABj4/FVlLmJOcj2E/s1600-h/oneway.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 183px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/S1JB9xaV2cI/AAAAAAAABj4/FVlLmJOcj2E/s320/oneway.JPG" alt="" id="BLOGGER_PHOTO_ID_5427473030426515906" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Our primitive example has an Activity and a Service. The service exposes two interfaces: one "normal" (Counter.aidl) and one oneway (increaseCounter.aidl). The interesting bit here is how one service can expose two interfaces. The onBind method checks the Intent used to bind the service and returns different binders based on the Intent. The important point here not to use the Intent extra Bundle to differentiate among interfaces. I did this and I can confirm that even though extras arrive at onBind (the API documentation states the contrary) but the framework gets completely confused and thinks that the service has already been bound with the same Intent (the framework seemingly cannot figure out that the Intent extras were different). In the example program I abused the category field therefore and this works nicely.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4985035690744566847?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4985035690744566847/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4985035690744566847' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4985035690744566847'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4985035690744566847'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/01/oneway-interfaces.html' title='Oneway interfaces'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/S1JB9xaV2cI/AAAAAAAABj4/FVlLmJOcj2E/s72-c/oneway.JPG' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-2816712590330773404</id><published>2010-01-10T23:15:00.002+01:00</published><updated>2010-01-10T23:18:00.920+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='remoteviews'/><category scheme='http://www.blogger.com/atom/ns#' term='app widgets'/><title type='text'>RemoteViews as hypertext document</title><content type='html'>In the previous blog entry I tried to demonstrate, how a web programming construct, the feed can be implemented using LiveFolders. In this entry, I would like to highlight another relatively obscure feature of the Android API, the RemoteViews widget and argue that RemoteViews are similar in nature to another web programming construct, the hypertext document itself.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://developer.android.com/reference/android/widget/RemoteViews.html"&gt;RemoteViews&lt;/a&gt; is the key mechanism behind the AppWidget feature of the Android platform. There are many good introductions to AppWidgets (like &lt;a href="http://developer.android.com/guide/topics/appwidgets/index.html"&gt;this&lt;/a&gt; or &lt;a href="http://android-developers.blogspot.com/2009/04/introducing-home-screen-widgets-and.html"&gt;this&lt;/a&gt;) so I will be short. AppWidget is a representation of an Android application on the home screen. The application can decide, what representative information it publishes on the home screen. As the screen estate is limited, the representation is obviously condensed, e.g. an application executing a long-running task can publish the progress indicator as home screen widget. The technical problem is that it is not evident, how to embed functionality (even condensed functionality) from another running application into the Launcher, the default home screen provider. If Launcher delegated the refreshing/redrawing of the views embedded from another application to that application, then malfunction of that application could disrupt the Launcher itself - something to be definitely avoided as the home screen is the last refuge of the users if something goes wrong with apps. Different platforms apply similar strategy. The idea is that the home screen application does not invoke other executables when it draws the home screen, it rather uses some sort of description of the widget UI in data format and displays that. For example that is the reason &lt;a href="http://www.forum.nokia.com/Technology_Topics/Web_Technologies/Web_Runtime/"&gt;Symbian forces the programmer to implement widgets as HTML/JavaScript applications&lt;/a&gt; and that is the reason Android decoupled the applications from the Launcher view structure&lt;br /&gt;&lt;br /&gt;The workhorse behind Android AppWidgets is the RemoteViews widget. RemoteViews is a parcelable data structure (so it can be sent through Android IPC) that captures an entire view structure. The creator of the RemoteViews sets up this data structure, by specifying the base layout and setting field values. This has no effect on the UI and therefore background components like services, broadcast event handlers, etc. can also do it. In fact, AppWidgets are broadcast event receivers, they process broadcasts from Launcher and respond with RemoteViews. When the RemoteViews data structure is received and deserialized, it can be applied to any ViewGroup of the UI and can therefore appear on the UI of another application. From this point of view, it works just like a hypertext document except that RemoteViews describes Android views and not general web controls.&lt;br /&gt;&lt;br /&gt;RemoteViews is a powerful construct that can be easily used outside of the context of AppWidgets. I present now a simple Activity and a Service where the Service provides an entire formatted View (and not just the data) that can be displayed easily by the Activity. Even though this is against the Big Book of service-oriented architectures (services should provide raw data and the consumers should care about formatting), there are lot of use cases for this construct. For example the output data of the service may not be so easy to display (e.g. in case of a map application) or you would like to tie the output of the service with advertisement, logo or link to your main application (a PendingIntent in Android parlance). In this case your Service (broadcast receiver, etc.) may just return the View structure you would like the client application to display. In the web world, web widgets are formatted mini webpages to be embedded into other web pages, hence the similarity of this web technology with RemoteViews.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/remoteview.zip"&gt;Click here to download the example program.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;There are three points to note here.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Check ITimeViewService.aidl under the src/aexp/remoteview directory and see how the remote method returns a RemoteViews instance.&lt;/li&gt;&lt;li&gt;Check TimeViewService.java and see how the RemoteView is constructed and updated. To make the point of returning formatted views as opposed to raw data, I added two formatted TextViews and a ProgressBar embedded into two LinearLayouts to the view structure returned by the service.&lt;/li&gt;&lt;li&gt;Check RemoteView.java and see how the RemoteView is applied to the view structure of that Activity. Everything under the "Get view data" button comes from the service when the RemoteViews is inflated into the view structure of the Activity.&lt;br /&gt; &lt;/li&gt;&lt;/ul&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_K2nCeh0MHDY/S0pRy26Kq5I/AAAAAAAABjw/C7L3iKAASOA/s1600-h/remoteviews.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 166px;" src="http://2.bp.blogspot.com/_K2nCeh0MHDY/S0pRy26Kq5I/AAAAAAAABjw/C7L3iKAASOA/s320/remoteviews.JPG" alt="" id="BLOGGER_PHOTO_ID_5425238635295976338" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;RemoteViews has disadvantages, however. It cannot serialize any Views, e.g. it cannot serialize an EditText view. I am aware of only&lt;a href="http://developer.android.com/guide/topics/appwidgets/index.html"&gt; this article &lt;/a&gt;about the restrictions of views the RemoteViews can serialize/deserialize.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-2816712590330773404?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/2816712590330773404/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=2816712590330773404' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2816712590330773404'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2816712590330773404'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2010/01/remoteviews-as-hypertext-document.html' title='RemoteViews as hypertext document'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_K2nCeh0MHDY/S0pRy26Kq5I/AAAAAAAABjw/C7L3iKAASOA/s72-c/remoteviews.JPG' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4130410576170295708</id><published>2009-12-31T01:57:00.005+01:00</published><updated>2009-12-31T02:05:09.535+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='livefolder'/><category scheme='http://www.blogger.com/atom/ns#' term='feed'/><title type='text'>LiveFolders as feeds</title><content type='html'>LiveFolders exist in Android since version 1.5. The mechanism was advertised as a lead feature of Android 1.5/1.6 but somehow it failed to take hold. There are excellent introductions to LiveFolders, for example &lt;a href="http://android-developers.blogspot.com/2009/04/live-folders.html"&gt;this one&lt;/a&gt; so I cut short here. Folder is an element of the Android desktop (technically, the Launcher application) that contains "links" to e.g. Android applications. More exactly, these "links" fire Intents and those intents can launch applications, open data sources, etc. A LiveFolder is different from a normal folder in that the content of the folder (folder items and their graphical representations) is not statically stored but is generated dynamically by a ContentProvider. There is a discovery mechanism based on Activities handling the android.intent.action.CREATE_LIVE_FOLDER intent. The Launcher discovers all the Activites handling this intent, invokes them by sending the intent, in response these Activities return the URIs of the LiveFolders' ContentProviders. If the user creates an icon for a LiveFolder and opens this icon, the Launcher will know the URI of the ContentProvider behind the LiveFolder, will list its content (as icon grid or list) and allow the user to activate an item in the LiveFolder which will fire an intent, opening a contact for example.&lt;br /&gt;&lt;br /&gt;So far so good but I would like you to think a bit further. So we have these LiveFolders that&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Allow applications to produce items of interest&lt;/li&gt;&lt;li&gt;And these items of interest, when "clicked", activate other actions.&lt;/li&gt;&lt;/ul&gt; Doesn't is sound familiar? Feeds in web programming are just like that. Android UI already has some elements of web programming (like the Back button) and now it has feeds. Feeds are not only fashionable nowadays but have deep roots in dataflow programming (&lt;a href="http://pallergabor.uw.hu/common/pgdiss.pdf"&gt;here is a short introduction if you want to know more&lt;/a&gt;) and clever application frameworks like &lt;a href="http://pipes.yahoo.com/pipes/"&gt;Yahoo Pipes&lt;/a&gt; were built around them.&lt;br /&gt;&lt;br /&gt;Sadly, exploiting this possibility is not easy now. The only LiveFolder client I am aware of is Launcher itself and aggregating/manipulating feeds is not easy. I am attempting to do just that in this blog post. I will show you how to create a LiveFolder of non-starred contacts of two existing LiveFolders: All contacts and starred contacts.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/livefolderaggregator.zip"&gt;Click here to download the live folder aggregating application&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Did you know that you can star contacts? I did not but there is a LiveFolder for starred contacts.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_K2nCeh0MHDY/Szv3uHmgHSI/AAAAAAAABjI/yxw1CSmkJHQ/s1600-h/livefolders_starred.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 165px;" src="http://2.bp.blogspot.com/_K2nCeh0MHDY/Szv3uHmgHSI/AAAAAAAABjI/yxw1CSmkJHQ/s320/livefolders_starred.JPG" alt="" id="BLOGGER_PHOTO_ID_5421198948157103394" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Install the application. Now if you select the Add menu on the Launcher main screen, select Folders, you will see the Nonstarred folder. Add that folder and it appears on the home screen. If you open that folder, you will see all non-starred contacts in it. If you click any such contact, the Contact application is opened and the contact is displayed.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_K2nCeh0MHDY/Szv4F4tq5LI/AAAAAAAABjQ/-rszDI8N7CQ/s1600-h/livefolders_addfolder.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 252px; height: 320px;" src="http://3.bp.blogspot.com/_K2nCeh0MHDY/Szv4F4tq5LI/AAAAAAAABjQ/-rszDI8N7CQ/s320/livefolders_addfolder.JPG" alt="" id="BLOGGER_PHOTO_ID_5421199356477498546" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/Szv4Q36j46I/AAAAAAAABjY/OCKy4eanRdo/s1600-h/livefolders_desktop.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 266px; height: 143px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/Szv4Q36j46I/AAAAAAAABjY/OCKy4eanRdo/s320/livefolders_desktop.JPG" alt="" id="BLOGGER_PHOTO_ID_5421199545241691042" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/Szv4ZeUm5WI/AAAAAAAABjg/K1DdCZ2zpfg/s1600-h/livefolders_nonstarred.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 249px; height: 320px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/Szv4ZeUm5WI/AAAAAAAABjg/K1DdCZ2zpfg/s320/livefolders_nonstarred.JPG" alt="" id="BLOGGER_PHOTO_ID_5421199692990440802" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;We generate the non-starred LiveFolder from to other LiveFolders. The application has two parts, an Activity that is invoked by Launcher to discover the LiveFolder. This is the part of the application that itself discovers other LiveFolders to retrieve the URIs of the two source LiveFolders. These URIs are then passed to the ContentProvider that aggregates the two source LiveFolders into a third one. The interesting part is the fake Cursor we return to the LiveFolder client which really accesses in-memory data and not SQLite database as it is usual with Android applications.&lt;br /&gt;&lt;br /&gt;As a bonus track, I share with you a simple LiveFolder client that helps you to discover other LiveFolders.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/selectlivefolder.zip"&gt;Click here to download the live folder client application&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_K2nCeh0MHDY/Szv4mHEo3GI/AAAAAAAABjo/o1e8dSP9BWQ/s1600-h/livefolders_selectlivefolder.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 219px; height: 320px;" src="http://2.bp.blogspot.com/_K2nCeh0MHDY/Szv4mHEo3GI/AAAAAAAABjo/o1e8dSP9BWQ/s320/livefolders_selectlivefolder.JPG" alt="" id="BLOGGER_PHOTO_ID_5421199910087744610" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;So far so good but I am not satisfied. In order to achieve the ease of manipulating RSS feeds in web programming, more automation is necessary. I intend to do it later, after I experimented with another idea.&lt;starred contacts=""&gt;&lt;folders&gt;&lt;home&gt;&lt;nonstarred&gt;&lt;/nonstarred&gt;&lt;/home&gt;&lt;/folders&gt;&lt;/starred&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4130410576170295708?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4130410576170295708/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4130410576170295708' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4130410576170295708'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4130410576170295708'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/12/livefolders-as-feeds.html' title='LiveFolders as feeds'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_K2nCeh0MHDY/Szv3uHmgHSI/AAAAAAAABjI/yxw1CSmkJHQ/s72-c/livefolders_starred.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-118121271677175723</id><published>2009-12-02T21:06:00.003+01:00</published><updated>2009-12-09T23:02:35.351+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dedexer'/><category scheme='http://www.blogger.com/atom/ns#' term='Dalvik'/><title type='text'>Understanding the Dalvik bytecode with the Dedexer tool</title><content type='html'>See my &lt;a href="http://skillsmatter.com/event/os-mobile-server/droidcon-london-2009"&gt;Droidcon London 2009&lt;/a&gt; presentation below as SlideShare presentation or &lt;a href="http://pallergabor.uw.hu/common/understandingdalvikbytecode.pdf"&gt;download it from here&lt;/a&gt;. Watch &lt;a href="http://skillsmatter.com/podcast/os-mobile-server/understanding-android-bytecode-with-the-dedexer-tool"&gt;the podcast here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="width:425px;text-align:left" id="__ss_2634903"&gt;&lt;a style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" href="http://www.slideshare.net/paller/understanding-the-dalvik-bytecode-with-the-dedexer-tool" title="Understanding the Dalvik bytecode with the Dedexer tool"&gt;Understanding the Dalvik bytecode with the Dedexer tool&lt;/a&gt;&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=understandingdalvikbytecode-091202135003-phpapp01&amp;stripped_title=understanding-the-dalvik-bytecode-with-the-dedexer-tool" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=understandingdalvikbytecode-091202135003-phpapp01&amp;stripped_title=understanding-the-dalvik-bytecode-with-the-dedexer-tool" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;"&gt;View more &lt;a style="text-decoration:underline;" href="http://www.slideshare.net/"&gt;documents&lt;/a&gt; from &lt;a style="text-decoration:underline;" href="http://www.slideshare.net/paller"&gt;Gabor Paller&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-118121271677175723?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/118121271677175723/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=118121271677175723' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/118121271677175723'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/118121271677175723'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/12/understanding-dalvik-bytecode-with.html' title='Understanding the Dalvik bytecode with the Dedexer tool'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-41770824556087142</id><published>2009-11-23T22:46:00.002+01:00</published><updated>2009-11-23T22:58:40.069+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dedexer'/><category scheme='http://www.blogger.com/atom/ns#' term='odex'/><title type='text'>Droidcon+ODEX file disassembly</title><content type='html'>First, the advertisement. I will make a longer presentation at &lt;a href="http://skillsmatter.com/event/os-mobile-server/droidcon-london-2009"&gt;Droidcon London 2009&lt;/a&gt; about Dalvik bytecode in general, using Dedexer examples. This will be a longer version of my &lt;a href="http://tinyurl.com/yg2lmz8"&gt;previous, short presentation&lt;/a&gt; (also &lt;a href="http://skillsmatter.com/podcast/java-jee/understanding-android-bytecode"&gt;in podcast&lt;/a&gt;). If central London is convenient for you, please, come. Otherwise I will share the presentation after the event.&lt;br /&gt;&lt;br /&gt;To celebrate the event, I finished the symbolic ODEX disassembly feature in &lt;a href="http://dedexer.sourceforge.net/"&gt;Dedexer&lt;/a&gt; (look for version 1.8). This means that instead of ugly offsets, Dedexer now correctly decompiles the method and field names for execute-inline, iget/iput-quick and nvoke-virtual-quick instruction families if the dependency files are available. So instead of this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;.line 3041&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         invoke-virtual-quick    {v5},vtable #0x2c&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         move-result-object      v2&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; .line 3042&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         iget-object-quick       v3,v5,[obj+0x28]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         invoke-virtual-quick    {v3},vtable #0xe&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         move-result-object      v0&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; .line 3043&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         execute-inline  {v2},inline #0x4&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         move-result     v1&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;You will get this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;.line 3041&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         invoke-virtual-quick    {v5},android/app/Activity/android/app/Activity/getPackageName   ; getPackageName()Ljava/lang/String; , vtable #0x2c&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         move-result-object      v2&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; .line 3042&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         iget-object-quick       v3,v5,mComponent Landroid/content/ComponentName; ;[obj+0x28]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         invoke-virtual-quick    {v3},android/content/ComponentName/android/content/ComponentName/getClassName   ; getClassName()Ljava/lang/String; , vtable #0xe&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         move-result-object      v0&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; .line 3043&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         execute-inline  {v2},Ljava/lang/String/length   ; length()I , inline #0x4&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         move-result     v1&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Much better, isn't it? See you at Droidcon and I will explain how to interpret the code fragment above.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-41770824556087142?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/41770824556087142/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=41770824556087142' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/41770824556087142'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/41770824556087142'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/11/droidconodex-file-disassembly.html' title='Droidcon+ODEX file disassembly'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-930247328454932291</id><published>2009-11-02T20:53:00.006+01:00</published><updated>2009-11-02T22:18:47.481+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='book review'/><title type='text'>Another book: Android Wireless Application Development</title><content type='html'>Somebody contacted me by LinkedIn whether &lt;a href="http://www.amazon.com/Android-Wireless-Application-Development-Conder/dp/0321627091/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1257191845&amp;amp;sr=8-1"&gt;I would write a review about this book from Addison-Wesley.&lt;/a&gt; Why not, replied I, if I get the book to read and it is understood that I am not a professional reviewer. I got the book, read it and here are my subjective impressions.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/Su87F9WjHJI/AAAAAAAABi4/fdcfBZjhMyo/s1600-h/awad.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 249px; height: 320px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/Su87F9WjHJI/AAAAAAAABi4/fdcfBZjhMyo/s320/awad.jpg" alt="" id="BLOGGER_PHOTO_ID_5399599451795037330" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Gee, there are SO MANY THINGS in Android - that was the lingering feeling after having read the book. Because the authors' strong intention is not to make compromises. They methodically go through  every feature of the Android API, including 1.5 features. Have you heard about AppWidgets before? Or LiveFolders? I admit that I have not but now I know about them because the book mentioned it.&lt;br /&gt;&lt;br /&gt;The enormous breadth of the discussion comes with a cost, however. Even though everything (or almost everything) is mentioned, very few topics are discussed in depth. For example I checked the most popular topics of this blog - unit tests, adapters. The Android unit testing framework is discussed as a bulleted list (no code examples) and the ArrayAdapter example uses Strings as backing data which causes so many problems for developers. I even managed to find the topic of hybrid applications (mashup of web technologies and Android applications, like the JavaScript handlers that an Android app can implement) that was not discussed at all (go to the lean &lt;a href="http://mylifewithandroid.blogspot.com/2009/05/two-books-on-android.html"&gt;Hello, Android book&lt;/a&gt; if you are interested, how this very fashionable approach works in Android).&lt;br /&gt;&lt;br /&gt;It is best to handle this book as an inventory of Android features and as such, it is very valuable.  Such an inventory takes 573 pages, as of version 1.5. I wonder what that number will be in 3 years time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-930247328454932291?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/930247328454932291/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=930247328454932291' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/930247328454932291'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/930247328454932291'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/11/another-book-android-wireless.html' title='Another book: Android Wireless Application Development'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_K2nCeh0MHDY/Su87F9WjHJI/AAAAAAAABi4/fdcfBZjhMyo/s72-c/awad.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-8344567110710742275</id><published>2009-10-28T21:29:00.003+01:00</published><updated>2009-10-28T21:50:43.852+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adapter'/><category scheme='http://www.blogger.com/atom/ns#' term='spinner'/><title type='text'>Spinner and its data behind</title><content type='html'>I got &lt;a href="https://www.blogger.com/comment.g?blogID=8214401912480503366&amp;amp;postID=5410658122118904209"&gt;another comment&lt;/a&gt; asking about Spinner and the data behind the widget. The author of the comment wanted to know, how to associate data to the item the Spinner displays. Then that associated data would be used to look up some other data source.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/spinnerandadapter.zip"&gt;Click here to download the example program&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In order to understand the trick, one has to know, how Adapters work in general and ArrayAdapter in particular. Adapters are objects that are able to turn data structures into widgets. The widgets are then displayed in a List or in a Spinner. So the two questions an Adapter answers are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;What widget or composite view to associate with a data structure with a certain index.&lt;/li&gt;&lt;li&gt;How to extract data from the data structure and how to set field(s) of the widget or composite view according to this data.&lt;/li&gt;&lt;/ul&gt;ArrayAdapter's answers are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Each widget for any index is the same and is inflated from the resource whose ID the ArrayAdapter receives in its constructor.&lt;/li&gt;&lt;li&gt;Each such a widget is expected to be an instance of TextView (or descendant). The widget's setText() method will be used with the string format of the item in the supporting data structure. The string format will be obtained by invoking toString() on the item.&lt;/li&gt;&lt;/ul&gt;Knowing these facts, the solution is very simple. We back ArrayAdapter with our own data structure. In this simple example MyData structure has just two fields but we can use structures with arbitrary complexity. The only thing we have to care about is that the data object's toString() method will be used to obtain the text which is displayed in the Spinner. Hence we made sure that toString() returns just one field of the object. The other field will be retrieved from MyData when the item is selected. Our application is written in such a way that then the other field is displayed in a separate text field.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/Suiubk_0o3I/AAAAAAAABiw/voZs3dpxGPY/s1600-h/spinnerandadapter.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 148px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/Suiubk_0o3I/AAAAAAAABiw/voZs3dpxGPY/s320/spinnerandadapter.JPG" alt="" id="BLOGGER_PHOTO_ID_5397755942214607730" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-8344567110710742275?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/8344567110710742275/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=8344567110710742275' title='17 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8344567110710742275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8344567110710742275'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/10/spinner-and-its-data-behind.html' title='Spinner and its data behind'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/Suiubk_0o3I/AAAAAAAABiw/voZs3dpxGPY/s72-c/spinnerandadapter.JPG' height='72' width='72'/><thr:total>17</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-5410658122118904209</id><published>2009-10-27T12:45:00.002+01:00</published><updated>2009-10-27T12:49:48.603+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='focus'/><category scheme='http://www.blogger.com/atom/ns#' term='list'/><category scheme='http://www.blogger.com/atom/ns#' term='button'/><title type='text'>Lists and focuses</title><content type='html'>I received another seemingly trivial question&lt;a href="http://mylifewithandroid.blogspot.com/2008/04/custom-widget-adapters.html?showComment=1256029565936#c6947005756767303681"&gt; in a comment.&lt;/a&gt; The situation is simple: we have a ListView and it contains TextViews. The user clicks (touches) a list row and the row gets highlighted until the user removes his or her finger (releases the mouse button in case of the emulator). Then comes the mistery. If you put a Button into the row, this highlight does not happen. How come?&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/focuslist.zip"&gt;Download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The secret is the quite complicated relationship between the ListView's selection algorithm and the focus. Button is focusable, basic TextView is not (Button is really just a specialized TextView). ViewGroup (whose child is LinearLayout which encapsulates one list row) delegates the focus to its first focusable child if it is not prevented to do so. As list row gives away the focus to the first focusable child (the Button) and the user did not touch the Button's area, neither the list row, nor the Button is selected. All this happens in "touch mode", try to select the row with the down/up key and you will see a completely different selection algorithm in action - the row is highlighted but the Button is not.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/SubeOnzxB2I/AAAAAAAABio/ec3SLNEva0g/s1600-h/listfocus.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 221px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/SubeOnzxB2I/AAAAAAAABio/ec3SLNEva0g/s320/listfocus.JPG" alt="" id="BLOGGER_PHOTO_ID_5397245546235758434" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;In order to achieve the selection effect you see in the screenshot, the ViewGroup must be prevented giving away the focus. The example program implements a quick and dirty solution: the Button is not focusable (ButtonListAdapter, line 47). More elegant solution would be to declare the ViewGroup (inflated from the buttonrow.xml layout) with FOCUS_BLOCK_DESCENDANTS flag. Conveniently, not being focusable does not prevent the Button in receiving events, click on the action button and the row counter increases nicely.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-5410658122118904209?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/5410658122118904209/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=5410658122118904209' title='20 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5410658122118904209'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5410658122118904209'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/10/lists-and-focuses.html' title='Lists and focuses'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_K2nCeh0MHDY/SubeOnzxB2I/AAAAAAAABio/ec3SLNEva0g/s72-c/listfocus.JPG' height='72' width='72'/><thr:total>20</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-5241362254962735258</id><published>2009-10-23T10:20:00.002+02:00</published><updated>2009-10-23T10:23:28.766+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='meetup'/><category scheme='http://www.blogger.com/atom/ns#' term='dedexer'/><category scheme='http://www.blogger.com/atom/ns#' term='android'/><title type='text'>My presentation about dedexer at Android Meetup</title><content type='html'>Per popular demand: see below my presentation about &lt;a href="http://dedexer.sourceforge.net/"&gt;dedexer&lt;/a&gt; at the &lt;a href="http://www.meetup.com/android/calendar/11405617/"&gt;Android Meetup, London&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style='width:425px;text-align:left'&gt;&lt;object style='margin:0px' width='425' height='355'&gt;&lt;param name='movie' value='http://static.slideshare.net/swf/ssplayer2.swf?doc=dedexer-12562855904027-phpapp01&amp;stripped_title=dedexer' /&gt;&lt;param name='allowFullScreen' value='true'/&gt;&lt;param name='allowScriptAccess' value='always'/&gt;&lt;embed src='http://static.slideshare.net/swf/ssplayer2.swf?doc=dedexer-12562855904027-phpapp01&amp;stripped_title=dedexer' type='application/x-shockwave-flash' allowscriptaccess='always' allowfullscreen='true' width='425' height='355'&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-5241362254962735258?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/5241362254962735258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=5241362254962735258' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5241362254962735258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5241362254962735258'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/10/my-presentation-about-dedexer-at.html' title='My presentation about dedexer at Android Meetup'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7771376253980240840</id><published>2009-10-18T23:05:00.003+02:00</published><updated>2009-10-19T00:25:14.003+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dedexer'/><category scheme='http://www.blogger.com/atom/ns#' term='odex'/><title type='text'>Help needed</title><content type='html'>I decided to release a new version of &lt;a href="http://dedexer.sourceforge.net/"&gt;dedexer&lt;/a&gt; but I am not satisfied. The Holy Grail I am chasing is the high-quality disassembly of ODEX files and &lt;a href="http://mylifewithandroid.blogspot.com/2009/05/about-quick-method-invocation.html"&gt;I intended to use the hint received from Nenik&lt;/a&gt;. I extended the dedexer tool with data flow analysis so it now has knowledge about the types in Dalvik registers at any point of the execution of Android bytecode. If you ask nicely the new version of the tool (-r switch), it will even share this information with you. Now a decompiled method looks like this if this switch is used:&lt;br /&gt;&lt;br /&gt;.method public &lt;init&gt;(Ljava/lang/String;)V&lt;br /&gt;.limit registers 4&lt;br /&gt;; this: v2 (LLineReader;)&lt;br /&gt;; parameter[0] : v3 (Ljava/lang/String;)&lt;br /&gt;.catch java/io/IOException from lbba to lbda using lbdc&lt;br /&gt;.line 18&lt;br /&gt;       invoke-direct   {v2},java/lang/Object/&lt;init&gt;    ; &lt;init&gt;()V&lt;br /&gt;; v2 : LLineReader;&lt;br /&gt;lbba:&lt;br /&gt;.line 20&lt;br /&gt;       new-instance    v0,java/io/FileInputStream&lt;br /&gt;; v0 : Ljava/io/FileInputStream;&lt;br /&gt;       invoke-direct   {v0,v3},java/io/FileInputStream/&lt;init&gt;  ; &lt;init&gt;(Ljava/lang/String;)V&lt;br /&gt;; v0 : Ljava/io/FileInputStream; , v3 : Ljava/lang/String;&lt;br /&gt;       iput-object     v0,v2,LineReader.fis Ljava/io/FileInputStream;&lt;br /&gt;; v0 : Ljava/io/FileInputStream; , v2 : LLineReader;&lt;br /&gt;.line 21&lt;br /&gt;       new-instance    v0,java/io/BufferedInputStream&lt;br /&gt;; v0 : Ljava/io/BufferedInputStream;&lt;br /&gt;       iget-object     v1,v2,LineReader.fis Ljava/io/FileInputStream;&lt;br /&gt;; v1 : Ljava/io/FileInputStream; , v2 : LLineReader;&lt;br /&gt;       invoke-direct   {v0,v1},java/io/BufferedInputStream/&lt;init&gt;      ; &lt;init&gt;(Ljava/io/InputStream;)V&lt;br /&gt;; v0 : Ljava/io/BufferedInputStream; , v1 : Ljava/io/FileInputStream;&lt;br /&gt;       iput-object     v0,v2,LineReader.bis Ljava/io/BufferedInputStream;&lt;br /&gt;; v0 : Ljava/io/BufferedInputStream; , v2 : LLineReader;&lt;br /&gt;lbda:&lt;br /&gt;.line 28&lt;br /&gt;       return-void&lt;br /&gt;lbdc:&lt;br /&gt;.line 23&lt;br /&gt;       move-exception  v0&lt;br /&gt;; v0 : Ljava/io/IOException;&lt;br /&gt;       goto    lbda&lt;br /&gt;.end method&lt;br /&gt;&lt;br /&gt;Great then, but where is the invoke-quick disassembly? Well, erm, I ran into problems. First of all, I could not figure out the data structures that store the names of other ODEX files that this ODEX file depends on. They seem to be in some sort of data structure at the end of the ODEX file that stores the name of these files but its exact layout remains a mistery for me.&lt;br /&gt;&lt;br /&gt;Second, in order to decode invoke-quick statements, iget-object-quick statements also need to be decoded because the type values they put into Dalvik registers are needed for the data flow analyser. The source of this instruction is known as an offset and the mapping of these offsets back to Java types.&lt;br /&gt;&lt;br /&gt;I will try to progress with these problems, any help is appreciated.&lt;br /&gt;&lt;br /&gt;And now some PR after the boring technical details.&lt;br /&gt;&lt;br /&gt;I will make a short presentation about dedexer during the &lt;a href="http://www.meetup.com/android/calendar/11405617/"&gt;coming Android meetup in London.&lt;/a&gt; If you are interested about the tool and central London is accessible for you, let's see each other there.&lt;/init&gt;&lt;/init&gt;&lt;/init&gt;&lt;/init&gt;&lt;/init&gt;&lt;/init&gt;&lt;/init&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7771376253980240840?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7771376253980240840/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7771376253980240840' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7771376253980240840'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7771376253980240840'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/10/help-needed.html' title='Help needed'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-268635594559142238</id><published>2009-08-23T22:21:00.002+02:00</published><updated>2009-08-23T22:37:08.654+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='softpedia'/><category scheme='http://www.blogger.com/atom/ns#' term='dedexer'/><category scheme='http://www.blogger.com/atom/ns#' term='mac'/><title type='text'>Dedexer in Softpedia</title><content type='html'>Wow, I got an e-mail that &lt;a href="http://dedexer.sourceforge.net/"&gt;dedexer&lt;/a&gt; 1.5 got&lt;a href="http://mac.softpedia.com/get/Developer-Tools/dedexer.shtml"&gt; included into Softpedia&lt;/a&gt;. The funny thing is that it got included into the Mac development tool section. I have already got a number of questions from Android developers working on Mac. Is it so that Mac is a favourite platform for Android developers or is it just my fortune? Any opinions?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-268635594559142238?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/268635594559142238/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=268635594559142238' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/268635594559142238'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/268635594559142238'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/08/dedexer-in-softpedia.html' title='Dedexer in Softpedia'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7127113799339742750</id><published>2009-08-17T23:16:00.004+02:00</published><updated>2009-08-17T23:51:40.073+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='chart library'/><category scheme='http://www.blogger.com/atom/ns#' term='aicharts'/><title type='text'>Fancy graphs</title><content type='html'>It seems that a sure sign of the maturity of an application platform when graph libraries start to appear. Android definitely &lt;a href="http://www.artfulbits.com/Android/aiCharts.aspx"&gt;got to this stage with aiCharts&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I didn't discover it myself. I got a mail from the authors. Even though aiCharts is a commercial product, the Android component market is still nascent enough so that they deserve advertising (even if it is only my lame advertising).&lt;br /&gt;&lt;br /&gt;aiCharts comes with flashy demonstrations and a number of demo programs.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/SonO07PxI0I/AAAAAAAABig/0TEeMfOncQk/s1600-h/screenshot1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 214px; height: 320px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/SonO07PxI0I/AAAAAAAABig/0TEeMfOncQk/s320/screenshot1.png" alt="" id="BLOGGER_PHOTO_ID_5371051439268373314" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;There was only one limitation I was able to discover: the demo programs require the latest API version and according to the authors, aiChart will not run with lower than Android 1.1. Another limitation is that ArtfulBits obviously wants to make money out of the product so the thing is protected by license key.&lt;br /&gt;&lt;br /&gt;I also received this chart from them that I now share with you. If you need a chart library, at least you know what options to choose from.&lt;br /&gt;&lt;br /&gt;&lt;table rules="none" border="0" cellspacing="0" cols="6" frame="void"&gt;  &lt;colgroup&gt;&lt;col width="169"&gt;&lt;col width="70"&gt;&lt;col width="70"&gt;&lt;col width="70"&gt;&lt;col width="70"&gt;&lt;col width="73"&gt;&lt;/colgroup&gt;  &lt;tbody&gt;   &lt;tr&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" valign="middle" width="169" align="center" height="54"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Feature name&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" valign="middle" width="70" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;aiCharts, Artfulbits v1.0.0&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" valign="middle" width="70" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Rchart, java4less, v1.0.0&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" valign="middle" width="70" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;chart4j, Google, v1.2&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" valign="middle" width="70" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;jFreeChart, Jfree, v1.0.13&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" valign="middle" width="73" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;AChartEngine, 4viewsoft, v0.3.0&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="20"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Chart types&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="left"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="left"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="left"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Area&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Bar&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Bubble&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Box and Whiskers&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Candle stick&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Column&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Doughnut&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Fast line&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Funnel&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Gantt&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;HiLo Open Close&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;HiLo&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Histogram&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Kagi&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Line&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Pie&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Point&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Polar&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Point and Figure&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Pyramid&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Renko&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Spline Area&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Spline&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;100% Stacked Area&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Stacked Area&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Stacked Bar&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;100% Stacked Column&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Stacked Column&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Step Line&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Step Area&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Three line break&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Tornado&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;3D Charts&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;pseoudo&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Annotations&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Multiple series&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Multiple areas&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Legend&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Multiple legends&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Legend dock&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Legend alignment&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Custom legend items&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Axis&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Logarithmic axes&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Multiple axes&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Axis label position&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Axis label alignment&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Labels mode&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Date values&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Axis scale&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Custom labels format&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Nice range calculation&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Zoom/Scroll&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Striplines&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Series&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;SQL data source&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Series drawables&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Per point style&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;XML data source&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Visual customization&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Themes&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Chart background&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Axes customization&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" align="left" height="15"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Series customization&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="15"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Annotations customization&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Saving chart to image/stream&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;XML inflation&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Multiple titles&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Events on chart objects clicks&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border-top: 1px solid rgb(0, 0, 0); border-left: 1px solid rgb(0, 0, 0); border-bottom: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Specially writtern for Android&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;APIs Documentation&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" valign="middle" align="justify" height="19"&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Intuitive architecture&lt;/span&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;+&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;-&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;   &lt;tr&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" align="left" height="19"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;Summary for features&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" sdval="61" sdnum="1033;" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;61&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" sdval="38" sdnum="1033;" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;38&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" sdval="25" sdnum="1033;" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;25&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" sdval="53" sdnum="1033;" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;53&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;    &lt;td style="border: 1px solid rgb(0, 0, 0);" sdval="9" sdnum="1033;" align="center"&gt;&lt;b&gt;&lt;span style="color: rgb(0, 0, 0);font-size:78%;" &gt;9&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;   &lt;/tr&gt;  &lt;/tbody&gt; &lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7127113799339742750?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7127113799339742750/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7127113799339742750' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7127113799339742750'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7127113799339742750'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/08/fancy-graphs.html' title='Fancy graphs'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_K2nCeh0MHDY/SonO07PxI0I/AAAAAAAABig/0TEeMfOncQk/s72-c/screenshot1.png' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3882924679030858692</id><published>2009-08-07T11:20:00.000+02:00</published><updated>2009-08-07T11:23:20.790+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dedexer'/><category scheme='http://www.blogger.com/atom/ns#' term='annotation'/><title type='text'>Dedexer annotation support</title><content type='html'>Just a short post for those few who do Dalvik bytecode analysis:&lt;a href="http://dedexer.sourceforge.net/"&gt; dedexer&lt;/a&gt; has now full annotation support! For example it now decompiles "throws" and inner class annotation, along with custom annotations. Go for &lt;a href="http://sourceforge.net/projects/dedexer/files/"&gt;1.5 release&lt;/a&gt; to get it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3882924679030858692?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3882924679030858692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3882924679030858692' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3882924679030858692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3882924679030858692'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/08/dedexer-annotation-support.html' title='Dedexer annotation support'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-6769357043582035701</id><published>2009-07-30T23:18:00.003+02:00</published><updated>2009-07-30T23:46:44.683+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='simple'/><title type='text'>It's that simple</title><content type='html'>Like many, I also thought that &lt;a href="http://code.google.com/p/simple/"&gt;this project was a joke&lt;/a&gt;. Somebody obviously created something like the Visual Basic and made it work on Android. Then&lt;a href="http://google-opensource.blogspot.com/2009/07/programming-made-simple.html"&gt; from this blog post&lt;/a&gt;, it became clear that Simple is dead serious.&lt;br /&gt;&lt;br /&gt;Simple tries to replicate the 20 year old Microsoft idea about high-performance components and a script language binding them into applications. There is one thing, however, that caught my attention. Why not Python or any other "real" script language? Why this Basic afterthought?&lt;br /&gt;&lt;br /&gt;The intriguing fact is that Android's virtual machine, Dalvik just got another language beside Java. The Simple compiler takes the Simple code and compiles directly into Dalvik bytecode, without going through Java. For example there is this Simple subroutine (remember the term "subroutine" ages ago? :-)).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;Sub PreviewBrick(brick As Brick, orientation As Integer)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Simple compiler turns it into a proper Dalvik method (Simple compiler's DEX output decompiled by &lt;a href="http://dedexer.sourceforge.net/"&gt;Dedexer&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;.method public PreviewBrick(Lcom/google/devtools/simple/samples/tetris/Brick;I)V&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;There is a runtime for Simple programs but it is not really an interpreter, it is just a glue between the standard Android classes and the Simple event system. That may explain, why the scripting language is not Python; a real scripting language may be too dynamic to compile.&lt;br /&gt;&lt;br /&gt;There have been similar attempts for Java proper but the JVM was never designed to run anything other than Java. When .Net appeared, Microsoft claimed that the .Net Common Language Runtime is much more suitable running certain languages than the Java bytecode. This may or may not be true, the fact remains that CLR runs mostly C# and Visual Basic.NET programs.&lt;br /&gt;&lt;br /&gt;From this demonstration with Simple, it is clear that Dalvik can also run a script language (Simple) and a component language (Java). Whether developers will find attractive enough to work in a script language that needs time-consuming compilation and deployment before running it (one huge advantage of scripting languages is that the program can be executed immediately after modification) or whether experimenting software experts want to use a rather obsolete language (like this BASIC clone) for binding their components is yet to be seen.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-6769357043582035701?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/6769357043582035701/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=6769357043582035701' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6769357043582035701'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6769357043582035701'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/07/its-that-simple.html' title='It&apos;s that simple'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7443213653715301721</id><published>2009-07-01T23:02:00.003+02:00</published><updated>2009-07-01T23:21:13.315+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='assembler'/><category scheme='http://www.blogger.com/atom/ns#' term='baksmali'/><category scheme='http://www.blogger.com/atom/ns#' term='disassembler'/><category scheme='http://www.blogger.com/atom/ns#' term='dex'/><category scheme='http://www.blogger.com/atom/ns#' term='smali'/><title type='text'>New DEX assembler/disassembler pair</title><content type='html'>&lt;a href="http://dedexer.sourceforge.net/"&gt;Dedexer&lt;/a&gt; got competition! Not one but immediately two - &lt;a href="http://code.google.com/p/smali/"&gt;smali and baksmali&lt;/a&gt; is a DEX assembler/disassembler pair.  The programs are brand new and - like Dedexer - they are also based on a Jasmin-like format. I tried them very shortly, the disassembler - baksmali - worked correctly and indeed produced an output similar to dedexer but I could not compile the output of baksmali back to DEX format with the assembler - smali. The disassembler does not handle ODEX files either.&lt;br /&gt;&lt;br /&gt;The initiative is indeed valuable, however, and I encourage every reverse engineer out there give the new tools a try.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7443213653715301721?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7443213653715301721/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7443213653715301721' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7443213653715301721'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7443213653715301721'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/07/new-dex-assemblerdisassembler-pair.html' title='New DEX assembler/disassembler pair'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3469771480953638897</id><published>2009-06-30T21:26:00.003+02:00</published><updated>2009-06-30T21:31:29.912+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adapter'/><category scheme='http://www.blogger.com/atom/ns#' term='theme'/><category scheme='http://www.blogger.com/atom/ns#' term='widget'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>Custom adapter in color</title><content type='html'>I got a comment on this blog with relation to an old sin of mine, the &lt;a href="http://mylifewithandroid.blogspot.com/2008/04/custom-widget-adapters.html"&gt;custom adapter example program&lt;/a&gt;. That example was about custom views in a list adapter. The comment was innocent enough: is there a way to change the colors of that list? I thought it was a 5 minute task to answer that question, in the end I had to deal with a topic I never wanted to know about: themes.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/themedcustomadapter.zip"&gt;Click here to download the example program.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;It turned out that the color scheme used by ListView is eventually taken from the active theme. It is possible to change it programmatically but it is quite complicated to do so. Meanwhile, with themes, it is relatively easy. If you know the information source. The &lt;a href="http://developer.android.com/guide/topics/ui/themes.html"&gt;Android development guide is almost completely useless regarding themes,&lt;/a&gt; for example sample themes don't work. &lt;a href="http://brainflush.wordpress.com/2009/03/15/understanding-android-themes-and-styles/"&gt;This blog entry from Brainflush&lt;/a&gt; is much more informative, at least its examples work. The source that helped me the most was the actual Android XML fileset that defines the themes. If you have access to the Android source tree, the Android system resources can be found under the &lt;a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=tree;f=core/res/res;h=a243588c388b66752511f8947b0c7fd2a1df8a76;hb=HEAD"&gt;platform\frameworks\base\core\res\res&lt;/a&gt; directory. The relevant files are styles.xml under the values subdirectory and the content of the drawable subdirectory.&lt;br /&gt;&lt;br /&gt;If you check the styles.xml in the res/values directory in our example program, you will see that modifying the color scheme of the ListView is a quite complicated, multi-step process. First of all, in res/values/styles.xml, the ListView style is overridden with the android:listViewStyle property. The ListViewStyle (MyListView) in turn references a list selector (android:listSelector) that is stored in the res/drawable directory. That selector defines color schemes for all the possible state combination of the list row. To increase the fun, the resource compiler screwed up the image IDs in the R.java file if the weather icon image files were not at the beginning of the file name list, hence the bizarre names in the drawable subdirectory. Now what we need more is a transition that refers a &lt;a href="http://developer.android.com/guide/developing/tools/draw9patch.html"&gt;9-patch&lt;/a&gt; background image (block blue in our case) for the selected row and we are ready.&lt;br /&gt;&lt;br /&gt;Don't say that it was not easy.&lt;br /&gt;&lt;br /&gt;PS: I apologize for the hideous colors. :-)&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/Skpnmxq5Y7I/AAAAAAAABiY/SDl1qu360KY/s1600-h/themedcustomadapter.GIF"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 151px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/Skpnmxq5Y7I/AAAAAAAABiY/SDl1qu360KY/s320/themedcustomadapter.GIF" alt="" id="BLOGGER_PHOTO_ID_5353205022949204914" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3469771480953638897?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3469771480953638897/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3469771480953638897' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3469771480953638897'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3469771480953638897'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/06/custom-adapter-in-color.html' title='Custom adapter in color'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/Skpnmxq5Y7I/AAAAAAAABiY/SDl1qu360KY/s72-c/themedcustomadapter.GIF' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7368517609848500514</id><published>2009-06-23T20:04:00.002+02:00</published><updated>2009-06-23T20:08:46.979+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='resources'/><category scheme='http://www.blogger.com/atom/ns#' term='assets'/><title type='text'>Assets</title><content type='html'>Let's have something lighter than the application separation stuff was in the previous entry.&lt;br /&gt;&lt;br /&gt;I got a mail from someone who wanted to embed raw files into the APK file so that they are available for the application when it is executing. I have never done such a thing but I remembered faintly something about the &lt;a href="http://developer.android.com/guide/topics/resources/resources-i18n.html"&gt;res/raw directory&lt;/a&gt; where such resources reside. They are still there but now &lt;a href="http://developer.android.com/guide/topics/resources/index.html"&gt;there is a better thing: assets. &lt;/a&gt;The difference between a raw resource in res/raw directory and an asset is that assets behave like a file system, they can be listed, iterated over, discovered, just like files while for raw resources, you need the resource ID.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/assets.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This simple application iterates over the files in the asset directory, reads their contents and sets three TextFields according to the content of these files. Assets go into the ./assets directory in the project root and can contain any files. The &lt;a href="http://developer.android.com/reference/android/content/res/AssetManager.html"&gt;AssetManager class&lt;/a&gt; provides access to assets as InputStreams.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/SkEaGud3b6I/AAAAAAAABYs/-bgwnzGRG0o/s1600-h/assets.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 178px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/SkEaGud3b6I/AAAAAAAABYs/-bgwnzGRG0o/s320/assets.JPG" alt="" id="BLOGGER_PHOTO_ID_5350586535147696034" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7368517609848500514?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7368517609848500514/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7368517609848500514' title='22 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7368517609848500514'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7368517609848500514'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/06/assets.html' title='Assets'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/SkEaGud3b6I/AAAAAAAABYs/-bgwnzGRG0o/s72-c/assets.JPG' height='72' width='72'/><thr:total>22</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7803436812650652864</id><published>2009-06-13T14:11:00.004+02:00</published><updated>2009-06-13T14:20:16.437+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='class loader'/><category scheme='http://www.blogger.com/atom/ns#' term='separation'/><title type='text'>Controlling application separation</title><content type='html'>I thought I knew how application separation in Android works. Then I had a little project and I realized that there is more than meets the eye. Here are my experiences.&lt;br /&gt;&lt;br /&gt;The basic - and very efficient - separation method in Android is the multiprocess capability of the Dalvik VM. Dalvik is able to fork many instances of itself and can run applications in different Linux processes. By default, each application (the &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;application&lt;/span&gt;&lt;/small&gt; tag in AndroidManifest.xml) has its own process. There is however another, often overlooked separation mechanism &lt;span style="font-style: italic;"&gt;inside&lt;/span&gt; each VM process that provides further separation. The good old ClassLoader separation - originally designed to separate applets, used heavily in multi-module frameworks like OSGi - is also at work. Inside each VM process, each task - acitivity or service - uses in its own ClassLoader. This means that object instances created in different tasks are not visible to each other. The standard setup for Android applications is such that the classes.dex in the application's APK package is on the path of the application's own classloader. The application's own classloader delegates the loading of system classes to the boot classloader instance which is common for all the tasks in one particular VM process.&lt;br /&gt;&lt;br /&gt;Class loading implementation in Dalvik is tied heavily to DEX files. This has visible side effects. Classes loaded by different class loaders that don't delegate to each other should be completely separated. The implementation differentiates the "initiating" and "defining" classloader (the latter is the one that managed to call defineClass() for a particular class from a particular DEX file in a particular VM process). This differentiation does not work very well and results in a glitch: once a class from a particular DEX file is defined, its static fields are visible and shared from all the class loaders that access the DEX file, even if they don't delegate to each other. This means that tasks of the same application (usually packed into the same DEX file) see each other's static fields.&lt;br /&gt;&lt;br /&gt;As if this was not enough, Android provides control of task-VM process mapping. As said previously, usually each application goes into its own VM process. It is possible to declare, however, that applications share a process. This works like the following:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;First we declare that applications wishing to share the same VM process belong to the same user ID. This is done by adding the &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;sharedUserId&lt;/span&gt;&lt;/small&gt; attribute to the &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;manifest&lt;/span&gt;&lt;/small&gt; tag in AndroidManifest.xml file like the following: &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;android:sharedUserId="aexp.share.sharedapp"&lt;/span&gt;&lt;/small&gt;. The value of the attribute must be a unique string shared among the applications wishing to use the same VM process. This is a powerful mechanism, hence Android enforces strong check of the APKs containing the declaration. Only the first APK for a particular sharedUserId can be installed without limitation. The issuer of the digital signature of that APK is saved and further APKs with the same sharedUserId must have digital signature from the same issuer, else their installation is rejected.&lt;/li&gt;&lt;li&gt;We have to declare the alias of the process a certain task or the entire application goes into with the &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;process&lt;/span&gt;&lt;/small&gt; attribute. This attribute can be added to &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;application&lt;/span&gt;&lt;/small&gt;, &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;activity&lt;/span&gt;&lt;/small&gt; or &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;service&lt;/span&gt;&lt;/small&gt; tags in the AndroidManifest.xml file. For example: &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;android:process="aexp.share.sharedappprocess"&lt;/span&gt;&lt;/small&gt;. VM processes are proper Linux processes identified by PIDs but Android does an additional transformation: whenever a process is started which is tagged by the &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;process&lt;/span&gt;&lt;/small&gt; alias, the Android framework saves the Linux PID and the process alias. If some other application refers to that process alias and the VM process is still running, that process will be used.&lt;/li&gt;&lt;/ul&gt; How can we exploit this mechanism? We can create ordinary, well-behaving Android applications and place them into the same process. Android IPC is such that the usual &lt;a href="http://mylifewithandroid.blogspot.com/2008/01/about-binders.html"&gt;binder-based communication&lt;/a&gt; is much faster in this case. The other approach is that we exploit our knowledge about class loaders. If we place two applications into the same process, they not only have different class loaders but also come from different DEX files. This means that the Android class loading glitch about the static fields cannot be exploited (I don't recommend it anyway as it is a clear bug and I believe, it will be corrected soon). We know, however, that each class loader in the same VM process delegates the resolution of the system classes to the boot class loader. If we find a system class that allows us to store and retrieve data, we win.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/shared.zip"&gt;Click here to download the example program&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Our two example programs demonstrate the techniques described below. SharedApp1 allows the user to enter a string and it shares that string in different ways. With its own popup, it shares the string by means of a static field of SharedApp1 activity class. SharedApp2 is another application, having its own APK file but is placed into the same process as SharedApp1. SharedApp1 and SharedApp2 communicate through the good old System.getProperty/setProperty mechanism. Observe, that this works only if the sharedUserId/process mechanism is properly used, if you remove e.g. the process attribute from the manifest file of any of the apps, the property set by SharedApp1 is not visible anymore to SharedApp2 because they run in different VM processes. Also note that I compiled these applications with the debug option which means that they are both signed with the debug key. That key is common for both applications therefore they can have the same sharedUserIds. If you use a proper signing key, take care of using the same key when you sign applications that depend on the sharedUserId mechanism.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_K2nCeh0MHDY/SjOX5r-uEgI/AAAAAAAABSk/E8VXu5YVOqw/s1600-h/sharedapp.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 290px; height: 320px;" src="http://2.bp.blogspot.com/_K2nCeh0MHDY/SjOX5r-uEgI/AAAAAAAABSk/E8VXu5YVOqw/s320/sharedapp.JPG" alt="" id="BLOGGER_PHOTO_ID_5346784199933039106" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And now for something completely different. I was one of the reviewers of Roy Osherove's book titled &lt;a href="http://www.amazon.com/Art-Unit-Testing-Examples-NET/dp/1933988274/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1244895289&amp;amp;sr=8-1"&gt;The Art of Unit Testing (Manning, about 27 USD)&lt;/a&gt;. Even though the book uses .NET examples, it is easily readable for Java programmers too. Unit testing is one of the most useful techniques I am aware of in software engineering, if you feel that your knowledge is not up to date, you might as well read this book. :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7803436812650652864?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7803436812650652864/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7803436812650652864' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7803436812650652864'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7803436812650652864'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/06/controlling-application-separation.html' title='Controlling application separation'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_K2nCeh0MHDY/SjOX5r-uEgI/AAAAAAAABSk/E8VXu5YVOqw/s72-c/sharedapp.JPG' height='72' width='72'/><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-1039593449242907151</id><published>2009-05-13T22:17:00.005+02:00</published><updated>2009-05-13T22:36:31.006+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='odex'/><category scheme='http://www.blogger.com/atom/ns#' term='invoke-virtual-quick'/><title type='text'>About quick method invocation</title><content type='html'>A good 3 months ago &lt;a href="http://mylifewithandroid.blogspot.com/2009/02/optimized-dex-files.html"&gt;I wrote about the ODEX format &lt;/a&gt;when support for that DEX variant was added to the &lt;a href="http://dedexer.sourceforge.net/"&gt;dedexer tool&lt;/a&gt;. Then I asked if there is anybody out there who knows, how the index in the invoke-virtual-quick Dalvik instruction can be interpreted. For example:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;invoke-virtual-quick {v1,v2},vtable #0x3b&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That 3BH is an offset but into what? I did not know, therefore dedexer does not interpret the offset which makes its output on ODEX files less useful. Then Nenik (nickname according to his request) finally gave me the solution. Here comes his mail, verbatim.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;The index computation is pretty simple, but for reverse analysis you  need more data.&lt;br /&gt;&lt;br /&gt;The vtable contains all the methods that can be invoked by  invoke-virtual, that is&lt;br /&gt;all nonprivate member methods (even those final and native).&lt;br /&gt;The vtable is obviously constructed by copying superclass's vtable, then  replacing&lt;br /&gt;overridden methods and appending all additional virtual methods.&lt;br /&gt;&lt;br /&gt;The methods in the vtable are ordered as they were in the dex file.&lt;br /&gt;Let's look at android.view.KeyCharacterMap for example. It extends  java.lang.Object,&lt;br /&gt;so it starts with:&lt;br /&gt;Object:&lt;br /&gt;0: .method protected clone()Ljava/lang/Object;&lt;br /&gt;1: .method public equals(Ljava/lang/Object;)Z&lt;br /&gt;2: .method protected finalize()V&lt;br /&gt;3: .method public final native getClass()Ljava/lang/Class;&lt;br /&gt;4: .method public native hashCode()I&lt;br /&gt;5: .method public final native notify()V&lt;br /&gt;6: .method public final native notifyAll()V&lt;br /&gt;7: .method public toString()Ljava/lang/String;&lt;br /&gt;8: .method public final wait()V&lt;br /&gt;9: .method public final wait(J)V&lt;br /&gt;a: .method public final native wait(JI)V&lt;br /&gt;&lt;br /&gt;Then it replaces:&lt;br /&gt;2: .method protected finalize()V&lt;br /&gt;with the implementation from KeyCharacterMap&lt;br /&gt;&lt;br /&gt;and adds:&lt;br /&gt;b: .method public get(II)I&lt;br /&gt;c: .method public getDisplayLabel(I)C&lt;br /&gt;d: .method public getEvents([C)[Landroid/view/KeyEvent;&lt;br /&gt;e: .method public getKeyData(ILandroid/view/KeyCharacterMap$KeyData;)Z&lt;br /&gt;f: .method public getKeyboardType()I&lt;br /&gt;10: .method public getMatch(I[C)C&lt;br /&gt;11: .method public getMatch(I[CI)C&lt;br /&gt;12: .method public getNumber(I)C&lt;br /&gt;13: .method public isPrintingKey(I)Z&lt;br /&gt;&lt;br /&gt;Anyway,&lt;br /&gt;invoke-virtual-quick {v1},vtable #0x2&lt;br /&gt;on an instance of any kind is simply the quick variant of&lt;br /&gt;invoke-virtual {v1}, Ljava/lang/Object;.finalize:()V&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Obviously for decoding invoke-virtual-quick opcodes inside, say,&lt;br /&gt;framework.odex, you need the coresponding core.odex and ext.odex&lt;br /&gt;to reconstruct the vtables of all the base classes.&lt;br /&gt;&lt;br /&gt;An .odex file contains a list of such dependencies appended&lt;br /&gt;after the body of the encapsulated dex, together with their SHA-1s:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;00498D10 44 6E 95 3A │ 13 BC 10 91 │ 0E 00 00 00 │ 02 00 00 00  Dn.:.¼..........&lt;br /&gt;00498D20 1C 00 00 00 │ 2F 73 79 73 │ 74 65 6D 2F │ 66 72 61 6D  ..../system/fram&lt;br /&gt;00498D30 65 77 6F 72 │ 6B 2F 63 6F │ 72 65 2E 6F │ 64 65 78 00  ework/core.odex.&lt;br /&gt;00498D40 93 97 93 82 │ 99 DA 46 4E │ E2 13 DD 35 │ 4C 48 B5 7B  .....ÚFNâ.Ý5LHµ{&lt;br /&gt;00498D50 F0 06 51 D7 │ 1B 00 00 00 │ 2F 73 79 73 │ 74 65 6D 2F  ð.Q×..../system/&lt;br /&gt;00498D60 66 72 61 6D │ 65 77 6F 72 │ 6B 2F 65 78 │ 74 2E 6F 64  framework/ext.od&lt;br /&gt;00498D70 65 78 00 54 │ F1 D0 82 95 │ 97 53 15 60 │ E4 D6 2C 48  ex.TñÐ...S.`äÖ,H&lt;br /&gt;00498D80 8E 36 51 C5 │ 75 BE 2C 00 │ 50 4B 4C 43 │ 08 80 01 00  .6QÅu¾,.PKLC....&lt;br /&gt;00498D90 08 80 01 00 │ 00 20 00 00 │ 00 00 21 AA │ C2 5C 31 00 .....  ....!ªÂ\1.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;With all this data, it should be possible to reconstruct .dex from current&lt;br /&gt;.odex format, something I would like to be able to do.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Thanks, Nenik, this is the information I needed to improve the dedexer tool. I will do that soon. If anyone has something to add, please, comment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-1039593449242907151?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/1039593449242907151/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=1039593449242907151' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1039593449242907151'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/1039593449242907151'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/05/about-quick-method-invocation.html' title='About quick method invocation'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-6121392510427263072</id><published>2009-05-07T22:38:00.004+02:00</published><updated>2009-05-07T22:45:31.661+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hello android'/><category scheme='http://www.blogger.com/atom/ns#' term='unlocking android'/><title type='text'>Two books on Android</title><content type='html'>I have reviewed two Android books recently and now, that both are in press, I decided to write some words about the experience. The first book is &lt;a href="http://www.amazon.com/Hello-Android-Introducing-Development-Platform/dp/1934356174/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1241728755&amp;amp;sr=8-1"&gt;Ed Burnette's Hello Android&lt;/a&gt; (The Pragmatic Programmers, ~22 USD).&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/SgNHf3NXf_I/AAAAAAAABSU/AthXezBeaOU/s1600-h/helloandroid.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 240px; height: 240px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/SgNHf3NXf_I/AAAAAAAABSU/AthXezBeaOU/s320/helloandroid.jpg" alt="" id="BLOGGER_PHOTO_ID_5333184996458332146" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The book is clearly written with newcomers in mind who want to produce something functional quickly. Innovative feature is the "fast forward" section at the end of each chapter. This guides the inpatient reader who don't want to read sequentially to other relevant section, much like a hypertext link. Then the book goes through the application model, user interface, 2D and 3D graphics (yes, it has a functional OpenGL example), multimedia, web programming and database issues. The book seems to concentrate on developers of UI-intensive applications, for example the Android service and content provider concepts are mentioned only shortly, meanwhile there is an abundance of UI-related examples, even advanced ones like 3D graphics. The integration of JavaScript and Java in the web programming section was a revelation for me, I did not know about the mechanism before reading the book and it is cool indeed.&lt;br /&gt;&lt;br /&gt;The other book is &lt;a href="http://www.amazon.com/Unlocking-Android-Frank-Ableson/dp/1933988673/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1241728929&amp;amp;sr=1-1"&gt;Unlocking Android from Ableson, Collins and Sen&lt;/a&gt; (Manning, ~29$).&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K2nCeh0MHDY/SgNICGCqVhI/AAAAAAAABSc/R-vnQEq0VLM/s1600-h/unlockingandroid.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 240px; height: 240px;" src="http://4.bp.blogspot.com/_K2nCeh0MHDY/SgNICGCqVhI/AAAAAAAABSc/R-vnQEq0VLM/s320/unlockingandroid.jpg" alt="" id="BLOGGER_PHOTO_ID_5333185584555513362" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This book definitely has higher ambitions. It is longer than the previous one (~400 vs. ~200 pages) and expects sequential reading. Beside the usual topics, it ventures into such exotic areas like Linux-level programming in C in the Android environment. Curiously, this breadth of topics means that the Manning book deals less with UI-programming than Burnette's book (82 pages in the Burnette book vs. 62 pages in the Manning book).The Manning book does provide, however, a detailed view of Android programming model. It discusses Android services extensively and even presents, how to write a content provider (although not in detail).&lt;br /&gt;&lt;br /&gt;To summarize, the two books follow different concepts. Burnette's book is, erm, pragmatic. It tries to make the reader productive as quickly as possible using the most common application type, the UI-intensive standalone application, with some web programming thrown in for good measure. Meanwhile, it omits a great deal of Android architecture. The Manning book builds systematically, by presenting the architectural concepts in detail. Decide yourself, which one suits you most. I enjoyed reading both.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-6121392510427263072?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/6121392510427263072/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=6121392510427263072' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6121392510427263072'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6121392510427263072'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/05/two-books-on-android.html' title='Two books on Android'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_K2nCeh0MHDY/SgNHf3NXf_I/AAAAAAAABSU/AthXezBeaOU/s72-c/helloandroid.jpg' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-2154762620564355233</id><published>2009-02-05T20:39:00.002+01:00</published><updated>2009-02-05T20:45:40.393+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='asynchronous communication'/><category scheme='http://www.blogger.com/atom/ns#' term='middleware'/><title type='text'>Asynchronous communication in Android</title><content type='html'>Old sins always come back to haunt. Once &lt;a href="http://mylifewithandroid.blogspot.com/2008/02/android-is-most-modern-application.html"&gt;I wrote a blog post&lt;/a&gt;, shamelessly &lt;a href="http://www.springerlink.com/content/l3210774w7752127/?p=d8a8b89a32ce41eba4ab52b900a9cdb4&amp;amp;pi=8"&gt;referring to a 2006 paper of mine&lt;/a&gt;. In that post, I praised the conformance of the Android system with regards to the requirements of the reflective middleware approach. I found that only one area was not covered appropriately, asynchronous communication. It could be then expected that when folks at &lt;a href="http://www.softwired-inc.com/"&gt;Softwired&lt;/a&gt; ported their&lt;a href="http://www.softwired-inc.com/products/mobile/mobile.html"&gt; iBus//Mobile asynchronous communication framework&lt;/a&gt; to Android, they found me.&lt;br /&gt;&lt;br /&gt;iBus//Mobile already supports a number of clients. For Java, there is J2SE, CDC and MIDP support, Android was just added to this list. Additionally, native clients like Windows CE and Symbian are also supported and all these clients are able to talk to each other using the iBus//Mobile queuing system. In Java, the access interface is the familiar JMS.&lt;br /&gt;&lt;br /&gt;iBus//Mobile depends on a gateway on the network and each client talks to this gateway. Even if device-to-device communication is required, the data goes through the gateway. The protocol support is nicely done, the framework supports a bunch of protocols. Some are used in polling mode (e.g. HTTP), some of them, however, also work in push mode like the TCP or SMS bearers. In case of TCP, they maintain a TCP stream to the gateway, keep that stream alive with keepalive messages and push the device-bound messages over the gateway-device direction of this stream. The programmer has full control over the protocol selection. Also, there is pluggable encryption support.&lt;br /&gt;&lt;br /&gt;Those who have not yet tried cannot even imagine, how time-consuming it can be to implement such a system. It sounds simple but when it comes to actually implement the communication between the client and the server parts, unpleasant surprises will pop up, e.g. about the differences of mobile networks with regards to timing, connection keepalive, etc.&lt;br /&gt;&lt;br /&gt;How does iBus//Mobile compare to my dream system I envisioned in my paper? First, it is nicely componentized although not along the line of the Android component system. There is a pluggable protocol support which is a great feature, it is hard to survive without it in the mobile world. My dream system included protocol support for direct device-to-device communication (e.g. Bluetooth). This is missing in iBus//Mobile which may be due to their business requirements. I can imagine a creative usage of multiple protocols at the same time on the client side, e.g. falling back to SMS from TCP if packet data connection is not available or using SMS to trigger data push by e.g. HTTP to simulate a push bearer that can transfer large amount of data. I also find the relationship between queues and synchronization intriguing. iBus//Mobile uses a sliding window-based algorithm to ensure that there can be multiple queue items transmitted independently from each other at the same time. If off-line operation is a frequent requirement (e.g. the application puts large number of elements into the queue then connects to the server to deliver those elements), full-scale synchronization solution is more efficient.&lt;br /&gt;&lt;br /&gt;But enough of the whining! Softwired provides a robust, field-tested solution to Android developers who need message queues. You'd better know about iBus//Mobile if you don't want to suffer in the weird world of mobile bearers, particularly in the push direction.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-2154762620564355233?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/2154762620564355233/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=2154762620564355233' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2154762620564355233'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2154762620564355233'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/02/asynchronous-communication-in-android.html' title='Asynchronous communication in Android'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-4619253239479972715</id><published>2009-02-03T22:52:00.001+01:00</published><updated>2009-02-03T22:54:32.828+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='disassembler'/><category scheme='http://www.blogger.com/atom/ns#' term='dex'/><category scheme='http://www.blogger.com/atom/ns#' term='optimization'/><title type='text'>Optimized DEX files</title><content type='html'>One of the less publicized ability of the Dalvik virtual machine is that it is able to execute "optimized" DEX files (or ODEX files). "Optimizing" a DEX file speeds up its execution but also ties it to the hardware platform on which the optimization was performed. ODEX optimization relies on unsafe bytecode instructions. Unsafe instructions are much faster to execute, on the other hand, malicious use of these instructions can crash the virtual machine. Dalvik has an additional layer of security, process separation and file permissions of the underlying Linux platform so this price is not that high. It is important to note, however, that much of the security normally associated to the Java virtual machine e.g. in J2ME is now provided by the underlying Linux platform.&lt;br /&gt;&lt;br /&gt;I remember the resistance when I proposed &lt;a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.59.9268"&gt;unsafe instruction for garbage collection performance optimization on mobile platforms.&lt;/a&gt; The critics argued that 1. the Java bytecode is not allowed to be modified and 2. the bytecode safety must be preserved at all costs. Seemingly, Dalvik designers have done away with both ortodoxies.&lt;br /&gt;&lt;br /&gt;The optimization is carried out on the target platform, by the target platform's virtual machine. The command to invoke the optimizer is called dexopt. There is no point in invoking this command on the emulator as it would create an optimized DEX that can be executed only on the emulator. The most important transformations are the replacement of the method indexes with vtable indexes and the field indexes with memory offsets. So instead of invoking a method by name, it is invoked by vtable index using a set of special instructions not specified in the Dalvik instruction set.&lt;br /&gt;&lt;br /&gt;For example:&lt;br /&gt;&lt;br /&gt; &lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;invoke-virtual-quick    {v1,v2},vtable #0x3b&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;(Note: if anyone reads this who is knowledgeable in the way the vtable index is computed, please, drop me a comment or mail :-))&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Also, for fields:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;iget-object-quick       v0,v2,[obj+0x100]&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;The &lt;a href="http://dedexer.sourceforge.net/"&gt;dedexer tool &lt;/a&gt;was updated and now provides a limited support for ODEX files. Sadly, I was not able to figure out, how to calculate method or field names out of vtable indexes and byte offsets which clearly limits the usefulness of the disassembled ODEX sources. When I was at it, I also implemented debug information processing that generates line number and local variable information like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;.var 0 is intent Landroid/content/Intent; from l144ac to l144c8&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         const-class     v3,com/android/vending/SubCategoryListActivity&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         const-string    v2,"android.intent.action.VIEW"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; .line 180&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;The &lt;a href="http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html"&gt;Dalvik opcode list &lt;/a&gt;was also updated with the description of the "quick" instructions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-4619253239479972715?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/4619253239479972715/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=4619253239479972715' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4619253239479972715'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/4619253239479972715'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/02/optimized-dex-files.html' title='Optimized DEX files'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-2374824735847096056</id><published>2009-01-19T22:11:00.005+01:00</published><updated>2009-01-19T22:29:23.214+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='instrumentation'/><category scheme='http://www.blogger.com/atom/ns#' term='key press'/><title type='text'>Generating keypresses programmatically</title><content type='html'>There &lt;a href="http://mylifewithandroid.blogspot.com/2008/11/test-driving-android-gui.html"&gt;was already a blog entry&lt;/a&gt; about programmatically generating key events but I was not satisfied with the outcome. The Instrumentation framework is nice but is somewhat heavyweight. If one wants to generate just keypresses, why to have all those entries in the manifest, instrumentation object, etc? So I used the wonderful new&lt;a href="http://dedexer.sourceforge.net/"&gt; dedexer tool&lt;/a&gt; to go after the inner workings of the instrumentation framework.&lt;br /&gt;&lt;br /&gt;The critical method in android.app.Instrumentation is this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;.method public sendKeySync(Landroid/view/KeyEvent;)V&lt;br /&gt;.catch android/os/RemoteException from le5cf6 to le5d12 using le5d14&lt;br /&gt; invoke-direct    {v2},android/app/Instrumentation/validateNotAppThread&lt;br /&gt;                 ; validateNotAppThread()V&lt;br /&gt;le5cf6:&lt;br /&gt; const-string    v0,"window"&lt;br /&gt; invoke-static    {v0},android/os/ServiceManager/getService&lt;br /&gt;                 ; getService(Ljava/lang/String;)Landroid/os/IBinder;&lt;br /&gt; move-result-object    v0&lt;br /&gt; invoke-static    {v0},android/view/IWindowManager$Stub/asInterface    &lt;br /&gt;                 ; asInterface(Landroid/os/IBinder;)Landroid/view/IWindowManager;&lt;br /&gt; move-result-object    v0&lt;br /&gt; const/4    v1,1&lt;br /&gt; invoke-interface    {v0,v3,v1},android/view/IWindowManager/injectKeyEvent    &lt;br /&gt;                 ; injectKeyEvent(Landroid/view/KeyEvent;Z)Z&lt;br /&gt;le5d12:&lt;br /&gt; return-void&lt;br /&gt;le5d14:&lt;br /&gt; move-exception    v0&lt;br /&gt; goto    le5d12&lt;br /&gt;.end method&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is actually a public API method. The method obtains the binder of the WindowManagerService from the ServiceManager then it invokes the injectKeyEvent method on WindowManagerService's public interface. That is something we can do ourselves, can't we?&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/keygen.zip"&gt;Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Unfortunately the situation is not that simple. Ever since the non-public classes like android.os.ServiceManager, android.view.IWindowManager, etc. were removed from android.jar, applications using non-public API cannot be compiled as easily as before. If you check the example program, you will see that I created stubs for these classes. These stubs are only there to allow compilation of the program, they don't get packaged into the apk file. On the platform itself, there will be real versions of them. In order to accomodate the stub compilation step, I modified somewhat the machine-generated build.xml file too.&lt;br /&gt;&lt;br /&gt;It is also important to notice the validateNotAppThread private method invocation at the beginning of the method. Events cannot be generated neither from the application's own thread, nor from the UI thread. That's the reason we prepare another thread and install a looper into it. Then we post our actions into that thread and that thread will be able to generate keypresses.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/SXTulhaskrI/AAAAAAAABOo/r47jmcu-0oc/s1600-h/keygen.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 218px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/SXTulhaskrI/AAAAAAAABOo/r47jmcu-0oc/s320/keygen.JPG" alt="" id="BLOGGER_PHOTO_ID_5293117790460089010" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;If you launch the program and press the "Generate keypresses" button, you will see how the text field above the button receives the keypresses we generated programmatically. At this point you may get excited and would try to generate keypresses for other applications. This will not work. Android applications are allowed to generate keypresses only for themselves. In WindowManagerService class, you will find a permission check controlling this behaviour.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; iget-object    v1,v12,com/android/server/WindowManagerService.mContext Landroid/content/Context;&lt;br /&gt; const-string    v2,"android.permission.INJECT_EVENTS"&lt;br /&gt; invoke-virtual    {v1,v2,v14,v15},android/content/Context/checkPermission    &lt;br /&gt;                     ; checkPermission(Ljava/lang/String;II)I&lt;br /&gt; move-result    v1&lt;br /&gt; if-eqz    v1,l4a7e0&lt;br /&gt; const-string    v1,"WindowManager"&lt;br /&gt; new-instance    v1,java/lang/StringBuilder&lt;br /&gt; invoke-direct    {v1},java/lang/StringBuilder/&lt;init&gt;    &lt;br /&gt;                     ; &lt;init&gt;()V&lt;br /&gt; const-string    v2,"Permission denied: injecting key event from pid "&lt;br /&gt;&lt;/init&gt;&lt;/init&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The android.permission.INJECT_EVENT permission is not that of the application, the WindowManagerService must have this permission otherwise requests to inject events into other applications' windows will be rejected. By default, WindowManagerService does not have this permission, that is why it is impossible to generate events for somebody else's window.&lt;br /&gt;&lt;br /&gt;After all this diving into Android's internals, I show that there is a simpler way to generate key events by abusing the android.app.Instrumentation class. Check out the generateKeysWInst method in the example program. This method simply instantiates the Instrumentation class and calls only the sendKeyDownUpSync public method. This works because we know that sendKeyDownUpSync is so simple that it will function even with this bizarrely created instrumentation object. I don't know which method is more futureproof: using internal, non-public API or abusing a public API. Decide yourself.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-2374824735847096056?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/2374824735847096056/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=2374824735847096056' title='20 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2374824735847096056'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2374824735847096056'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/01/generating-keypresses-programmatically.html' title='Generating keypresses programmatically'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/SXTulhaskrI/AAAAAAAABOo/r47jmcu-0oc/s72-c/keygen.JPG' height='72' width='72'/><thr:total>20</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-5165798252181511642</id><published>2009-01-09T22:52:00.002+01:00</published><updated>2009-01-09T22:59:10.136+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='disassembler'/><category scheme='http://www.blogger.com/atom/ns#' term='dex'/><category scheme='http://www.blogger.com/atom/ns#' term='dedexer'/><title type='text'>Disassembling DEX files</title><content type='html'>One of the most remarkable features of the Dalvik virtual machine (the workhorse under the Android system) is that it does not use Java bytecode. Instead, a homegrown format called DEX was introduced and not even the bytecode instructions are the same as Java bytecode instructions. There was some discussion whether this makes Dalvik a Java virtual machine at all. My personal opinion is that this is a religious and legal dispute. Dalvik opcodes are clearly designed to support only the Java language. Compiling programs to Dalvik bytecode written in a language other than Java is certainly possible, as it was demonstrated with Java but neither the Java bytecode, nor the Dalvik bytecode makes any effort to support any language other than Java. This is in contrast with the .Net virtual machine where at least a claim has been made that the VM supports multiple languages - even though there are always limitations in any virtual machine that prevents running a particular language on a particular virtual machine.&lt;br /&gt;&lt;br /&gt;Android comes with a disassembler called dexdump. The location of this tool is not intuitive, it runs on the Linux platform that hosts Android. Launch the emulator, and issue the following commands:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;adb shell&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;dexdump&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In order to use the tool, one has to move the DEX file to the Android platform (e.g. adb push in case of the emulator). Then one can say:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;dexdump -d  classes.dex&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The output of this tool is not very easy to use, however. Take for example the bytecode compiled from the following switch statement.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;000418: 2b02 0c00 0000                         |0000: packed-switch v2, 0000000c // +0000000c&lt;br /&gt;00041e: 12f0                                   |0003: const/4 v0, #int -1 // #ff&lt;br /&gt;000420: 0f00                                   |0004: return v0&lt;br /&gt;000422: 1220                                   |0005: const/4 v0, #int 2 // #2&lt;br /&gt;000424: 28fe                                   |0006: goto 0004 // -0002&lt;br /&gt;000426: 1250                                   |0007: const/4 v0, #int 5 // #5&lt;br /&gt;000428: 28fc                                   |0008: goto 0004 // -0004&lt;br /&gt;00042a: 1260                                   |0009: const/4 v0, #int 6 // #6&lt;br /&gt;00042c: 28fa                                   |000a: goto 0004 // -0006&lt;br /&gt;00042e: 0000                                   |000b: nop // spacer&lt;br /&gt;000430: 0001 0300 faff ffff 0500 0000 0700 ... |000c: packed-switch-data (10 units)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The jump table used by the packed-switch instruction is not disassembled at all, it is not even dumped entirely. The same problem applies to fill-array-data tables and there are further restrictions.&lt;br /&gt;&lt;br /&gt;I decided therefore to create a more comfortable disassembler and here is the first cut.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://dedexer.sourceforge.net/"&gt;Access the dedexer project's page on SourceForge.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This tool is easier to use than dexdump for many reasons. For starter, it is a standard Java program that runs on the usual JVMs. Its format is much more readable and is familiar to those who know the &lt;a href="http://jasmin.sourceforge.net/"&gt;Jasmin syntax&lt;/a&gt;. For example the previous fragment is disassembled like this by dedexer:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;.method public calc1(I)I&lt;br /&gt;    packed-switch    v2,0&lt;br /&gt;        ps418_422    ; case 0&lt;br /&gt;        ps418_426    ; case 1&lt;br /&gt;        ps418_42a    ; case 2&lt;br /&gt;        default: ps418_default&lt;br /&gt;ps418_default:&lt;br /&gt;    const/4    v0,15&lt;br /&gt;l420:&lt;br /&gt;    return    v0&lt;br /&gt;ps418_422:&lt;br /&gt;    const/4    v0,2&lt;br /&gt;    goto    l420&lt;br /&gt;ps418_426:&lt;br /&gt;    const/4    v0,5&lt;br /&gt;    goto    l420&lt;br /&gt;ps418_42a:&lt;br /&gt;    const/4    v0,6&lt;br /&gt;    goto    l420&lt;br /&gt;    nop   &lt;br /&gt;.end method&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In addition, individual file is created for each class, along with the directory structure representing the package structure.&lt;br /&gt;&lt;br /&gt;This is not a full decompiler, however. One has to know the &lt;a href="http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html"&gt;Dalvik opcodes&lt;/a&gt; in order to work with the tool. This opcode list has been extended and maintained as dedexer was developed and is now in sync with the disassembler. You will see some unknown opcodes in the list. I have not encountered those instructions "out in the wild" and the disassembler does not recognize them either. If you see any of those, send me the DEX file so that I can analyse it!&lt;br /&gt;&lt;br /&gt;This is a simple tool and is not without limitations. The most painful one is that the tool does not process the debug and annotation information in the DEX file. Array data dump could also be better. I am sure that the feature most people would like to see is a bridge toward Java class files but that is far away. Jasmin will be able to generate Java class files once the backward conversion from Dalvik opcodes to Java bytecode is provided but that's a complex task so don't hold your breath. The condition I set for myself as release condition is that the tool is able to disassemble the DEX file in framework.jar. It is able to, so I guess, the tool may be of use for others too. Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-5165798252181511642?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/5165798252181511642/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=5165798252181511642' title='39 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5165798252181511642'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5165798252181511642'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2009/01/disassembling-dex-files.html' title='Disassembling DEX files'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>39</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-6406860553838003907</id><published>2008-12-13T01:38:00.004+01:00</published><updated>2008-12-13T01:59:03.305+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dex'/><category scheme='http://www.blogger.com/atom/ns#' term='opcodes'/><category scheme='http://www.blogger.com/atom/ns#' term='Dalvik'/><title type='text'>The Dalvik opcodes</title><content type='html'>I wanted to continue with my adventures with the Android test framework but I ran into some troubles. With pre-1.0 SDKs my solution was simple in these cases: take apart the SDK's android.jar and decomplile the relevant classes. In 1.0 SDK, however, all the classes in android.jar are just stubs, at least in the version on the PC filesystem. The real classes are in DEX format, on the emulated device's file system.&lt;br /&gt;&lt;br /&gt;That's sad news because the DEX format is not particularly well documented. More exactly: undocumented. There are some descriptions floating on the Internet but they are obsolete and inaccurate. Conveniently, the dx tool in Android SDK has some less used options that effectively document this format.&lt;br /&gt;&lt;br /&gt;Dx is the utility that turns Java class files into DEX files. Every Android developer uses it regularly, although not everybody may be aware of its existence because the tool is invoked by automatically generated make/ant files. Dx has an option that dumps the content of the DEX file in human-readable format while generating the DEX file. This is the batch script I use to get that dump:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;set BASEDIR=&lt;whatever&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;javac %1\*.java&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;dx --dex --verbose --verbose-dump --dump-to=%BASEDIR%\%1\dexdump.txt --output=%BASEDIR%\%1\classes.dex %BASEDIR%\%1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Put your Java files into a subdirectory (e.g. test1) and invoke the batch script with the name of the directory. Beside the familiar classes.dex, dexdump.txt will be generated. This dump file is so verbose that reverse engineering of the DEX file format becomes something of a feasible project.&lt;br /&gt;&lt;br /&gt;With using some test Java classes, &lt;a href="http://code.google.com/android/reference/dalvik/bytecode/Opcodes.html"&gt;the official Android opcode list&lt;/a&gt; and a lot of time, my first step was to document the Android opcodes. This is the bytecode the Dalvik virtual machine uses instead of the Java bytecode. If you are familiar with Java bytecode, you will see that the opcode set is pretty similar. Significant difference is that the Dalvik opcodes are register-based while Java bytecode is stack-based.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html"&gt;Click here to access the Dalvik opcode list.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The next step will be to put together a DEX disassembler. That will take some time, see you in 2009 with that!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-6406860553838003907?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/6406860553838003907/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=6406860553838003907' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6406860553838003907'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6406860553838003907'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/12/dalvik-opcodes.html' title='The Dalvik opcodes'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3257953271683100283</id><published>2008-12-02T23:04:00.003+01:00</published><updated>2008-12-02T23:33:39.642+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='instrumentation'/><category scheme='http://www.blogger.com/atom/ns#' term='junit'/><title type='text'>Instrumentation and JUnit</title><content type='html'>JUnit has been nicely integrated into Android. JUnit classes are specialized to facilitate common Android testing tasks. In this post, I will talk about my experiences with &lt;a href="http://code.google.com/android/reference/android/test/InstrumentationTestRunner.html"&gt;InstrumentationTestRunner&lt;/a&gt; and the facilities it provides to integrate Android instrumentation with JUnit test execution.&lt;br /&gt;&lt;br /&gt;As we have seen previously, the difficulty in JUnit-instrumentation integration stems from the fact that an instrumentation is an entire Android application, started, managed and terminated for testing purposes. One has to work quite a bit to make sure that the test controller is not terminated along with the application under test. InstrumentationTestRunner solves this problem by launching the Dalvik VM in special, instrumentation mode.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/instrrunner.zip"&gt;You can download the example program from here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The test subject is our well known Calculator program that was renamed as Calculator2 so that we do not confuse it with the version coming from previous application packages, other than that it is the same simple program. Under aexp.calculator.tests, you will find two JUnit test classes. There are things to note, however.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Instead of TestCase, the test classes inherit from ActivityInstrumentationTestCase, templated by the activity class under test.&lt;/li&gt;&lt;li&gt;The constructor of the test class defines the activity under test class precisely.&lt;/li&gt;&lt;/ul&gt;One such test class or collection of test classes can be executed by InstrumentationTestRunner. Execute the following command (from command prompt).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;adb shell am instrument -w aexp.calculator/android.test.InstrumentationTestRunner&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The console of the command prompt reads like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;aexp.calculator.tests.AddFunctionalTests:...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;aexp.calculator.tests.SubFunctionalTests:...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;Test results for InstrumentationTestRunner=......&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;Time: 11.448&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;OK (6 tests)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Meanwhile, the emulator main window flashes with action. Calculator2 is launched 4 times, each time the key input is automatically provided by the test case. All this is arranged by the Android specialization of TestCase/TestRunner.&lt;br /&gt;&lt;br /&gt;Please observe the AndroidManifest.xml of the project. Check out the uses-library tag and the use of instrumentation tag, particularly the targetPackage attribute that refers to the application package under test (and not the Java package name of the test classes). InstrumentationTestRunner automagically looks for test classes that fit the test execution criteria (again, check out &lt;a href="http://code.google.com/android/reference/android/test/InstrumentationTestRunner.html"&gt;InstrumentationTestCase documentation&lt;/a&gt; for options), there was no need to organize the test classes into suites.&lt;br /&gt;&lt;br /&gt;Note that the arrangement of this project is not typical. I mixed the test classes and the activity under test into the same application. This eliminates a lot of deployment problems that caused so much trouble for so many people when playing with the Android test framework. The typical deployment, however, is to place the test classes and application under test into separate application packages. I will get to that in the next post.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3257953271683100283?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3257953271683100283/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3257953271683100283' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3257953271683100283'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3257953271683100283'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/12/instrumentation-and-junit.html' title='Instrumentation and JUnit'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-2283508160101684768</id><published>2008-11-23T15:25:00.002+01:00</published><updated>2008-11-23T15:46:39.687+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='junit'/><category scheme='http://www.blogger.com/atom/ns#' term='unit test'/><title type='text'>JUnit in Android</title><content type='html'>Now that we have the instrumentation framework in our basket and we are able to connect instrumentation objects with a test runner, it is time to put a bit of structure into out tests. The number of test cases grows quicky. If there is no way to organize them or they depend on each other in a subtle way, test case maintenance soon becomes a nightmare. Experience shows that in case of a resource-constrained project, test effort is the first to suffer cutbacks and if the test cases are not maintained properly, the team or the developer may soon feel that the test set is too much hassle compared to the value the tests provide. The result is a fallback to the default, "chaotic" way of development with the predictable drop of quality.&lt;br /&gt;&lt;br /&gt;The popular JUnit test framework is integrated into Android. JUnit, if used properly, brings two major benefits to the test implementation.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;JUnit enforces the hierarchical organization of the test cases&lt;/li&gt;&lt;li&gt;The pattern JUnit is based on guarantees the independence of the test cases and minimizes their interference.&lt;/li&gt;&lt;/ul&gt; &lt;a href="http://manning.com/massol/"&gt;Entire books have been written about JUnit and unit testing&lt;/a&gt; therefore I will restrict myself to the minimum in explaining the test framework. JUnit's main building block is the TestCase class. Actual test case classes are inherited from TestCase. One TestCase child class contains one or more test methods, these test methods are the real test cases and the TestCase descendants are really already test groups. By convention, the test method names are prefixed with "test" (like testThis(), testThat(), etc.). JUnit can discover the test methods by itself if this convention is respected, otherwise the test method names have to be declared one by one.&lt;br /&gt;&lt;br /&gt;The most important impact of using JUnit is the way one test method is executed. For each test method, the TestCase descendant class is instantiated, its setUp() method is invoked, then the test method in question is invoked, then the tearDown() method is invoked then the instance becomes garbage. This is repeated for each test method. This guarantees that test methods are independent of each other, their interference is minimized. Of course, "clever" programmers can work around this pattern and implement test cases that depend on each other but this will bring us back to the mess that was to be eliminated in the first place. JUnit also provides ways to collect test classes into test suites and organize these suites hierarchically.&lt;br /&gt;&lt;br /&gt;JUnit has been included into Android and Android-specific extensions were provided in the android.test package. Android test cases classes should inherit from &lt;a href="http://code.google.com/android/reference/android/test/AndroidTestCase.html"&gt;android.test.AndroidTestCase&lt;/a&gt; instead of&lt;a href="http://code.google.com/android/reference/junit/framework/TestCase.html"&gt; junit.framework.TestCase&lt;/a&gt;. The main difference between the two is that AndroidTestCase knows about the &lt;a href="http://code.google.com/android/reference/android/content/Context.html"&gt;Context&lt;/a&gt; object and provides method to obtain the current Context. As many Android API methods need the Context object, this makes coding of Android test cases much easier. AndroidTestCase already contains one test method that tests the correct setup of the test case class, the testAndroidTestCaseSetupProperly method. This adds one test method to every AndroidTestCase descendant but this fact rarely disturbs the programmer.&lt;br /&gt;&lt;br /&gt;Those familiar with the "big", PC-based JUnit implementations will miss a UI-oriented TestRunner. The &lt;a href="http://code.google.com/android/reference/android/test/AndroidTestRunner.html"&gt;android.test.AndroidTestRunner&lt;/a&gt; does not have any user interface (not even console-based UI). If you want to get any readable feedback out of it, you have to handle callbacks from the test runner.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/junitexample.zip"&gt;You can download the example program from here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Our example program contains a very simple test runner UI, implemented as an Activity. The UI provides just the main progress indicators, details are written into the log so this implementation is nowhere near to the flexible test runners of the "big" JUnit. The test suite contains two test case classes. One of them (the MathTest) is really trivial. It demonstrates, however, the hidden testAndroidTestCaseSetupProperly() test method that explains the two extra test cases (the test runner will display 5 test cases when we have really only 3). The main lesson from the other test case class (ContactTest) is that individual test cases must be made independent from each other. This test case class inserts and deletes the test contact for each test method execution, cleaning up its garbage after each test execution. This is the effort that must be made to minimize interference. ExampleSuite organizes these two test classes into a suite. As we respected the JUnit test method naming conventions, we can leave the discovery of test methods to JUnit.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_K2nCeh0MHDY/SSlspRJu-rI/AAAAAAAABOg/BapWMMnRt_Q/s1600-h/androidjunit.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 184px;" src="http://3.bp.blogspot.com/_K2nCeh0MHDY/SSlspRJu-rI/AAAAAAAABOg/BapWMMnRt_Q/s320/androidjunit.JPG" alt="" id="BLOGGER_PHOTO_ID_5271864295048542898" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;At this point, we have everything to write structured, easy to maintain tests for Android, using the Android test instrumentation mechanism to drive the UI. This will be my next step.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-2283508160101684768?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/2283508160101684768/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=2283508160101684768' title='25 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2283508160101684768'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2283508160101684768'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/11/junit-in-android.html' title='JUnit in Android'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_K2nCeh0MHDY/SSlspRJu-rI/AAAAAAAABOg/BapWMMnRt_Q/s72-c/androidjunit.JPG' height='72' width='72'/><thr:total>25</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-6920628873655946147</id><published>2008-11-19T22:14:00.004+01:00</published><updated>2008-11-19T22:43:51.812+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='instrumentation'/><title type='text'>Controlling instrumentation</title><content type='html'>Those who are familiar with test frameworks could have felt something missing after the &lt;a href="http://mylifewithandroid.blogspot.com/2008/11/test-driving-android-gui.html"&gt;previous instrumentation example&lt;/a&gt;. The test did not finish very elegantly, both the test launcher and the test program were terminated and the results should be fished out from the logs. The reason is that when the instrumentation finishes, it terminates the entire &lt;span style="font-style: italic;"&gt;application&lt;/span&gt;, not just the activity that was instrumented. Although application as a notion exists since the beginning of Android's public life, developers usually concentrate on Activities. Application is a bunch of artifacts packed into one apk file, having one manifest file, running in the same JVM slice (seemingly independent JVM simulated for each application by the Dalvik virtual machine), represented &lt;a href="http://code.google.com/android/reference/android/app/Application.html"&gt;by the same Application object&lt;/a&gt;.  Instrumentation works on application level (as opposed to activity level) and when an instrumentation is finished, the entire application is terminated. That's why our instrumentation launcher did not survive the end of instrumentation - as it lived in the same application, it was also terminated along with the instrumentation object and the target application.&lt;br /&gt;&lt;br /&gt;If we want to build a decent automated test framework, we have to control it from a separate application. This example does just that.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/instrumentation2.zip"&gt;You can download the example program from here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In the package, you will find two application directories. The instrumentation1 directory contains our friends, the slightly modified calculator test target, the instrumentation object and the instrumentation launcher that we will not use now. The instrcontroller directory contains a separate application, our instrumentation controller that starts the instrumentation and receives the test result. Like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_K2nCeh0MHDY/SSSFO-Nz2FI/AAAAAAAABOY/rgJ6-mPpkSk/s1600-h/instrcontroller.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 158px;" src="http://1.bp.blogspot.com/_K2nCeh0MHDY/SSSFO-Nz2FI/AAAAAAAABOY/rgJ6-mPpkSk/s320/instrcontroller.JPG" alt="" id="BLOGGER_PHOTO_ID_5270483956195907666" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The only interesting part is the communication between the instrumentation object and the launcher. The instrumentation object has a finish() method that is seemingly capable of sending back a result Bundle to the instrumentation's starter but I was not able to figure out, how that bundle is received by the instrumentation's starter (&lt;a href="http://groups.google.com/group/android-developers/browse_thread/thread/f175347d642a2226"&gt;my Android newsgroup thread also remained unanswered&lt;/a&gt;). Therefore I had no better idea than to create a not too elegant do-it-yourself method of sending back the test result using broadcast intents. If anyone knows how to do it right, please notify me.&lt;br /&gt;&lt;br /&gt;And before I finish, something completely different.&lt;br /&gt;&lt;br /&gt;I received a mail from the &lt;a href="http://www.phoload.com/android"&gt;Phoload people that they launched their Android content&lt;/a&gt; and they asked me to advertise this fact in one of my posts. Certainly, it is a new market, there is a lot of risk taking here so the early birds definitely deserve help - however small mine can be. I did have my concerns, however, how this Phoload site would coexist e.g. with &lt;a href="http://www.android.com/market/"&gt;Android Marketplace&lt;/a&gt; but the Phoload people are convinced that they can carve out a nice piece of market by offering mobile applications for multiple platforms and by providing easier navigation and more visible place for applications. Let they be right and have success!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-6920628873655946147?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/6920628873655946147/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=6920628873655946147' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6920628873655946147'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6920628873655946147'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/11/controlling-instrumentation.html' title='Controlling instrumentation'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_K2nCeh0MHDY/SSSFO-Nz2FI/AAAAAAAABOY/rgJ6-mPpkSk/s72-c/instrcontroller.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3567378790629294286</id><published>2008-11-16T21:31:00.007+01:00</published><updated>2008-11-16T22:23:53.707+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='instrumentation'/><category scheme='http://www.blogger.com/atom/ns#' term='unit test'/><title type='text'>Test-driving Android GUI</title><content type='html'>It happened again that I received an e-mail from somebody asking whether there is a way to automate the testing of Android user interfaces. I did not know so I went after the issue and to my utter surprise, I discovered that an entire unit test framework has been developed into Android while I looked the other way. In this blog entry, I will describe my experiences with the Android instrumentation framework.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/instrumentation1.zip"&gt;You can download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The idea behind Android instrumentation framework is that there are instrumentation components that resemble a lot the usual Activities. The purpose of these instrumentation components, however, is to test-drive other, normal Activities. Let's see the example program!&lt;br /&gt;&lt;br /&gt;Install the package in the bin directory as usual:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;adb install instrumentation-debug.apk&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If the package has been installed before, you first have to uninstall it. This is new in the 1.0 version of the SDK, previously the new installation would simply overwrite the old one.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;adb uninstall aexp.instrumentation&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Two applications appear in the application folder. "Calculator" is a primitive calculator application used as test target. You can launch it independently and try it yourself. If you launch "Calculator instrumentation launcher", you will be confronted with a button to launch Calculator instrumentation. Once you push it, the calculator launches but its input keypresses will come from the instrumentation component. You will see the input fields filled up automatically, the addition button pushed and the result displayed. Then both the calculator instance and the launcher will disappear but if you check the logs (adb logcat), you will see that the test was successful.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_K2nCeh0MHDY/SSCMtVUSyMI/AAAAAAAABOQ/9oK11NNWxh8/s1600-h/instrcalc.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 202px;" src="http://2.bp.blogspot.com/_K2nCeh0MHDY/SSCMtVUSyMI/AAAAAAAABOQ/9oK11NNWxh8/s320/instrcalc.JPG" alt="" id="BLOGGER_PHOTO_ID_5269366274467743938" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The key instruction is in CalculatorInstrumentationLauncher.java where the instrumentation component is launched:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;               startInstrumentation(&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;                    new ComponentName( &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;                        CalculatorInstrumentationLauncher.this,&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;                        CalculatorInstrumentation.class ), &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;                    null, &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;                    null );&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This launches CalculatorInstrumentation component which is a descendant of android.app.Instrumentation. The instrumentation component resembles a normal Activity, except that it is able to drive user input of other activities. This time we launch the Calculator, feed in keypresses (sendCharacterSync()) and invoke one of its method to obtain the result. Note the usage of runOnMainSync() method when we invoke Calculator's method to obtain the result.&lt;br /&gt;&lt;br /&gt;It is definitely cool that Android has a user interface-oriented unit testing framework built into the platform. Maybe I am ignorant but I am not aware of any other platform that has this capability. In order to &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;Test Driven Development (TDD),&lt;/a&gt; we need a proper unit testing framework and Android has even JUnit integrated. More about that later.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3567378790629294286?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3567378790629294286/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3567378790629294286' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3567378790629294286'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3567378790629294286'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/11/test-driving-android-gui.html' title='Test-driving Android GUI'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_K2nCeh0MHDY/SSCMtVUSyMI/AAAAAAAABOQ/9oK11NNWxh8/s72-c/instrcalc.JPG' height='72' width='72'/><thr:total>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-5476658720914960808</id><published>2008-11-04T23:10:00.003+01:00</published><updated>2008-11-04T23:31:01.323+01:00</updated><title type='text'>Again about synchronization</title><content type='html'>Once I wrote a post about&lt;a href="http://mylifewithandroid.blogspot.com/2008/02/synchronization-in-android.html"&gt; synchronization in Android on this blog.&lt;/a&gt; Although that post is rather obsolete now because the synchronization-related Android API changed a lot, old sins did come back to haunt me. Rushang Shah from CompanionLink Software wrote a kind letter to me because of that old blog entry asking my opinion &lt;a href="http://www.companionlink.com/products/android.html"&gt;about their Android-based synchronization solution&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Disclaimer: Although I have no relationship whatsoever with CompanionLink, I do advertise their product now because of that nice letter. :-)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The product is an Android-based synchronization solution for PIM and CRM data. Try it, it is a great product but I use it as a pretext to emphasize again my mania about synchronization as a key mobile enabler. I have already&lt;a href="http://mylifewithandroid.blogspot.com/2008/02/android-is-most-modern-application.html"&gt; written a post about it&lt;/a&gt;, there is also a&lt;a href="http://www.springerlink.com/content/l3210774w7752127/?p=d8a8b89a32ce41eba4ab52b900a9cdb4&amp;amp;pi=8"&gt; more detailed paper&lt;/a&gt; involved there. My claim is that synchronization should not be considered as part of PIM or CRM systems, it is a general enabler because mobility inherently requires disconnected operation due to varying network conditions. Therefore my dream synchronization solution is not restricted to predefined data types but can be used as a building block in any application.&lt;br /&gt;&lt;br /&gt;In order to demonstrate my point, &lt;a href="http://pallergabor.uw.hu/androidblog/peeble.zip"&gt;here is a larger Android client-Java server application I wrote some time back.&lt;/a&gt; The application itself probably cannot be executed on the latest Android platform because the API changed since and I was lazy to bring up the application to the latest API. It does demonstrate, however, that synchronization needs can be identified in any mobile application, for any type of mobile data.&lt;br /&gt;&lt;br /&gt;So Rushang, if you listen to me, you turn your product into a general-purpose synchronization component.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-5476658720914960808?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/5476658720914960808/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=5476658720914960808' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5476658720914960808'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5476658720914960808'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/11/again-about-synchronization.html' title='Again about synchronization'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7954169199773951708</id><published>2008-05-13T14:36:00.003+02:00</published><updated>2008-05-13T14:42:33.541+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='expandable'/><category scheme='http://www.blogger.com/atom/ns#' term='list'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>Expandable lists</title><content type='html'>Multilevel, expandable lists are useful user interface elements and Android does support two-level expandable lists. This fact would not be worth a blog entry if there were no traps using the widget.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/explist.zip"&gt; Click here to download the example program.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The example program implements a simple, two-level expandable list to display color shade information.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_K2nCeh0MHDY/SCmL2CnlgCI/AAAAAAAAAs8/1sY2uLoFufE/s1600-h/expandable.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_K2nCeh0MHDY/SCmL2CnlgCI/AAAAAAAAAs8/1sY2uLoFufE/s320/expandable.JPG" alt="" id="BLOGGER_PHOTO_ID_5199841005308051490" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The first thing to note is that our Activity extends android.app.ExpandableListActivity. Otherwise setting up the list is pretty similar to ordinary ListActivity classes. The tricky part is the android.widget.SimpleExpandableListAdapter.&lt;br /&gt;&lt;br /&gt;       &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;SimpleExpandableListAdapter expListAdapter =&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;      &lt;br /&gt;new SimpleExpandableListAdapter(&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;this,&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;createGroupList(),    // groupData describes the first-level entries&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;R.layout.child_row,    // Layout for the first-level entries&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;          new&lt;br /&gt;String[] { "colorName" },  // Key in the groupData maps to display&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;new int[] { R.id.childname },  // Data under "colorName" key goes into this TextView&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;          createChildList(),    // childData describes second-level entries&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;R.layout.child_row,    // Layout for second-level entries&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;new String[] { "shadeName", "rgb" },    // Keys in childData maps to display&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;new int[] { R.id.childname, R.id.rgb } // Data under the keys above go into these TextViews&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;      &lt;br /&gt;);&lt;/span&gt;&lt;/small&gt;&lt;br /&gt;&lt;br /&gt;First of all, this adapter requires somewhat complicated (although pretty well-documented) data structures. There are two of them, one describing the first-level group elements, the other describing the second-level string elements.&lt;br /&gt;&lt;br /&gt;Here is the first one:&lt;br /&gt;List-&gt;Map&lt;br /&gt;Every map describes one first-level group element. The keys in the Map are arbitrary but are specified for SimpleExpandableListAdapter so that it can map the keys in the Map to widget IDs. In our case, the data under key name "colourName" will be set as the value of the TextView with "childname" ID.&lt;br /&gt;&lt;br /&gt;The second one is a bit more complicated.&lt;br /&gt;List-&gt;List-&gt;Map&lt;br /&gt;Every entry in the first List represents a group. Entries in the second List represent entries of the group and each such entry is a Map that describes one child, similarly to the description of groups. In our case, the child Map contains two entries (keys "shadeName" and "rgb") that go to TextViews ("childname" and "rgb").&lt;br /&gt;&lt;br /&gt;So far so good. Unfortunately, SimpleExpandableListAdapter has its tricks. First of all, the adapter takes the description of one row from a layout (child_row in our case, located under res/layout/child_row.xml). When the adapter draws the expand button onto the row, it does not consider the content of the row, it simply draws the button over the row. That's the reason child_row.xml defines generous left padding for the first TextView. While this behaviour can be considered just an oddity, the fact that SimpleExpandableListAdapter must be created with identical views for first- and second-level lines &lt;a href="http://groups.google.com/group/android-developers/browse_thread/thread/5a761c4f1c7e6e3d"&gt;is a plain bug that existed at least since m3-rc37a&lt;/a&gt;. Having group- and child rows with different styles is a cool and useful feature that is supported by SimpleExpandableListAdapter but does not work.&lt;br /&gt;&lt;br /&gt;Replace this fragment:&lt;br /&gt;&lt;br /&gt;&lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;          R.layout.child_row,    // Layout for the first-level entries&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;new String[] { "colorName" },  // Key in the groupData maps to display&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;new int[] { R.id.childname },  // Data under "colorName" key goes into this TextView&lt;/span&gt;  &lt;/small&gt;&lt;br /&gt;&lt;br /&gt;with this one:&lt;br /&gt;&lt;br /&gt;&lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;          R.layout.group_row,    // Layout for the first-level entries&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;         &lt;br /&gt;new String[] { "colorName" },  // Key in the groupData maps to display&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;         new int[] { R.id.groupname },  // Data under "colorName" key goes into this TextView&lt;/span&gt;  &lt;/small&gt;&lt;br /&gt;&lt;br /&gt;and you will see, how the expandable list gets confused.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7954169199773951708?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7954169199773951708/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7954169199773951708' title='23 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7954169199773951708'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7954169199773951708'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/05/expandable-lists.html' title='Expandable lists'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_K2nCeh0MHDY/SCmL2CnlgCI/AAAAAAAAAs8/1sY2uLoFufE/s72-c/expandable.JPG' height='72' width='72'/><thr:total>23</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-2058333147083936392</id><published>2008-04-28T22:46:00.004+02:00</published><updated>2008-04-28T22:57:34.614+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='views'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>Custom views</title><content type='html'>Now that I entertained myself (and hopefully the readers of the blog as well) with custom adapters and animations, I set out to an even more adventurous exercise. In Android, entirely new view classes could be created and used in conjunction with XML layout files, similarly to built-in views. Views have their own lifecycle. Intererested readers should consult the documentation of  &lt;a href="http://code.google.com/android/reference/android/view/View.html"&gt;android.view.View&lt;/a&gt; class. Very shortly: views may handle the event when their inflation is finished, when they are being measured by the layout manager, when they are being laid out, when their size change, when they gain or lose focus, when they are attached to and detached from a window and when the window they are attached to changes visibility. This is pretty complex so I chose an easier way to introduce custom views by extending a built-in view.&lt;br /&gt;&lt;br /&gt;The scenario which absolutely requires custom views is the following: we have a list of alarm events and some of them requires the user's acknowledgement. There is a flashing exclamation mark beside such alarms. Like this (except that the exclamation mark is flashing:)&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_K2nCeh0MHDY/SBY5cZNmSvI/AAAAAAAAArQ/AOD_xSjNBj8/s1600-h/customview.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_K2nCeh0MHDY/SBY5cZNmSvI/AAAAAAAAArQ/AOD_xSjNBj8/s320/customview.JPG" alt="" id="BLOGGER_PHOTO_ID_5194402380185488114" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/customview.zip"&gt;You can download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;We will support the list with a custom adapter,&lt;a href="http://mylifewithandroid.blogspot.com/2008/04/custom-widget-adapter-based-on-xml.html"&gt; as we have seen previously&lt;/a&gt;. A single row for this adapter looks like this (located under res/layout/alarm_row.xml, XML mangling because of blog engine limitation).&lt;br /&gt;&lt;br /&gt;[?xml version="1.0" encoding="utf-8"?]&lt;br /&gt;[LinearLayout&lt;br /&gt;    xmlns:android="http://schemas.android.com/apk/res/android"&lt;br /&gt;    android:layout_width="fill_parent"&lt;br /&gt;    android:layout_height="wrap_content"&lt;br /&gt;    android:orientation="horizontal"]&lt;br /&gt;&lt;br /&gt;   [TextView android:id="@+id/alarmtext"&lt;br /&gt;        android:textSize="16px"&lt;br /&gt;        android:layout_width="280px"&lt;br /&gt;        android:layout_height="wrap_content"/]&lt;br /&gt;&lt;br /&gt;   [aexp.customview.AlarmingView&lt;br /&gt;       android:id="@+id/alarming"&lt;br /&gt;       android:layout_width="wrap_content"&lt;br /&gt;       android:layout_height="wrap_content"/]&lt;br /&gt;[/LinearLayout]&lt;br /&gt;&lt;br /&gt;TextView is our old friend but the second view in the row is our own. If you check the aexp.customview.AlarmingView class, you will see that it is a descendant of ImageView. We still have to define three constructors (one for the creation from code, 2 others for inflating from XML) but the rest of the view lifecycle handling is inherited from ImageView. The flashing effect is achieved by periodically updating the drawable resource displayed by the underlying ImageView.&lt;br /&gt;&lt;br /&gt;There is one point worth highlighting. There is a single private class handling the periodic flashing (coded as singleton for all AlarmingView instances to save resources and achieve synchronous flashing) but that class does not use the java.lang.Thread mechanism. If you code it that way (I did first :-( ), the application is shut down with an error message that views are not thread-safe and can be manipulated only from the thread that created them. Hence, we create the private class instance when the first AnimatedView is created and therefore the Handler instance in the private class will also be created in the context of the thread that creates the AnimatedView in the first place. Then we request this Handler instance to update the state of our views periodically.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-2058333147083936392?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/2058333147083936392/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=2058333147083936392' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2058333147083936392'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2058333147083936392'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/04/custom-views.html' title='Custom views'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_K2nCeh0MHDY/SBY5cZNmSvI/AAAAAAAAArQ/AOD_xSjNBj8/s72-c/customview.JPG' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-8953081277406895332</id><published>2008-04-22T23:19:00.003+02:00</published><updated>2008-04-22T23:24:16.989+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='animation'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>Animated views</title><content type='html'>I have to admit, I am aesthetically challenged. Creating a nice GUI is not my game and I respected those who could design such things. Because of this limitation of mine, I generally look down on glittering UIs with contempt. But even I was moved - even if only a bit - by Android's animation capabilities.&lt;br /&gt;&lt;br /&gt;Android is able to animate any View object and each and every View has a startAnimation() method that launches animation. Animations exist as independent objects that can be applied to the Views. Animation objects can even be populated from XML resources that live in the res/anim directory.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/animatedview.zip"&gt; You can download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Let's look at the animation file below (again, XML mangling due to the limitations of the blog engine). This file can be found as res/anim/magnify.xml in the example program bundle.&lt;br /&gt;&lt;br /&gt;[?xml version="1.0" encoding="utf-8"?]&lt;br /&gt;[scale xmlns:android="http://schemas.android.com/apk/res/android"&lt;br /&gt;  android:fromXScale="0.5"&lt;br /&gt;  android:toXScale="2.0"&lt;br /&gt;  android:fromYScale="0.5"&lt;br /&gt;  android:toYScale="2.0"&lt;br /&gt;  android:pivotX="0%"&lt;br /&gt;  android:pivotY="50%"&lt;br /&gt;  android:startOffset="0"&lt;br /&gt;  android:duration="400"&lt;br /&gt;  android:fillBefore="true" /]&lt;br /&gt;&lt;br /&gt;This is a scaling animation. The attributes mean the followoing:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The content of the view is actually shrinken first (fromXScale, fromYScale=0.5) to half before it starts to grow to double its standard size and 4 times of the initial size (0.5-&gt;2.0). This is done both in X and Y axis.&lt;/li&gt;&lt;li&gt;The pivot point from which the object grows is 0% in the X axis. This means that the leftmost points of the object will stay in place and the object grows rightward to double size. Meanwhile, the pivot point on the Y axis is the middle of the object (50%), this means that the object will grow up and down from its center line.&lt;/li&gt;&lt;li&gt;The animation starts immediately (startOffset=0) and finishes in 400 msec.&lt;/li&gt;&lt;/ul&gt; Once we have an animation object, we can apply it to any view. In the example program, I created a simple list and applied the animation to the TextView list elements whenever one list element is selected (either by moving the selection with the arrow keys or clicking at list elements). Two points are worth noting:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Just because we animate, the list layout is not recalculated. Hence the generous top and bottom paddings around the list elements. There is enough space provided for the TextView to grow.&lt;/li&gt;&lt;li&gt;In the list row layout (res/layout/row.xml), the layout_width is set to fill_parent. This is seemingly random choice but actually, the program does not work well with wrap_content as layout_width. Whenever a list element is selected, its color changes to highlight. If the width is wrap_content, then the size of the TextView (hence the higlighted screen area) is just the length of the text. When the animation starts, the text grows past this size and the end of the text becomes unreadable. This is very important therefore to allow the list row to occupy the entire available width because then the entire row will be highlighted and the animation cannot grow larger than the highlighted area.&lt;/li&gt;&lt;/ul&gt; At last, two pictures about the beginning and the end of the animation.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_K2nCeh0MHDY/SA5W85NmSUI/AAAAAAAAAm0/JKqZxrTQoJo/s1600-h/animated_idle.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_K2nCeh0MHDY/SA5W85NmSUI/AAAAAAAAAm0/JKqZxrTQoJo/s320/animated_idle.JPG" alt="" id="BLOGGER_PHOTO_ID_5192183024554756418" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_K2nCeh0MHDY/SA5XQJNmSWI/AAAAAAAAAnA/xndOZ-nm0bY/s1600-h/animated_selecting.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_K2nCeh0MHDY/SA5XQJNmSWI/AAAAAAAAAnA/xndOZ-nm0bY/s320/animated_selecting.JPG" alt="" id="BLOGGER_PHOTO_ID_5192183355267238242" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-8953081277406895332?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/8953081277406895332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=8953081277406895332' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8953081277406895332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/8953081277406895332'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/04/animated-views.html' title='Animated views'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_K2nCeh0MHDY/SA5W85NmSUI/AAAAAAAAAm0/JKqZxrTQoJo/s72-c/animated_idle.JPG' height='72' width='72'/><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-2034802846022676835</id><published>2008-04-21T01:47:00.002+02:00</published><updated>2008-04-21T01:51:29.051+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adapter'/><category scheme='http://www.blogger.com/atom/ns#' term='XML'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>Custom widget adapter based on XML layout</title><content type='html'>&lt;a href="http://www.anddev.org/custom_widget_adapters-t1796.html"&gt;Lex from anddev.org pointed out&lt;/a&gt; an annoying property of &lt;a href="http://mylifewithandroid.blogspot.com/2008/04/custom-widget-adapters.html"&gt;my custom Weather adapter example&lt;/a&gt;: the view row the Weather objects are transformed to are encoded in Java instead of being defined in XML layout resource. This makes the weather display style harder to modify and every application using the WeatherAdapter has the same weather screen layout.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/customadapterxml.zip"&gt; You can download the example program from here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I created a new version of WeatherAdapter that takes an additional parameter in its constructor, an ID of a composite view (a LinearLayout in our case) that has three children views having the IDs "city", "temperature" and "sky" respectively. The code is flexible enough to handle the case when one or more of these child views are missing, in this case the field will simply not set from the Weather object. Check the res/layout/weather_row.xml file for an example.&lt;br /&gt;&lt;br /&gt;The new version is indeed more flexible which is demonstrated by the bit more complicated display of weather rows. Thanks for the feedback, Lex, it really made this example program more valuable.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_K2nCeh0MHDY/SAvW3GC7XAI/AAAAAAAAAmU/hW1d6GVAjuI/s1600-h/weatherdisplayxml.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_K2nCeh0MHDY/SAvW3GC7XAI/AAAAAAAAAmU/hW1d6GVAjuI/s320/weatherdisplayxml.jpg" alt="" id="BLOGGER_PHOTO_ID_5191479237478669314" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-2034802846022676835?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/2034802846022676835/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=2034802846022676835' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2034802846022676835'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2034802846022676835'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/04/custom-widget-adapter-based-on-xml.html' title='Custom widget adapter based on XML layout'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_K2nCeh0MHDY/SAvW3GC7XAI/AAAAAAAAAmU/hW1d6GVAjuI/s72-c/weatherdisplayxml.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7909585983148085208</id><published>2008-04-18T16:38:00.002+02:00</published><updated>2008-04-18T16:42:48.994+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adapter'/><category scheme='http://www.blogger.com/atom/ns#' term='widget'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>Custom widget adapters</title><content type='html'>In an earlier post, &lt;a href="http://mylifewithandroid.blogspot.com/2008/03/my-first-meeting-with-simpleadapter.html"&gt;I wrote about the SimpleAdapter&lt;/a&gt; and how SimpleAdapter allows significant flexibility when laying out list items. SimpleAdapter is great but then I became curious what it takes to write an adapter.&lt;br /&gt;&lt;br /&gt;Adapters are simple devices. On one side of the adapter is a data structure like a Java object storing data. SimpleAdapter handles Java objects that can be meaningfully translated into Strings by invoking the objects' toString() method (every Java object supports that but for quite many of them, the toString() format is not meaningful for the end user). On the other side of the adapter, there is a View that the data structure was transformed into. That View is displayed to the user. As we use Adapters to supports list views, the Adapter handles lists of Java objects (that are eventually transformed into a list of Views).&lt;br /&gt;&lt;br /&gt;Android's built-in adapters are sufficiently versatile but it is often handy to create a custom adapter. Let's look at the following example.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_K2nCeh0MHDY/SAiy-2FMMBI/AAAAAAAAAmM/SjEJ2DOQxaM/s1600-h/weatherdisplay.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_K2nCeh0MHDY/SAiy-2FMMBI/AAAAAAAAAmM/SjEJ2DOQxaM/s320/weatherdisplay.JPG" alt="" id="BLOGGER_PHOTO_ID_5190595363283415058" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/customadapter.zip"&gt;You can download the example code from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The example is a simple weather display. A weather entry consists of 3 data items: name of the city (String), temperature in the city in degrees (integer) and an icon showing whether the sky is sunny, overcast or it is raining. The first two entries could be handled using SimpleAdapter but the third cannot: SimpleAdapter does not handle icons. Therefore we create our own adapter, called WeatherAdapter that takes list of Weather objects storing the weather info and turns that list into Views that can be rendered as list rows.&lt;br /&gt;&lt;br /&gt;The most important part of the trick happens in a private class in WeatherAdapter.java called WeatherAdapterView. WeatherAdapter does nothing more than manages a list of WeatherAdapterViews. WeatherAdapterView is the View the Weather data object is mapped to. It is itself a composite View, composed by a LinearLayout. The LinearLayout is set up programmatically (as opposed to an XML layout) and contains two TextViews and one ImageView. The ImageView encapsulates the icon. It is worth checking how the icon images are referenced in Weather.java: as the icons are in the res/drawable directory, R.java has integer IDs for them. The getSkyResource() method in the Weather class just returns this resource ID based on the sky member variable of the Weather class.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7909585983148085208?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7909585983148085208/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7909585983148085208' title='19 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7909585983148085208'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7909585983148085208'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/04/custom-widget-adapters.html' title='Custom widget adapters'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_K2nCeh0MHDY/SAiy-2FMMBI/AAAAAAAAAmM/SjEJ2DOQxaM/s72-c/weatherdisplay.JPG' height='72' width='72'/><thr:total>19</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-5362192955249329366</id><published>2008-03-16T23:37:00.004+01:00</published><updated>2008-03-16T23:59:42.159+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='simpleadapter'/><category scheme='http://www.blogger.com/atom/ns#' term='widget'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>My first meeting with the SimpleAdapter widget</title><content type='html'>Such a shame. Normally, Android developers have their SimpleAdapter adventures right at the beginning of their Android development career, meanwhile I met this beast just now. I wouldn't say that this UI feature is overdocumented and that there are truckloads of example programs out there (if there are, Google does not find them) so I decided to share my experiences with whoever cares to read this blog entry.&lt;br /&gt;&lt;br /&gt;It all started with a very simple wish that I wanted to create a list like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_K2nCeh0MHDY/R92iQfRGOYI/AAAAAAAAAS4/k9ijTtuN3PU/s1600-h/customlist.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_K2nCeh0MHDY/R92iQfRGOYI/AAAAAAAAAS4/k9ijTtuN3PU/s320/customlist.JPG" alt="" id="BLOGGER_PHOTO_ID_5178473550700755330" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Each list entry has two lines and the lines are styled differently. Fortunately, there is an example program at &lt;a href="http://code.google.com/android/reference/android/app/ListActivity.html"&gt;the ListActivity documentation page&lt;/a&gt; which happens not to work and takes the input behind the list from a database. So it took me some time to find my friend, the SimpleAdapter widget and to find out, how to use it.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/customlist.zip"&gt;You can download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The root of the problem is that each element of this list is a complex view, consisting of two TextViews (XML mangling because of blog engine limitations):&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;[?xml version="1.0" encoding="utf-8"?]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;[LinearLayout&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;     xmlns:android="http://schemas.android.com/apk/res/android"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;     android:layout_width="fill_parent"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;     android:layout_height="wrap_content"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;     android:orientation="vertical"]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;     [TextView android:id="@+id/text1"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         android:textSize="16px"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         android:textStyle="bold"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         android:layout_width="fill_parent"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         android:layout_height="wrap_content"/]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;     [TextView android:id="@+id/text2"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         android:textSize="12px"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         android:textStyle="italic"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         android:layout_width="fill_parent"&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;         android:layout_height="wrap_content"/]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;[/LinearLayout]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The corresponding SimpleAdapter maps an element from list of objects to this view.&lt;br /&gt;&lt;br /&gt; &lt;span style="font-family: courier new;"&gt;      SimpleAdapter notes = new SimpleAdapter( &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;            this, &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;            list,&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;            R.layout.main_item_two_line_row,&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;            new String[] { "line1","line2" },&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;            new int[] { R.id.text1, R.id.text2 }  );&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now this is not a trivial line of code. What it says is:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;"list" is a reference to an object implementing the List interface. Each element in this list is an object implementing the Map interface. In our implementation example, the list is ArrayList and the elements in this list are HashMaps.&lt;/li&gt;&lt;li&gt;The layout describing one row in the main list is defined in layout/main_item_two_line_row.xml file.&lt;/li&gt;&lt;li&gt;In each individual HashMap, there are two key-value pairs. The keys are "line1" and "line2", the corresponding values are arbitrary objects whose toString() method yields the value displayed in the list row. In our case, the values are Strings.&lt;/li&gt;&lt;li&gt;Values stored with key "line1" will be displayed in the TextView whose id is "text1". Similarly, "line2" Map key is associated with "text2" TextView.&lt;/li&gt;&lt;/ul&gt;You can try the program following the instructions in the &lt;a href="http://mylifewithandroid.blogspot.com/2007/12/my-first-steps-with-command-line.html"&gt;Start here&lt;/a&gt; entry. You can add items to the list using the menu.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-5362192955249329366?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/5362192955249329366/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=5362192955249329366' title='32 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5362192955249329366'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/5362192955249329366'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/03/my-first-meeting-with-simpleadapter.html' title='My first meeting with the SimpleAdapter widget'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_K2nCeh0MHDY/R92iQfRGOYI/AAAAAAAAAS4/k9ijTtuN3PU/s72-c/customlist.JPG' height='72' width='72'/><thr:total>32</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-6900980795637004749</id><published>2008-03-11T22:45:00.003+01:00</published><updated>2008-03-11T22:51:00.882+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='content providers'/><title type='text'>Observing content</title><content type='html'>Android documentation makes the point about content providers that they are perfect tool for hiding data access details.  The Android content provider API has a clever little feature, however, that allows applications (and services) to observe changes of a dataset. The use case is that some component manipulates the persistent dataset that other components depend upon. The usual pattern is that the component makes the changes then invokes some sort of notification interface so that the dependent components are aware that the dataset was changed. If such a strong coupling is not possible, the dependent components may even need to poll for changes.&lt;br /&gt;&lt;br /&gt;The Android content provider framework allows a much more elegant design. As datasets are identified by unique URIs, it is possible to ask for notifications if a certain URI is changed. The framework is the following.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Well-behaving content providers are expected to notify the content resolver if they do something that may potentially change their dataset. This includes insert and update operations but registering database cursors returned by query operations is also necessary because cursors may be used to update data.&lt;/li&gt;&lt;li&gt;Components interested in dataset changes register at the content resolver. If any URI is manipulated that they registrated for, they get notification.&lt;/li&gt;&lt;/ol&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/dpobserver.zip"&gt; You can download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The content observer framework depends on the cooperation of content providers and content observers. Content providers are expected to notify content resolver that they have updated the dataset. You can observe this in our well-known simple provider, SimpleStringDataProvider.java.&lt;br /&gt;&lt;br /&gt;In  insert method:&lt;br /&gt;&lt;br /&gt;&lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;getContext().getContentResolver().notifyChange(uri, null);&lt;/span&gt;&lt;br /&gt;&lt;/small&gt;&lt;br /&gt;This notifies all the observers registered for that particular URI that change happened.&lt;br /&gt;&lt;br /&gt;In query method:&lt;br /&gt;&lt;br /&gt;&lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;c.setNotificationUri(getContext().getContentResolver(), url);&lt;/span&gt;&lt;/small&gt;&lt;br /&gt;&lt;br /&gt;where c is reference to a Cursor object. If the data behind the cursor's position is updated (Cursor.update*(), Cursor.commitUpdates()), the given URI will be notified.&lt;br /&gt;&lt;br /&gt;The other side of the picture is the observer. In this simple example, the observer is located in the DPObserver activity. As the observer is unregistered using the observer object reference, the observer is registered at onStart() and unregistered at onStop() because after onStop() the state of the activity may be lost.&lt;br /&gt;&lt;br /&gt;In registerContentObservers:&lt;br /&gt;&lt;br /&gt;&lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;ContentResolver cr = getContentResolver();&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;stringsObserver = new StringsContentObserver( handler );&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;cr.registerContentObserver( SimpleString.Strings.CONTENT_URI, true, stringsObserver );&lt;/span&gt;&lt;/small&gt;&lt;br /&gt;&lt;br /&gt;We requested notification for the content URI allocated to the Strings provider plus all its descendants. This is important because e.g. the Strings provider generates URIs for newly allocated items like the following: content://aexp.dpobserver.SimpleString/strings/[id]&lt;id&gt; where &lt;id&gt; [id] is an integer number. These URIs are not equals to the Strings provider's base URI, these are descendants of the base URI.&lt;br /&gt;&lt;br /&gt;The observer object is a child of the android.database.ContentObserver class. Its onChange() method is called by the content resolver if there is a change of the data behind the URI the observer was registered for. Unfortunately the URI that triggered the invocation is not passed to the method. The onChange() method is called in the context of a Handler. In our case, this Handler uses the UI thread of the activity.&lt;br /&gt;&lt;br /&gt;You can try the application by launching the DPObserver activity and monitoring the log (adb logcat from the command prompt). Whenever you add a new data item (by clicking a menu item), the onChange() will generate a log message.&lt;br /&gt;&lt;br /&gt;Now comes the interesting bit. We actually registered another observer, that one for the URI of content://contacts/people and descendants which belongs to the Contacts application. Launch now the Contacts application and add a new user. You will see in the log that our activity got called (except if it was stopped by the application manager because it went into the background). At this point, we could access the Contacts content provider and find out, what entry was added. It would be much more easier if we had the URI of the added entry but finding the new entry is possible by looking for the entry with the highest ID. We could hook onto Contacts database manipulations with our own functionality. Isn't that interesting? ;-)&lt;/id&gt;&lt;/id&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-6900980795637004749?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/6900980795637004749/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=6900980795637004749' title='19 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6900980795637004749'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6900980795637004749'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/03/observing-content.html' title='Observing content'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>19</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-3504749552539899526</id><published>2008-02-29T20:50:00.002+01:00</published><updated>2008-02-29T20:52:58.032+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='services'/><title type='text'>Autostarting services</title><content type='html'>Some privileged services start automatically in Android while the lowly applications I have written so far had to be started manually. I was not satisfied with the situation so I went after the Android bootup sequence.&lt;br /&gt;&lt;br /&gt;The Linux layer under the JVM boots up normally, using the init script but I was not interested in that. I found that the Java services are all started by the System server (android.server.SystemServer) which does launch each system server in a hardwired way.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt; ...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; Log.i("SystemServer", "Starting Alarm Manager.");&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; sm.addService("alarm", new AlarmManagerService(context));&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; Log.i("SystemServer", "Starting Window Manager.");&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; wm = WindowManagerService.main(context, power);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; sm.addService("window", wm);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; ...&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There is no way getting our service autostarted this way but fortunately there is the android.intent.action.BOOT_COMPLETED intent which is broadcasted when the boot completes. One needs android.permission.RECEIVE_BOOT_COMPLETED permission to receive this intent.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/autostart.zip"&gt; Download the example program from here.&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;The example program has two parts: an intent receiver catching the BOOT_COMPLETED intent and a primitive service that counts down from 10 in every 3 seconds and logs the counter value. When it counts down, the service dies completely, this is necessary as there is no simple way to uninstall packages in the SDK so this service is not allowed to cause trouble later. Please, note the use of Handler for the timed countdown: you can't stay for too long in onStart() otherwise the application manager will shoot down the service.&lt;br /&gt;&lt;br /&gt;Check the boot log below and you will find how the service is started. Each ... represents deleted log entries because meanwhile the boot process is happening and other services write the log.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;D/BootCompletedIntentReceiver(  576): android.intent.action.BOOT_COMPLETED&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/BootCompletedIntentReceiver(  576): BackgroundService started&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/ActivityThread(  576): Creating service aexp.persistentapp.BackgroundService&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/BackgroundService(  576): onStart&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;I/ActivityManager(  510): Stopping service: {aexp.persistentapp/aexp.persistentapp.BackgroundService}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; D/ActivityThread(  576): Stopping service aexp.persistentapp.BackgroundService@400bcb18&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; D/BackgroundService(  576): onDestroy&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/BackgroundService(  576): Counter: 10&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/BackgroundService(  576): Counter: 9&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/BackgroundService(  576): Counter: 8&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/BackgroundService(  576): Counter: 7&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt; ....&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/BackgroundService(  576): Counter: 6&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;D/BackgroundService(  576): Counter: 5&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-3504749552539899526?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/3504749552539899526/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=3504749552539899526' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3504749552539899526'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/3504749552539899526'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/02/autostarting-services.html' title='Autostarting services'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7084267017620473631</id><published>2008-02-22T00:13:00.005+01:00</published><updated>2010-10-22T21:47:21.156+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='philosophy'/><title type='text'>Android is the most modern application platform</title><content type='html'>Exceptionally, in this blog entry I intend to talk about philosophy instead of hard software code. There are so many mobile application platforms and some of them like Symbian is pretty widespread. So why the excitement about one more platform which is in addition Java-based - there are a number of Java-based mobile platform starting with J2ME, OSGi R4, Limo and so on. Everybody has one's own answer and I think the &lt;a href="http://code.google.com/android/adc.html"&gt;Android Developer Challenge&lt;/a&gt; is a great factor. However, one paper I wrote about 2 years ago (shameless self-advertisement ...) comes to my mind constantly when I work with Android. In that paper I analyzed mobile middleware patterns (&lt;a href="http://pallergabor.uw.hu/common/icsr9.pdf"&gt;get the presentation for free&lt;/a&gt;, the &lt;a href="http://www.springerlink.com/content/l3210774w7752127/?p=d8a8b89a32ce41eba4ab52b900a9cdb4&amp;amp;pi=8"&gt;paper unfortunately costs money&lt;/a&gt;. Write an &lt;a href="mailto:gaborpaller@gmail.com"&gt;e-mail to me&lt;/a&gt; and I send you a copy). There are 4 major patterns in mobile middleware (quote from the introduction of the paper).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-style: italic;"&gt;Context-awareness &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Traditional middleware is strictly layered meaning that it shields the applications from events concerning the lower level of the stack. For example if a remote invocation cannot be sent, the middleware layer may retry and eventually send a general error to the service user. Context-awareness means that there is no such shielding and the application is aware of the environment situation. Context changes are inherently asynchronous and are often delivered in the form of events. It is important to note that only the application can decide what context events are important and how to handle them. For example when a business application notices that its cheap and fast proximity&lt;/span&gt;&lt;span style="font-style: italic;"&gt; connection is no longer available, the application can revert using slower and more expensive cellular connection. The same option may not be available for a game which is not allowed to use more costly bearer and in case of disconnection, an error should be sent&lt;/span&gt;&lt;span style="font-style: italic;"&gt; to the end user or the application may switch to standalone mode.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-style: italic;"&gt;Reflection &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Reflection generally means that the program is able to make computations on its own structure during its execution (retrieve the current structure, evaluate the structure against environmental constraints then update the structure if necessary). Reflection is a crucial technique in mobile computing, especially, if the application is expected to be context-aware. Even moderate number of context states yield large number of context state combinations. If the application and the middleware are built in monolithic fashion, the application and middleware code must be prepared for all possible context state combinations which quickly becomes intractable. Also, the memory footprint of the monolithic middleware increases with each context state combination handled. In order to keep the footprint&lt;/span&gt;&lt;span style="font-style: italic;"&gt; minimal and the design of the system clear, the middleware needs to be decomposed into a collection of smaller components. The application chooses just the components it needs and composes the middleware that serves the application the best. In case of context&lt;/span&gt;&lt;span style="font-style: italic;"&gt; changes, the application evaluates the context transition and possibly changes the instantiated components and/or their configuration.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-style: italic;"&gt;Off-line access &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Disconnection is an inherent property of mobile computing. The reason for disconnection can be physical (no coverage) or social (mobile access is too expensive or not acceptable in the given situation). In order to provide acceptable user experience, operation in disconnected mode must be available. The key technology to achieve disconnected operation is the relocation of relevant data and code to the mobile device. Data relocation can be achieved by pretty established data synchronization techniques.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-style: italic;"&gt;Asynchronous communication &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Networked computing is dominated by solutions following Remote Procedure Call (RPC) semantics. RPC mimics the procedure call on a single processor, the calling procedure is suspended for the duration of the call and execution continues in the called procedure. Therefore RPC is inherently synchronous. Mobile transport networks are characterized by long and variable delays and frequent transmission errors. In this environment communication must be asynchronous ( event-based or messagebased terms are also used for the same concept). This affects the communication semantics the middleware uses. Instead of procedure call semantics, communicationrelated events are delivered to the application.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The paper goes on and designs such a framework for OSGi but that's history. So you can imagine my surprise when I started to work with Android and saw the intent and the related service framework that realizes context-awareness and reflection. The decomposition of the Android application into intent handler activities implement one key element of the reflective vision - standalone components that can be reconnected as the environmental situation demands it - and the intent delivery framework is perfectly suitable for context-aware computing. The intent matching  logic in  the current Android implementation is pretty simple (exact match of action and categories) but it takes not much imagination to extend it with more sophisticated matching logic if the target platform's capabilities (e.g. power consumption, processor speed) allow it. &lt;a href="http://mylifewithandroid.blogspot.com/2007/12/activity-invocation-performance.html"&gt;Intent delivery is slow&lt;/a&gt; but service binding based on intent matching - so that the service is selected by an intent and then the selected service is called repeatedly - &lt;a href="http://mylifewithandroid.blogspot.com/2008/01/service-invocation-performance.html"&gt;is sufficiently fast&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In addition, off-line operation - &lt;a href="http://mylifewithandroid.blogspot.com/2008/02/synchronization-in-android.html"&gt;synchronizable content providers&lt;/a&gt; - are part of the platform. The only key element Android misses is a comprehensive asynchronous communication framework like queues. &lt;a href="http://blogs.zdnet.com/Burnette/?p=533"&gt;The XMPP service that has just been removed can be such a service&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Android is not just another Java-based mobile platform but actually the only platform that adopts the results of the mobile middleware research therefore it is interesting in itself, without the Android Developer Challenge and its prizes. Its fate depends mostly on business factors but it is still important to note that the Android platform's architecture is several steps ahead of the competing platforms.&lt;br /&gt;&lt;br /&gt;Now back to coding. :-)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update:&lt;/span&gt; Mr. Fan found this post so interesting that he translated it to Chinese. &lt;a href="http://www.yeeyan.com/articles/view/71458/65484"&gt;Here is the Chinese translation&lt;/a&gt; (I couldn't check it :-))&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7084267017620473631?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7084267017620473631/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7084267017620473631' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7084267017620473631'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7084267017620473631'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/02/android-is-most-modern-application.html' title='Android is the most modern application platform'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-6173975054873755733</id><published>2008-02-20T22:11:00.004+01:00</published><updated>2008-02-20T22:21:54.358+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='location'/><title type='text'>Location providers - climb the Triglav with Android</title><content type='html'>Android SDK as it is clearly encourages developers to deal with location-aware applications. Both the location provider and the map part of the system is sufficiently documented and is functional which is pretty much in contrast with e.g. the &lt;a href="http://mylifewithandroid.blogspot.com/2008/02/synchronization-in-android.html"&gt;synchronization&lt;/a&gt; and &lt;a href="http://mylifewithandroid.blogspot.com/2008/01/hacking-into-androids-bluetooth-support.html"&gt;bluetooth&lt;/a&gt; parts. Good tutorials have been written about how to use the Android location framework so I wanted to try something else. I wanted to see, how to introduce one's own location data into the system.&lt;br /&gt;&lt;br /&gt;Android SDK comes with one built-in location provider called "gps". This location provider moves along a predetermined route - obviously around Google premises. I was curious whether it is possible to introduce one's own location provider into the system therefore I took apart android.server.LocationProviderService. I found that it is relatively easy to introduce one's own location track which is then presented as new location provider to Android applications. Creating a new location provider software module turned out to be more complicated and eventually I did not try that out. LocationProviderService is able to handle location providers with their own Java logic but unfortunately the classes of these providers are loaded by the own classloader of LocationProviderService. This means that it is not possible to deploy the location provider software module as a package (apk), one has to fiddle with the software base of the Android system.&lt;br /&gt;&lt;br /&gt;I did not do it because, to my pleasant surprise, Android's own location provider turned to be a pretty versatile simulator itself. Deploying own location providers based on track data really works like a charm. The LocationProviderService loads providers from under the /data/misc/location directory (device-based path). Each subdirectory under this directory is a location provider and the name of the location provider equals to the name of the subdirectory. The files in the subdirectory define the provider. As an example, you can go to the tools subdirectory of the example program package and run pull_gps.bat (emulator needs to be running when you do this). This batch file will fetch the three files associated with the "gps" provider as gps_location, gps_nmea and gps_properties (their original names on the device are location, nmea and properties, respectively, under the subdirectory called gps). The properties file describes the properties of the location provider in an easy to understand textual format. The location file is the last known location and the nmea is the track info in NMEA format. Well, I am not familiar with location format and I don't know what NMEA format is. There are two other possibilities: instead of "nmea" file, you can have "kml" or "track". In case of "kml", the track data is in KML format while "track" is a very simple textual format. I went for the latter and didn't try the former one - erm, I left that execise to the interested reader. ;-)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/locprovider.zip"&gt; You can download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The "track" format is simple. It looks like this:&lt;br /&gt;&lt;time&gt;&lt;longitude&gt;&lt;latitude&gt;&lt;altitude&gt;&lt;bearing&gt;&lt;speed&gt;&lt;br /&gt;&lt;/speed&gt;&lt;/bearing&gt;&lt;/altitude&gt;&lt;/latitude&gt;&lt;/longitude&gt;&lt;/time&gt;time longitude latitude altitude [bearing speed]&lt;br /&gt;&lt;time&gt;&lt;longitude&gt;&lt;latitude&gt;&lt;altitude&gt;&lt;bearing&gt;&lt;speed&gt;&lt;time&gt;&lt;longitude&gt;&lt;latitude&gt;&lt;altitude&gt;&lt;bearing&gt;&lt;speed&gt;&lt;br /&gt;where &lt;time&gt; is in milliseconds and is zero-based (first location has 0 timestamp) and bearing and speed can be omitted. LocationProviderService will emit location update intent broadcasts according to the timestamps in the file. Now the only thing remained to solve was how to get those track files. I don't have GPS receiver so I looked around on the Internet and I found a &lt;a href="http://www.hribi.net/gps.asp?lng=1&amp;amp;sledid=32"&gt;Slovenian mounteneering and hiking&lt;/a&gt; website (for those not from Central Europe, &lt;a href="http://en.wikipedia.org/wiki/Slovenia"&gt;Slovenia&lt;/a&gt; is a lovely country just between Austria and the Adrian seashore). This has hiking path section with GPS tracks and they have even two tours to the magnificient Triglav peak. I have good memories of that peak because we spent once one week beside the &lt;a href="http://travel.mongabay.com/slovenia/lake_bohinj.html"&gt;Bohinj glacier lake&lt;/a&gt; and Triglav was visible from some of the peaks around the lake. We did not climb it because there was our baby boy with us but I was told that if you take that peak seriously (it is nearly 3000 meters high) then it is accessible by almost anyone. Just don't go in slippers, take an entire day and you will be able to climb it.&lt;br /&gt;&lt;br /&gt;&lt;/time&gt;&lt;/speed&gt;&lt;/bearing&gt;&lt;/altitude&gt;&lt;/latitude&gt;&lt;/longitude&gt;&lt;/time&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.lifetrek-slovenia.com/en/pic/product/triglav-bobo.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px;" src="http://www.lifetrek-slovenia.com/en/pic/product/triglav-bobo.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;time&gt;&lt;longitude&gt;&lt;latitude&gt;&lt;altitude&gt;&lt;bearing&gt;&lt;speed&gt;&lt;time&gt;&lt;br /&gt;The tools subdirectory contains the ozitotrack tool that I created for standard J2SE (so it is not an Android application :-)). The hiking track I downloaded is located in the triglavMT.plt  file. Run the tool like the following:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;java ozitotrack -s20 triglavMT.plt triglav&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This will create triglav_track and triglav_location files. The -s switch instructs the tool to divide time interval among location fixes by 20 - the original track took 3 hours to complete, this will shorten it to just over 8 minutes. For the third file, triglav_properties, I took the gps_properties file (pulled from the emulator with the pull_gps script) and edited it. Now we have the three files needed for the location provider, let's upload it into the emulator.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;push_provider.bat triglav&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Again, you need to have the emulator running when you execute this command. Then unfortunately you have to restart the emulator - LocationProviderService loads the provider data at startup. Now you are ready to go. Upload the LocationProvider.apk package into the emulator (see the &lt;a href="http://mylifewithandroid.blogspot.com/2007/12/my-first-steps-with-command-line.html"&gt;Start here&lt;/a&gt; on the right pane to learn, how to do it) and start it.&lt;br /&gt;&lt;br /&gt;&lt;/time&gt;&lt;/speed&gt;&lt;/bearing&gt;&lt;/altitude&gt;&lt;/latitude&gt;&lt;/longitude&gt;&lt;/time&gt;&lt;/speed&gt;&lt;/bearing&gt;&lt;/altitude&gt;&lt;/latitude&gt;&lt;/longitude&gt;&lt;/time&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_K2nCeh0MHDY/R7yZJHywgmI/AAAAAAAAAD8/ey8-aldn7E4/s1600-h/triglavprov.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_K2nCeh0MHDY/R7yZJHywgmI/AAAAAAAAAD8/ey8-aldn7E4/s320/triglavprov.JPG" alt="" id="BLOGGER_PHOTO_ID_5169174854304301666" border="0" /&gt;&lt;/a&gt;&lt;time&gt;&lt;longitude&gt;&lt;latitude&gt;&lt;altitude&gt;&lt;bearing&gt;&lt;speed&gt;&lt;time&gt;&lt;longitude&gt;&lt;latitude&gt;&lt;altitude&gt;&lt;bearing&gt;&lt;speed&gt;&lt;time&gt;&lt;br /&gt;We have another provider called "triglav" beside the original "gps" provider. Select that provider and launch the "Display location" menu item. You will see us climbing the Triglav peak in 8 minutes (the altitude rises steadily to almost 3000 meters). When we reach the peak, we go back on our track to the origin and so on, this is the property of LocationProviderService.&lt;br /&gt;&lt;br /&gt;Don't forget to look around on the peak, the panorama is said to be magnificient.&lt;/time&gt;&lt;/speed&gt;&lt;/bearing&gt;&lt;/altitude&gt;&lt;/latitude&gt;&lt;/longitude&gt;&lt;/time&gt;&lt;/speed&gt;&lt;/bearing&gt;&lt;/altitude&gt;&lt;/latitude&gt;&lt;/longitude&gt;&lt;/time&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-6173975054873755733?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/6173975054873755733/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=6173975054873755733' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6173975054873755733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/6173975054873755733'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/02/location-providers-climb-triglav-with.html' title='Location providers - climb the Triglav with Android'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_K2nCeh0MHDY/R7yZJHywgmI/AAAAAAAAAD8/ey8-aldn7E4/s72-c/triglavprov.JPG' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-2902381660026902473</id><published>2008-02-16T00:26:00.007+01:00</published><updated>2008-02-16T00:55:34.662+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sdk'/><title type='text'>The rocky road to m5</title><content type='html'>Even though I slept over the release of m5-rc14 SDK release because of my hurdles with the synchronization framework, I lost no time test driving it. I ported the UI part of the sync example program to M5 which contains three activities and a content provider. The port was much more time consuming than I expected.&lt;br /&gt;&lt;br /&gt;I knew in advance &lt;a href="http://code.google.com/android/migrating/m3-to-m5/m5-api-changes.html"&gt;from the release notes&lt;/a&gt; that I will have troubles with the content provider because android.net.ContentURI has gone and was replaced by android.net.Uri. What I did not expect, however, is that because of minor schema changes, the layout and manifest XML files also require quite an amount of handwork. What was previously&lt;br /&gt;&lt;br /&gt;[ListView id="@+id/android:list"&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;now has to become&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[ListView android:id="@+id/android:list"&lt;br /&gt;&lt;br /&gt;(XML mangling because of the blog engine limitations)&lt;br /&gt;This required 7 changes in the layout files of the rather primitive application. The Android manifest was also changed for good. Gone are the class and value attributes and now everything is a name.&lt;br /&gt;&lt;br /&gt;Before:&lt;br /&gt; [activity class=".SyncExample2" android:label="SyncExample2"&lt;br /&gt;After:&lt;br /&gt;[activity android:name="SyncExample2" android:label="SyncExample2"]&lt;br /&gt;Overall, 6 changes in the 21 lines of the Android manifest file.&lt;br /&gt;&lt;br /&gt;Updating the content provider was really painful. The replacement of android.net.ContentURI with android.net.Uri and other class changes required 20 changes in the very simple content provider with 229 lines. I marked all changes as comments so you can review them in the code.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/newsdk.zip"&gt;You can download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And when I thought that everything was done, it turned out that the NotificationManager (that I used fortunately at two places only) has also changed. Transient notifications are widgets nowadays and are created like this:&lt;br /&gt;&lt;br /&gt;Toast.makeText(this, "message", Toast.LENGTH_SHORT).show();&lt;br /&gt;&lt;br /&gt;In order not to make it boring, the documentation remarks merrily that android.widget.Toast may not be the final name of the thing. And I did not even mention the changes of the undocumented API that the content provider list views use - well, one uses undocumented API at one's own risk.&lt;br /&gt;&lt;br /&gt;Overall, seemingly minor changes of API "beatification" cause significant work even on smaller programs. As a bonus, you may marvel at the new UI. Installed applications went to the main screen.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_K2nCeh0MHDY/R7YlpHywgjI/AAAAAAAAADk/JyLJaN1MysY/s1600-h/newsdk.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_K2nCeh0MHDY/R7YlpHywgjI/AAAAAAAAADk/JyLJaN1MysY/s320/newsdk.JPG" alt="" id="BLOGGER_PHOTO_ID_5167359010851029554" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;And look at this horizontal menu!&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_K2nCeh0MHDY/R7YmDXywglI/AAAAAAAAAD0/tOVtbW2SX-c/s1600-h/newmenubar.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_K2nCeh0MHDY/R7YmDXywglI/AAAAAAAAAD0/tOVtbW2SX-c/s320/newmenubar.JPG" alt="" id="BLOGGER_PHOTO_ID_5167359461822595666" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-2902381660026902473?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/2902381660026902473/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=2902381660026902473' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2902381660026902473'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/2902381660026902473'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/02/rocky-road-to-m5.html' title='The rocky road to m5'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_K2nCeh0MHDY/R7YlpHywgjI/AAAAAAAAADk/JyLJaN1MysY/s72-c/newsdk.JPG' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-7501716700359734189</id><published>2008-02-14T22:38:00.003+01:00</published><updated>2008-02-14T22:44:18.947+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='synchronization'/><title type='text'>Synchronization in Android</title><content type='html'>&lt;span style="font-style: italic;"&gt;Note: this example program was written before the &lt;a href="http://android-developers.blogspot.com/2008/02/android-sdk-m5-rc14-now-available.html"&gt;new m5-rc14 release of the SDK&lt;/a&gt; was published and therefore it refers to m3-rc37a SDK release. I intend to investigate the sync issue in the new release too.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The first thing I noted about Android content providers that they support synchronization. This fact has huge implications and shows, how well the Android mobile platform is designed. Synchronization is a key enabler in mobile applications but is often "engineered in" later into the platforms. This approach is about as realistic as "adding security later" to a non security-aware platform.&lt;br /&gt;&lt;br /&gt;It is not surprising for an Android-hardened developer that synchronization does not work in the m3-rc37a release and the example program presented here is not able to accomplish any real synchronization. This statement would discourage lesser programmers but not the ones dealing with the Android platform! Decyphering the Android synchronization framework is an interesting exercise in itself even though that framework may change in the future. Key elements are already there and there are some interesting findings regarding synchronization as a service.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/syncexample.zip"&gt; You can download the example program from here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_K2nCeh0MHDY/R7S1SXywgiI/AAAAAAAAADc/r9azp8wiLbw/s1600-h/sync.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_K2nCeh0MHDY/R7S1SXywgiI/AAAAAAAAADc/r9azp8wiLbw/s320/sync.JPG" alt="" id="BLOGGER_PHOTO_ID_5166953999729984034" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The naive observation that led me to deal with the Android synchronization framework is that the log clearly indicates a running synchronization service. The synchronization service (actually called android.content.ContentService) listens to data connection/disconnection events and logs these state changes. In order to actually activate the sync part of the service (other part of the service deals with the XMPP connection), the following steps should be taken.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The sync part of the service must be enabled by manipulating the sync_enabled property in the settings table. This is done by calling android.provider.Settings.Gservices.putString( cr,"sync_enabled","true" ). You noticed it well: the G in Gservices refers to Google. Clearly, Android sync framework programmers considered synchronization a Google service. In order for this call to succeed, the application must have android.permission.WRITE_SETTINGS permission (check the manifest of the example program).&lt;/li&gt;&lt;li&gt;Account information must be provided. Interestingly again, the service handling this logic is called GoogleLoginService. ContentService obtains account information from GoogleLoginService and if there are no accounts specified, synchronization stops. This is a bit frightening but in m3-rc37a release the account information is stored in a local SQLite table. Do the following:&lt;br /&gt;   &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;adb shell&lt;/span&gt;     &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;cd data/data/com.google.android.providers.googleapps/databases&lt;/span&gt;      &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;sqlite3 accounts.db&lt;/span&gt;      &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;INSERT INTO "accounts" VALUES(1,'user','password',1);&lt;/span&gt;      &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;.quit&lt;/span&gt;&lt;/small&gt;&lt;/li&gt;&lt;li&gt;The sync engine stores its settings, history and stats in a content provider under the content://sync/ tree. Strangely, this content provider is not implemented at all. The example program provides a naive implementation which is good enough to make the sync service work.&lt;br /&gt; &lt;/li&gt;&lt;/ul&gt; The sync service is ready for work now. In Android, only synchronizable data providers can be synchronized and this makes this whole exercise more valuable than just pure hacking. Content providers must jump some fences to become synchronizable. Pretty few built-in content providers of the SDK are synchronizable (you can examine these lists using the example program's "List sync providers" and "List content provider" menu items).&lt;br /&gt;&lt;br /&gt;The steps necessary to make a content provider synchronizable are the following:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The content provider must be marked as synchronizable in the manifest (again, the XML fragment is mangled because of the limitations of the blog engine):&lt;br /&gt;  &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;[provider class=".SimpleStringDataProvider"&lt;/span&gt;      &lt;span style="font-family:Courier New,Courier,monospace;"&gt;              android:authorities="aexp.syncexample.SimpleString"&lt;/span&gt;      &lt;span style="font-family:Courier New,Courier,monospace;"&gt;             &lt;br /&gt;android:syncable="true"/]&lt;/span&gt;&lt;/small&gt;&lt;/li&gt;&lt;li&gt;The content provider must be able to provide a temporary version of itself. The method signature is the following:&lt;br /&gt; &lt;br /&gt;   &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;public ContentProvider getTemporaryInstance()&lt;/span&gt;&lt;/small&gt;&lt;br /&gt; &lt;br /&gt;The synchronization engine manipulates the temporary instance of the content provider and changes done on the temporary content provider are merged by another method the content provider must implement:&lt;br /&gt; &lt;br /&gt;   &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;public MergeResult merge(SyncContext context, ContentProvider diffs, boolean readOnly)&lt;/span&gt;&lt;/small&gt;&lt;br /&gt; &lt;br /&gt;This latter method gets the temporary content provider instance after one synchronization step is finished on it and merges it with the main content provider. Meanwhile, collisions may be detected (data changed incompatibly on both sides since the last synchronization) and as a result, synchronization may even be interrupted by the user.&lt;/li&gt;&lt;li&gt;And last, but not least, the synchronizable content provider must be able to return the SyncAdapter that is able to synchronize the provider.&lt;br /&gt; &lt;br /&gt;   &lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;public SyncAdapter getSyncAdapter(Context context)&lt;/span&gt;&lt;/small&gt;&lt;br /&gt; &lt;br /&gt;The SyncAdapter instance abstracts the entire synchronization engine. The example program provides a dummy implementation.&lt;/li&gt;&lt;/ul&gt; This is not enough. The database structure of the content provider must be designed in such a way that the SyncAdapter supported by the content provider is able to manipulate it. There are different synchronization strategies and android.provider.SyncColumns provides a number of columns that may be useful implementing them. The content provider in the example program supports the most common one, the timestamp-based synchronization. In this method, the "version" of a data item is expressed by a high-resolution timestamp. Each item timestamped after the timestamp of the last synchronization is candidate for sync processing.&lt;br /&gt;&lt;br /&gt;The interaction between the sync engine and the sync adapter can be followed on the the log below:&lt;br /&gt;&lt;br /&gt;&lt;small&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/SyncExample(  614): syncDB&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;D/SyncExample(  614): After syncDB&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/Sync    (  467): running sync operation type: 1 url: content://aexp.syncexample.SimpleString&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;D/ActivityThread(  467): Installing external provider aexp.syncexample.SimpleString: aexp.syncexample.SimpleStringDataProvider&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/ActivityThread(  467): Installing external provider sync: aexp.syncexample.SyncSettingsProvider&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;I/Sync    (  467): Starting sync for aexp.syncexample.SimpleString&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/SyncAdapterStub(  614): syncStarting&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;D/SyncAdapterStub(  614): isReadOnly&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/SyncAdapterStub(  614): getServerDiffs, contentProvider: aexp.syncexample.SimpleStringDataProvider@40014470&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/SIMPLESTRINGDATAPROVIDER(  614): merge, diffs: aexp.syncexample.SimpleStringDataProvider@40014470&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/SIMPLESTRINGDATAPROVIDER(  614): merge, tempContentProvider: android.content.ContentProvider$Transport@400144a0&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/SyncAdapterStub(  614): writeSyncData: aexp.syncexample.SimpleString&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;D/SIMPLESTRINGDATAPROVIDER(  614): merge, diffs: null&lt;/span&gt;  &lt;span style="font-family:Courier New,Courier,monospace;"&gt;&lt;br /&gt;D/SyncAdapterStub(  614): syncEnding&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;I/Sync    (  467): Ending sync for aexp.syncexample.SimpleString&lt;/span&gt; &lt;br /&gt;&lt;span style="font-family:Courier New,Courier,monospace;"&gt;D/Sync    (  467): finished sync operation type: 1 url: content://aexp.syncexample.SimpleString&lt;/span&gt;&lt;/small&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;In the first step, getServerDiffs is called on the SyncAdapter, using the temporary provider as input. In case of the timestamp-based adapter, the client-server communication must establish the timestamp at which this client and server synchronized last, send client changes after that timestamp and obtain changes from the server into the temporary provider.&lt;/li&gt;&lt;li&gt;In the second step, the provider's merge method is called with the temporary provider as parameter. At this point the content provider must reconcile the client-side database with the temporary database. During the merging, collisions may be detected when the client and the server both changed a data item since the last synchronization in non-compatible way. Eventually collisions may need to be escalated to the user who may even interrupt the synchronization process.&lt;/li&gt;&lt;li&gt;After this step, we have the reconciled database on the client side. The client now needs to notify the server about collision resolution and sends the client diffs one more time using the SyncAdapter's sendClientDiffs method. This step is not present in the log because our primitive synchronization example did not produce collisions.&lt;br /&gt; &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;After all this suffering, I guess, you would like to see a real synchronization session. I have to disappoint you: beside some scattered SyncML DS-related classes, I was not able to find any real sync adapter. Implementing a real synchronization engine takes a lot of time but I happen to have a SyncML-based engine implemented and I intend to put it under Android's sync framework. So stay tuned. :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8214401912480503366-7501716700359734189?l=mylifewithandroid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://mylifewithandroid.blogspot.com/feeds/7501716700359734189/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8214401912480503366&amp;postID=7501716700359734189' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7501716700359734189'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8214401912480503366/posts/default/7501716700359734189'/><link rel='alternate' type='text/html' href='http://mylifewithandroid.blogspot.com/2008/02/synchronization-in-android.html' title='Synchronization in Android'/><author><name>Gabor Paller</name><uri>http://www.blogger.com/profile/14307475522972458932</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_K2nCeh0MHDY/R7S1SXywgiI/AAAAAAAAADc/r9azp8wiLbw/s72-c/sync.JPG' height='72' width='72'/><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8214401912480503366.post-8055699908090004902</id><published>2008-02-03T10:47:00.001+01:00</published><updated>2009-06-25T22:52:19.544+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='services'/><title type='text'>Double life of a service</title><content type='html'>Earlier (with much less experience in Android services) &lt;a href="http://mylifewithandroid.blogspot.com/2008/01/invoking-services.html"&gt;I wrote about the two kinds of services behind the android.app.Service abstraction&lt;/a&gt;: a long-running service whose lifecycle is independent of the activity that started it (Context.startService) and a service bound by other services and activities by its AIDL interface (Context.bindService). The lifecycle of this second type of service depends on the lifecycles of the entities that bind it, if the service has no more bound client, the application manager is free to destroy it. Meanwhile, the first type of service exists until the clients that started the service stop it or until the service stops itself. In extremely low resource conditions the application manager may shut down the first type of service too but this is rare.&lt;br /&gt;&lt;br /&gt;I don't know how about you but the difference between the two types always seemed to be unnatural for me. For starter, I can pretty much imagine a number of useful and practical services that are long-running and clients want to communicate with them at the same time. For example a P2P module would be such a service; it would run in the background doing discovery-related or other long-running tasks and occassionally client applications would connect to it to accomplish P2P operations.&lt;br /&gt;&lt;br /&gt;It turns out that this difference is artificial. Android services can exhibit both properties: they can be long-running and can be bound and unbound with AIDL interface operations. This is possible because there is &lt;span style="font-weight: bold;"&gt;only one service instance&lt;/span&gt;, independently of the method the service was started with. I discovered it recently &lt;a href="http://groups.google.com/group/android-developers/browse_thread/thread/b07e5b89a50704d6#71543fa42f224efc"&gt;and as there was a topic about it in the Android groups&lt;/a&gt;, I decided to quickly put together an example program about it.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/dualservice.zip"&gt; You can download the example program from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pallergabor.uw.hu/androidblog/dualservice1.5.zip"&gt;You can download the example program updated for SDK 1.5 from here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Our example is simple. There is a service that holds a counter and can return that counter by means of an AIDL interface. The service need to be bound by Context.bindService() to obtain client-side interface stub. This is not very useful, however, because the counter does not change, it needs to count forward. In order to do that, we start the same service with Context.startService(). It turns out that the onStart() that follows is invoked on the same service instance. Hence we have a service that conforms to both types: it is long-running and serves interface invocations at the same time.&lt;br /&gt;&lt;br /&gt;Simple? I thought so. I implemented a version of the example program and it worked flawlessy except that the service could not be stopped. It turns out that a dual service like that can be stopped with Context.stopService() call only if both lifecycles models allow it. This means that stopService() has no effect on a bound service!&lt;br /&gt;&lt;br /&gt;Let's take an example. The client activity allows binding/unbinding, starting/stopping the service manually.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_K2nCeh0MHDY/R6WQeLDLr0I/AAAAAAAAADU/azJy2Us8drw/s1600-h/dualservice.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_K2nCeh0MHDY/R6WQeLDLr0I/AAAAAAAAADU/azJy2Us8drw/s320/dualservice.JPG" alt="" id="BLOGGER_PHOTO_ID_5162691395886755650" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now let's see what happens if the service is bound, unbound then started and stopped.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICECLIENT(  568): bindService()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/ActivityThread(  568): Creating service aexp.dualservice.DualService&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICE(  568): onCreate&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICECLIENT(  568): onServiceConnected&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;I/ActivityManager(  465): Stopping service: {aexp.dualservice/aexp.dualservice.DualService}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICECLIENT(  568): unbindService()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/ActivityThread(  568): Stopping service aexp.dualservice.DualService@40044928&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICE(  568): onDestroy&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICECLIENT(  568): startService()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/ActivityThread(  568): Creating service aexp.dualservice.DualService&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICE(  568): onCreate&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICE(  568): onStart&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;I/ActivityManager(  465): Stopping service: {aexp.dualservice/aexp.dualservice.DualService}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICECLIENT(  568): stopService()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/ActivityThread(  568): Stopping service aexp.dualservice.DualService@40048cd0&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;D/DUALSERVICE(  568): onDestroy&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;The service lifecycles work nicely: when the serv
