<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;Ck8BRXo7fip7ImA9WhFTGE4.&quot;"><id>tag:blogger.com,1999:blog-12998912</id><updated>2013-06-10T10:20:54.406+08:00</updated><category term="galaxy" /><category term="decode" /><category term="cli" /><category term="actionbar" /><category term="lru" /><category term="news" /><category term="dreamerslab" /><category term="movies" /><category term="dogfan" /><category term="canucks" /><category term="parsing" /><category term="honeycomb" /><category term="httpurlconnection" /><category term="library" /><category term="presentation" /><category term="evernote" /><category term="actionmode" /><category term="fastboot" /><category term="disklrucache" /><category term="contentextraction" /><category term="httpresponsecache" /><category term="actionbarshelock" /><category term="actionbarsherlock" /><category term="performance" /><category term="vim" /><category term="tv" /><category term="macflickr" /><category term="contest" /><category term="googleio" /><category term="xml" /><category term="obfuscate" /><category term="floss" /><category term="imageloader" /><category term="marketenabler" /><category term="java" /><category term="gtug" /><category term="zencoding" /><category term="dream" /><category term="memory" /><category term="root" /><category term="io2011" /><category term="ui" /><category term="nested" /><category term="android" /><category term="compatibility" /><category term="view" /><category term="admob" /><category term="market" /><category term="speech" /><category term="ssl" /><category term="led" /><category term="LruCache" /><category term="proguard" /><category term="vpon" /><category term="plugins" /><category term="widget" /><category term="feedly" /><category term="google" /><category term="thunk" /><category term="jank" /><category term="ossf" /><category term="responsecache" /><category term="bootloader" /><category term="javascript" /><category term="asynchronous" /><category term="cache" /><category term="tablet" /><category term="jreadability" /><category term="libs-for-android" /><category term="perl" /><category term="courage" /><category term="usa" /><category term="launcher" /><category term="vimrc" /><category term="workspace" /><category term="opensource" /><category term="fulltext" /><category term="coscup2011" /><category term="layout" /><category term="coscup" /><category term="clearly" /><category term="image" /><category term="お父さん扇風機" /><category term="leaks" /><category term="iosched" /><category term="evernote-clearly" /><category term="hack" /><category term="tweetdeck" /><category term="decoding" /><category term="commandline" /><category term="urldecoding" /><category term="access modifier" /><category term="vi" /><category term="fragmentation" /><category term="programming" /><category term="content-extraction" /><category term="mat" /><category term="samsung" /><category term="urldecode" /><category term="ad" /><category term="regex" /><category term="jquery" /><category term="taiwan" /><category term="fivefilters" /><category term="twitter" /><category term="textview" /><category term="adapter" /><category term="unlock" /><category term="pattern" /><category term="smali" /><category term="actionmodestyle" /><category term="commandlinefu" /><category term="readability" /><category term="apktool" /><category term="vancouver" /><category term="reader" /><category term="viewpager" /><category term="filtering" /><title>Wu-Man's Blog</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.wu-man.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.wu-man.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>30</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/wuman" /><feedburner:info uri="wuman" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="license" type="text/html" href="http://creativecommons.org/licenses/by-sa/3.0/" /><logo>http://creativecommons.org/images/public/somerights20.gif</logo><feedburner:emailServiceId>wuman</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><entry gd:etag="W/&quot;AkECRn0-fip7ImA9WhNbEU4.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-2848517439448623734</id><published>2013-01-14T12:44:00.003+08:00</published><updated>2013-01-14T12:44:27.356+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-01-14T12:44:27.356+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="opensource" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>A Deep Dive into Open Source Android Development</title><content type="html">Over the past month I have given talks at &lt;a href="http://www.openfoundry.org/en/activities/details/359" target="_blank"&gt;three&lt;/a&gt; &lt;a href="http://registrano.com/events/a22ba4" target="_blank"&gt;different&lt;/a&gt;&amp;nbsp;&lt;a href="http://registrano.com/events/95b1cd" target="_blank"&gt;occasions&lt;/a&gt;, all centered around the same topic: open source Android development. &amp;nbsp;The speech is split up into two parts:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;An overview of a long list of popular open source libraries and tools available for Android application development;&lt;/li&gt;
&lt;li&gt;A walk through of the process of releasing your own open source library.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;br /&gt;
Android application development can be non-trivial if compatibility across platform versions is a requirement. &amp;nbsp;Without building on the shoulders of other giants, it is extremely difficult to develop an Android application with great quality. &amp;nbsp;The first half of the speech aims to introduce popular tools and libraries that can help facilitate the development of good Android applications.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Furthermore, it is always great to contribute back to the open source community by releasing your own library as open source projects. &amp;nbsp;The second half of the talk aims to help developers do exactly that. &amp;nbsp;It walks you through the entire process step-by-step and hopefully convinces you that releasing an open source library is both easy and worthwhile.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
I have made the slides available on both &lt;a href="http://www.slideshare.net/wuman/a-deep-dive-into-open-source-android-development" target="_blank"&gt;SlideShare&lt;/a&gt; and &lt;a href="https://speakerdeck.com/wuman/a-deep-dive-into-open-source-android-development" target="_blank"&gt;SpeakerDeck&lt;/a&gt;. &amp;nbsp;Feel free to also check out a recorded video of the presentation at &lt;a href="http://www.taipei-gtug.org/" target="_blank"&gt;Taipei GTUG&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;object width="320" height="266" class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="http://3.gvt0.com/vi/gwB8xkTckKc/0.jpg"&gt;&lt;param name="movie" value="http://www.youtube.com/v/gwB8xkTckKc&amp;fs=1&amp;source=uds" /&gt;&lt;param name="bgcolor" value="#FFFFFF" /&gt;&lt;param name="allowFullScreen" value="true" /&gt;&lt;embed width="320" height="266"  src="http://www.youtube.com/v/gwB8xkTckKc&amp;fs=1&amp;source=uds" type="application/x-shockwave-flash" allowfullscreen="true"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=sSJdQRFKYgQ:_bPZbrg1ca4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=sSJdQRFKYgQ:_bPZbrg1ca4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=sSJdQRFKYgQ:_bPZbrg1ca4:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=sSJdQRFKYgQ:_bPZbrg1ca4:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/sSJdQRFKYgQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/2848517439448623734/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=2848517439448623734" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/2848517439448623734?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/2848517439448623734?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/sSJdQRFKYgQ/a-deep-dive-into-open-source-android.html" title="A Deep Dive into Open Source Android Development" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2013/01/a-deep-dive-into-open-source-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEUNR306eip7ImA9WhNSFkg.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-1615674011033368786</id><published>2012-10-31T10:57:00.000+08:00</published><updated>2012-10-31T10:58:16.312+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-10-31T10:58:16.312+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="library" /><category scheme="http://www.blogger.com/atom/ns#" term="leaks" /><category scheme="http://www.blogger.com/atom/ns#" term="cache" /><category scheme="http://www.blogger.com/atom/ns#" term="ui" /><category scheme="http://www.blogger.com/atom/ns#" term="memory" /><category scheme="http://www.blogger.com/atom/ns#" term="performance" /><category scheme="http://www.blogger.com/atom/ns#" term="libs-for-android" /><category scheme="http://www.blogger.com/atom/ns#" term="reader" /><category scheme="http://www.blogger.com/atom/ns#" term="image" /><category scheme="http://www.blogger.com/atom/ns#" term="pattern" /><category scheme="http://www.blogger.com/atom/ns#" term="imageloader" /><category scheme="http://www.blogger.com/atom/ns#" term="asynchronous" /><category scheme="http://www.blogger.com/atom/ns#" term="google" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="jank" /><category scheme="http://www.blogger.com/atom/ns#" term="opensource" /><title>AndroidImageLoader: Loading Images Asynchronously on Android</title><content type="html">Almost every Android application has a need to bind remote images to a &lt;a href="http://developer.android.com/reference/android/view/View.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;View&lt;/span&gt;&lt;/a&gt; on screen. &amp;nbsp;A very common usage is to load a bunch of remote images, cache them for later reuse, and finally bind them to each view in a scrolling &lt;a href="http://developer.android.com/reference/android/widget/AdapterView.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;AdapterView&lt;/span&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Making this process efficient and memory-leak-free, however, is not exactly trivial. &amp;nbsp;There are at least these &lt;b&gt;performance&lt;/b&gt; considerations:&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;All the IO activities need to be done in a background thread to avoid blocking the main thread.&lt;/li&gt;
&lt;li&gt;Images that fail to be loaded (usually due to network disconnection or missing resource) should be remembered so that unnecessary retrying requests are not repeated.&lt;/li&gt;
&lt;li&gt;Loading requests should be executed in a LIFO manner to improve &lt;i&gt;perceived&lt;/i&gt; response time.&lt;/li&gt;
&lt;li&gt;Loaded images should be cached at least in memory so that future requests for the same image can be loaded faster.&lt;/li&gt;
&lt;li&gt;Asynchronously binding images to an &lt;span style="font-family: Courier New, Courier, monospace;"&gt;AdapterView&lt;/span&gt; can be tricky. &amp;nbsp;Calling &lt;a href="http://developer.android.com/reference/android/widget/BaseAdapter.html#notifyDataSetChanged()" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;BaseAdapter.notifyDataSetChanged()&lt;/span&gt;&lt;/a&gt; upon callback can be a very expensive operation.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
On the other hand, a major &lt;b&gt;memory&lt;/b&gt; consideration is to avoid memory leaks.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;Since image loading must be done in an asynchronous manner, views holding a strong reference to &lt;a href="http://developer.android.com/reference/android/content/Context.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;Context&lt;/span&gt;&lt;/a&gt; inevitably have to be kept and referenced by the image loader itself. &amp;nbsp;This can best be prevented by using a &lt;a href="http://developer.android.com/reference/java/lang/ref/WeakReference.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;WeakReference&lt;/span&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Loaded images are to be kept in cache, which can potentially cause a memory leak if &lt;a href="http://developer.android.com/reference/android/graphics/drawable/Drawable.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;Drawable&lt;/span&gt;s&lt;/a&gt; are cached rather than &lt;a href="http://developer.android.com/reference/android/graphics/Bitmap.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;Bitmap&lt;/span&gt;s&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
To address these issues, I have forked the &lt;a href="http://code.google.com/p/libs-for-android/wiki/ImageLoader" target="_blank"&gt;Image Loader&lt;/a&gt; component of the &lt;a href="http://code.google.com/p/libs-for-android/" target="_blank"&gt;libs-for-android&lt;/a&gt; project to create the &lt;b&gt;AndroidImageLoader&lt;/b&gt; library.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="https://raw.github.com/wuman/AndroidImageLoader/master/library/src/site/static/feature.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="https://raw.github.com/wuman/AndroidImageLoader/master/library/src/site/static/feature.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The AndroidImageLoader is an Android library that helps to load images asynchronously. Like its upstream libs-for-android project, it executes image requests in a thread pool and provides caching support. The following features of libs-for-android are kept:&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;Images are downloaded and saved to cache via a pool of background threads.&lt;/li&gt;
&lt;li&gt;Supports preloading of off-screen images into a memory cache.&lt;/li&gt;
&lt;li&gt;Supports prefetching of images into a disk cache.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://developer.android.com/reference/java/net/HttpURLConnection.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;HttpUrlConnection&lt;/span&gt;&lt;/a&gt; is used for loading images, which respects cache control.&lt;/li&gt;
&lt;li&gt;Custom &lt;a href="http://developer.android.com/reference/java/net/URLStreamHandlerFactory.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;URLStreamHandlerFactory&lt;/span&gt;&lt;/a&gt; is supported for creating connections to special &lt;span style="font-family: Courier New, Courier, monospace;"&gt;URL&lt;/span&gt;s such as &lt;span style="font-family: Courier New, Courier, monospace;"&gt;content://&lt;/span&gt; URIs.&lt;/li&gt;
&lt;li&gt;Supports Bitmap transformations by accepting a &lt;a href="http://developer.android.com/reference/java/net/ContentHandler.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;ContentHandler&lt;/span&gt;&lt;/a&gt; for loading images.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The AndroidImageLoader improves libs-for-android in the following ways:&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;In addition to memory cache, the library also supports second level disk caching. Caching support in AndroidImageLoader is now made to depend on the &lt;a href="http://wuman.github.com/TwoLevelLruCache/" target="_blank"&gt;TwoLevelLruCache&lt;/a&gt; library, which in turn depends on the more widely used &lt;a href="http://developer.android.com/reference/android/util/LruCache.html" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;LruCache&lt;/span&gt;&lt;/a&gt; and &lt;a href="https://github.com/JakeWharton/DiskLruCache" target="_blank"&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;DiskLruCache&lt;/span&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Image requests are now placed into a LIFO task queue, which makes more sense in most scrolling scenarios.&lt;/li&gt;
&lt;li&gt;The API for view binding is now in a separate &lt;span style="font-family: Courier New, Courier, monospace;"&gt;ViewBinder&lt;/span&gt; class. Applications can use the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;ImageViewBinder&lt;/span&gt; class to bind to &lt;span style="font-family: Courier New, Courier, monospace;"&gt;ImageView&lt;/span&gt;s or extend the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;AbstractViewBinder&lt;/span&gt; class for custom views. Also, &lt;span style="font-family: Courier New, Courier, monospace;"&gt;ImageView&lt;/span&gt;s within an &lt;span style="font-family: Courier New, Courier, monospace;"&gt;AdapterView&lt;/span&gt; no longer require a different kind of binding.&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family: Courier New, Courier, monospace;"&gt;OutOfMemoryError&lt;/span&gt;s are automatically detected and caught. When memory is running low, the cache size is automatically decreased to give back more memory to the system.&lt;/li&gt;
&lt;li&gt;Prefetching can now be supported out of the box via the &lt;span style="font-family: Courier New, Courier, monospace;"&gt;SinkContentHandler&lt;/span&gt; and the &lt;a href="https://github.com/candrews/HttpResponseCache" target="_blank"&gt;HttpResponseCache&lt;/a&gt; library.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The library is open source and &lt;a href="http://wuman.github.com/AndroidImageLoader/" target="_blank"&gt;hosted on GitHub&lt;/a&gt;. &amp;nbsp;It is well documented and has samples that illustrate the use of the exposed API. &amp;nbsp;Feel free to try it out.&lt;/div&gt;
&lt;/div&gt;
&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=bynfcvRMEpU:rrareJmRKWs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=bynfcvRMEpU:rrareJmRKWs:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=bynfcvRMEpU:rrareJmRKWs:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=bynfcvRMEpU:rrareJmRKWs:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/bynfcvRMEpU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/1615674011033368786/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=1615674011033368786" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/1615674011033368786?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/1615674011033368786?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/bynfcvRMEpU/androidimageloader-loading-images.html" title="AndroidImageLoader: Loading Images Asynchronously on Android" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>4</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/10/androidimageloader-loading-images.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE4HQXk_eyp7ImA9WhNTFkQ.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-1355994171943090970</id><published>2012-10-20T01:14:00.000+08:00</published><updated>2012-10-20T09:35:30.743+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-10-20T09:35:30.743+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="httpurlconnection" /><category scheme="http://www.blogger.com/atom/ns#" term="LruCache" /><category scheme="http://www.blogger.com/atom/ns#" term="httpresponsecache" /><category scheme="http://www.blogger.com/atom/ns#" term="cache" /><category scheme="http://www.blogger.com/atom/ns#" term="opensource" /><category scheme="http://www.blogger.com/atom/ns#" term="lru" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="responsecache" /><category scheme="http://www.blogger.com/atom/ns#" term="disklrucache" /><title>TwoLevelLruCache: Standing on the Shoulder of Two Giants</title><content type="html">Android application development is made easier by people who continue to create and share useful libraries.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
A few days ago I &lt;a href="https://plus.google.com/108284392618554783657/posts/PoFw9o4wtxE" target="_blank"&gt;discovered&lt;/a&gt; a port of the DiskLruCache from the Android ICS platform. &amp;nbsp;The class is a delegate backing store for the &lt;a href="http://developer.android.com/reference/android/net/http/HttpResponseCache.html" target="_blank"&gt;HttpResponseCache&lt;/a&gt;, which in turn supports caching for &lt;a href="http://developer.android.com/reference/java/net/HttpURLConnection.html" target="_blank"&gt;HttpUrlConnection&lt;/a&gt; responses. &amp;nbsp;The DiskLruCache was &lt;a href="https://plus.google.com/106557483623231970995/posts/Ein9QjNVzSL" target="_blank"&gt;released with ICS&lt;/a&gt; but was not accessible as a public API due to its many dependencies on other hidden parts of the platform. &amp;nbsp;&lt;a href="http://jakewharton.com/" target="_blank"&gt;Jake Wharton&lt;/a&gt;, the amazing guy who released a number of libraries to help &lt;s&gt;combat fragmentation&lt;/s&gt;&amp;nbsp;support backward compatibility on Android, ported the DiskLruCache class and made it a standalone library. &amp;nbsp;The &lt;a href="https://github.com/JakeWharton/DiskLruCache" target="_blank"&gt;standalone DiskLruCache library&lt;/a&gt;&amp;nbsp;has proven great for at least two things:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;The library has helped another &lt;a href="https://github.com/candrews/HttpResponseCache" target="_blank"&gt;great port of the HttpResponseCache&lt;/a&gt; to make use of advanced disk caching. &amp;nbsp;This means that apps targeting pre-ICS devices can now also enjoy the improved feature.&lt;/li&gt;
&lt;li&gt;What I am even more impressed by the library is that Jake has also ported its &lt;a href="https://github.com/JakeWharton/DiskLruCache/tree/master/src/test/java/com/jakewharton" target="_blank"&gt;rigorous unit testing&lt;/a&gt;. &amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Looking at these role models I can't help but want to reciprocate. &amp;nbsp;It is only right to contribute back since I myself have benefited so much from open source projects too. &amp;nbsp;With that in mind, I have isolated a part of my apps into a standalone library and am releasing it as an open source project on GitHub.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The &lt;b&gt;&lt;a href="http://wuman.github.com/TwoLevelLruCache/" target="_blank"&gt;TwoLevelLruCache&lt;/a&gt;&lt;/b&gt; is a two-level LRU cache composed of a smaller, first level &lt;a href="http://developer.android.com/reference/android/support/v4/util/LruCache.html" target="_blank"&gt;LruCache&lt;/a&gt; in memory and a larger, second level DiskLruCache. &amp;nbsp;It is designed to increase the capacity of the cache without also dramatically increasing the memory footprint of an app.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The project is at a very early stage. &amp;nbsp;I will continue to update it with unit tests, documentation, samples, and improvements and bug fixes. &amp;nbsp;&lt;a href="http://wuman.github.com/TwoLevelLruCache/" target="_blank"&gt;Try it out&lt;/a&gt; and send your feedback my way.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Happy coding. &amp;nbsp;:)&lt;/div&gt;
&lt;/div&gt;
&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=5nEBZuI2MGQ:YqOePgqnees:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=5nEBZuI2MGQ:YqOePgqnees:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=5nEBZuI2MGQ:YqOePgqnees:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=5nEBZuI2MGQ:YqOePgqnees:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/5nEBZuI2MGQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/1355994171943090970/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=1355994171943090970" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/1355994171943090970?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/1355994171943090970?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/5nEBZuI2MGQ/twolevellrucache-standing-on-shoulder.html" title="TwoLevelLruCache: Standing on the Shoulder of Two Giants" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/10/twolevellrucache-standing-on-shoulder.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0QGRHs6cSp7ImA9WhNTFUk.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-6235522902943285263</id><published>2012-10-18T14:21:00.001+08:00</published><updated>2012-10-18T14:22:05.519+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-10-18T14:22:05.519+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="proguard" /><category scheme="http://www.blogger.com/atom/ns#" term="xml" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="view" /><category scheme="http://www.blogger.com/atom/ns#" term="obfuscate" /><title>Caveat in Declaring a Click Handler in Android Layout XMLs</title><content type="html">Today I came across a post on the &lt;a href="http://corner.squareup.com/" target="_blank"&gt;Square blog&lt;/a&gt; written by &lt;a href="https://plus.google.com/106557483623231970995/about" target="_blank"&gt;Jesse Wilson&lt;/a&gt;, pointing out &lt;a href="http://corner.squareup.com/2012/08/getting-to-the-bottom.html" target="_blank"&gt;an unexpected error&lt;/a&gt; that might arise from declaring a &lt;a href="http://developer.android.com/reference/android/view/View.OnClickListener.html" target="_blank"&gt;View.OnClickListener&lt;/a&gt; in the Android layout XML files. &amp;nbsp;The Square team worked around this problem by always declaring click handlers in code rather than XML.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
This reminds me of my own experience in running into problems with click handler declaration in XML, but for a different root cause. &amp;nbsp;In production release we often obfuscate our code using &lt;a href="http://developer.android.com/tools/help/proguard.html" target="_blank"&gt;proguard&lt;/a&gt;. &amp;nbsp; If the proguard configuration is not written properly, there is a chance that click handler methods get renamed by proguard. &amp;nbsp;Those obfuscated method names are not recognized by XML declarations and can cause problems at run time.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Fortunately this problem should now be resolved because Google is &lt;a href="http://tools.android.com/recent/proguardimprovements" target="_blank"&gt;providing a base configuration file for proguard&lt;/a&gt;, which includes the following lines:&lt;/div&gt;
&lt;br /&gt;
&lt;pre class="brush:bash"&gt; -keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);                                                                                                                                  
 }&lt;/pre&gt;
&lt;script src="http://syntaxhighlight-blogger-dynamic.googlecode.com/files/syntaxhighlighter-20111212.js" type="text/javascript"&gt;&lt;/script&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=_Ar_N4mV0eE:OCyLph_gXAs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=_Ar_N4mV0eE:OCyLph_gXAs:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=_Ar_N4mV0eE:OCyLph_gXAs:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=_Ar_N4mV0eE:OCyLph_gXAs:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/_Ar_N4mV0eE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/6235522902943285263/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=6235522902943285263" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/6235522902943285263?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/6235522902943285263?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/_Ar_N4mV0eE/caveat-in-declaring-onclickhandler-in.html" title="Caveat in Declaring a Click Handler in Android Layout XMLs" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/10/caveat-in-declaring-onclickhandler-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkQCQXo7cSp7ImA9WhJaFE0.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-5832329466365909950</id><published>2012-10-03T15:21:00.000+08:00</published><updated>2012-10-05T11:39:20.409+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-10-05T11:39:20.409+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="evernote" /><category scheme="http://www.blogger.com/atom/ns#" term="clearly" /><category scheme="http://www.blogger.com/atom/ns#" term="fulltext" /><category scheme="http://www.blogger.com/atom/ns#" term="evernote-clearly" /><category scheme="http://www.blogger.com/atom/ns#" term="filtering" /><category scheme="http://www.blogger.com/atom/ns#" term="contentextraction" /><category scheme="http://www.blogger.com/atom/ns#" term="readability" /><category scheme="http://www.blogger.com/atom/ns#" term="content-extraction" /><category scheme="http://www.blogger.com/atom/ns#" term="fivefilters" /><category scheme="http://www.blogger.com/atom/ns#" term="jreadability" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Introducing JReadability: Making Web Content Easy-to-Read</title><content type="html">Reading content on the web is becoming quite a pain. &amp;nbsp;Instead of the main text that are of real interest to us, we are often sidetracked by complex layouts, fancy effects, and annoying ads. &amp;nbsp;Hence services like &lt;a href="http://evernote.com/clearly/" target="_blank"&gt;Evernote Clearly&lt;/a&gt;, &lt;a href="http://www.readability.com/" target="_blank"&gt;Readability&lt;/a&gt;, and &lt;a href="http://fivefilters.org/content-only/" target="_blank"&gt;Full-Text RSS&lt;/a&gt; are extremely useful and becoming popular.&lt;br /&gt;
&lt;br /&gt;
Being able to extract content from a web page is particularly useful for mobile settings, where both network bandwidth and screen real-estate are scarce. &amp;nbsp;With this motivation and inspired by arc90's &lt;a href="http://code.google.com/p/arc90labs-readability/"&gt;readability.js&lt;/a&gt; project and&amp;nbsp;Keyvan Minoukadeh's &lt;a href="http://code.fivefilters.org/php-readability/"&gt;php port&lt;/a&gt;, I have ported the readability.js project to Java so that it can be used as a library on Android. &amp;nbsp;I have named this &lt;a href="http://wuman.github.com/JReadability/"&gt;JReadability&lt;/a&gt; and made it &lt;a href="http://wuman.github.com/JReadability/"&gt;open source on GitHub&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
JReadability is released under the Apache License, Version 2.0. &amp;nbsp;Feel free to fork and improve it.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=Hgr5r4ODgDw:nAPowTDiz2E:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=Hgr5r4ODgDw:nAPowTDiz2E:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=Hgr5r4ODgDw:nAPowTDiz2E:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=Hgr5r4ODgDw:nAPowTDiz2E:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/Hgr5r4ODgDw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/5832329466365909950/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=5832329466365909950" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/5832329466365909950?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/5832329466365909950?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/Hgr5r4ODgDw/introducing-jreadability-making-web.html" title="Introducing JReadability: Making Web Content Easy-to-Read" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/10/introducing-jreadability-making-web.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4MSXo5eCp7ImA9WhJUEkg.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-996054117581235776</id><published>2012-09-04T14:39:00.001+08:00</published><updated>2012-09-10T12:56:28.420+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-10T12:56:28.420+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="urldecoding" /><category scheme="http://www.blogger.com/atom/ns#" term="commandlinefu" /><category scheme="http://www.blogger.com/atom/ns#" term="decode" /><category scheme="http://www.blogger.com/atom/ns#" term="cli" /><category scheme="http://www.blogger.com/atom/ns#" term="urldecode" /><category scheme="http://www.blogger.com/atom/ns#" term="commandline" /><category scheme="http://www.blogger.com/atom/ns#" term="parsing" /><category scheme="http://www.blogger.com/atom/ns#" term="regex" /><category scheme="http://www.blogger.com/atom/ns#" term="perl" /><category scheme="http://www.blogger.com/atom/ns#" term="decoding" /><title>One-Liner URLDecoder</title><content type="html">&lt;div&gt;
&lt;span style="font-family: inherit;"&gt;I found a useful &lt;a href="http://www.commandlinefu.com/commands/view/2285/urldecoding" target="_blank"&gt;one-liner URLDecoder&lt;/a&gt; on &lt;a href="http://commandlinefu.com/"&gt;commandlinefu.com&lt;/a&gt;:&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;pre class="brush:bash"&gt;perl -pe 's/%([0-9a-f]{2})/sprintf("%s", pack("H2",$1))/eig'&lt;/pre&gt;
&lt;div&gt;
&lt;span style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style="font-family: inherit;"&gt;Haven't found a good plugin for vim yet though.&lt;/span&gt;&lt;/div&gt;


&lt;script src='http://syntaxhighlight-blogger-dynamic.googlecode.com/files/syntaxhighlighter-20111212.js' type='text/javascript'&gt;&lt;/script&gt;
&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=_v1syfGFJNg:mO1Oy9Eqsug:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=_v1syfGFJNg:mO1Oy9Eqsug:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=_v1syfGFJNg:mO1Oy9Eqsug:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=_v1syfGFJNg:mO1Oy9Eqsug:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/_v1syfGFJNg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/996054117581235776/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=996054117581235776" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/996054117581235776?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/996054117581235776?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/_v1syfGFJNg/one-liner-urldecoder.html" title="One-Liner URLDecoder" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/09/one-liner-urldecoder.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08BQHs5fip7ImA9WhJWFk4.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-7799310878615189682</id><published>2012-08-22T19:44:00.000+08:00</published><updated>2012-08-22T19:44:11.526+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-22T19:44:11.526+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="zencoding" /><category scheme="http://www.blogger.com/atom/ns#" term="vi" /><category scheme="http://www.blogger.com/atom/ns#" term="plugins" /><category scheme="http://www.blogger.com/atom/ns#" term="vim" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="hack" /><category scheme="http://www.blogger.com/atom/ns#" term="vimrc" /><title>A Programmer's vim Settings</title><content type="html">For years I have been a programmer who is not fond of IDEs. &amp;nbsp;For almost everything that I can, I use the vim editor in the terminal. &amp;nbsp;This habit continued even when I switched from Ubuntu to Mac. &amp;nbsp;One of the greatest things about using vim as an editor is that I can program whenever and wherever I want, as long as I have a shell.&lt;br /&gt;
&lt;br /&gt;
Additionally, what makes vim super powerful is the huge number of plugins that augment the editor. &amp;nbsp;I spent some time to organize my vim settings using the &lt;a href="http://www.vim.org/scripts/script.php?script_id=2332" target="_blank"&gt;pathogen.vim plugin&lt;/a&gt;. &amp;nbsp;I am sharing my settings as&amp;nbsp;&lt;a href="https://github.com/wuman/vimrc" target="_blank"&gt;a project on github&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-DjPEWTxfjvc/UDTFh7wzoZI/AAAAAAAAaik/clKhXIgT7JA/s1600/Screen+Shot+2012-08-22+at+7.40.52+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="594" src="http://4.bp.blogspot.com/-DjPEWTxfjvc/UDTFh7wzoZI/AAAAAAAAaik/clKhXIgT7JA/s640/Screen+Shot+2012-08-22+at+7.40.52+PM.png" width="640" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A screenshot of my vim&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
The project comes pre-installed a number of plugins that I use regularly, including one of my favorites the &lt;a href="http://www.vim.org/scripts/script.php?script_id=2981" target="_blank"&gt;ZenCoding plugin&lt;/a&gt;. &amp;nbsp;I'd appreciate if you share with me more cool plugins.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=wrT2KSnrU-4:EqlJhcq6wgY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=wrT2KSnrU-4:EqlJhcq6wgY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=wrT2KSnrU-4:EqlJhcq6wgY:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=wrT2KSnrU-4:EqlJhcq6wgY:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/wrT2KSnrU-4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/7799310878615189682/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=7799310878615189682" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7799310878615189682?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7799310878615189682?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/wrT2KSnrU-4/a-programmers-vim-settings.html" title="A Programmer's vim Settings" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-DjPEWTxfjvc/UDTFh7wzoZI/AAAAAAAAaik/clKhXIgT7JA/s72-c/Screen+Shot+2012-08-22+at+7.40.52+PM.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/08/a-programmers-vim-settings.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkAHRXY7eCp7ImA9WhJWFk4.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-7473090965549918968</id><published>2012-07-24T01:48:00.000+08:00</published><updated>2012-08-22T19:25:34.800+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-22T19:25:34.800+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="usa" /><category scheme="http://www.blogger.com/atom/ns#" term="movies" /><category scheme="http://www.blogger.com/atom/ns#" term="market" /><category scheme="http://www.blogger.com/atom/ns#" term="marketenabler" /><category scheme="http://www.blogger.com/atom/ns#" term="tv" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="unlock" /><category scheme="http://www.blogger.com/atom/ns#" term="root" /><category scheme="http://www.blogger.com/atom/ns#" term="hack" /><title>Hack to Enable the Google Play Movies &amp; TV App Outside of US</title><content type="html">If you live or travel outside of the US you may notice that Google actively disables the Google Play Movies &amp;amp; TV app as soon as it finds out that the device is physically located outside of the US. &amp;nbsp;I personally find it annoying because I can no longer watch movies I purchased in the US when I'm in Taiwan. &amp;nbsp;So I spent some time to reverse engineer the app and understand how it is doing the check. &amp;nbsp;I then came up with a little hack to get my app back up running. &amp;nbsp;Here are the steps:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Make sure the phone is rooted.&lt;/li&gt;
&lt;li&gt;Use the Market Unlocker or the Market Enabler to fake a US carrier. &amp;nbsp;This should allow your Android Market to purchase or rent movies.&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;adb shell pm enable&amp;nbsp;com.google.android.videos.&lt;/span&gt;&lt;span style="font-family: inherit;"&gt;&amp;nbsp; This should allow your Google Play Movies &amp;amp; TV app to appear again in the launcher.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;sqlite3 /data/data/com.google.android.gsf/databases/gservices.db&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;UPDATE main SET value='us' WHERE name='device_country';&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Force stop Google Play Services and Google Play Movies &amp;amp; TV and restart the movies app. &amp;nbsp;Voila! &amp;nbsp;You should now be able to download or stream your purchased movies.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;[UPDATE 2012/8/22]&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Please note that in addition to the above hacks, you still need to use a US proxy to playback or download movies.&lt;/div&gt;
&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=azPKWE7b5fI:cJLvDEiXjzw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=azPKWE7b5fI:cJLvDEiXjzw:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=azPKWE7b5fI:cJLvDEiXjzw:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=azPKWE7b5fI:cJLvDEiXjzw:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/azPKWE7b5fI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/7473090965549918968/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=7473090965549918968" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7473090965549918968?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7473090965549918968?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/azPKWE7b5fI/hack-to-enable-google-play-movies-tv.html" title="Hack to Enable the Google Play Movies &amp; TV App Outside of US" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/07/hack-to-enable-google-play-movies-tv.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0EMSHszfSp7ImA9WhJTEE0.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-7088847399829477951</id><published>2012-06-18T15:48:00.001+08:00</published><updated>2012-06-18T15:48:09.585+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-18T15:48:09.585+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="taiwan" /><category scheme="http://www.blogger.com/atom/ns#" term="news" /><category scheme="http://www.blogger.com/atom/ns#" term="tablet" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Taiwanese News Reader for Android</title><content type="html">At least two Android tablets have been given to me as gifts from Google: &amp;nbsp;the Samsung Galaxy Tab 10.1 from &lt;a href="http://www.google.com/events/io/2011/index-live.html" target="_blank"&gt;Google I/O 2011&lt;/a&gt; and the HTC Flyer from the &lt;a href="http://registrano.com/events/21e315" target="_blank"&gt;Taipei Android Developer Lab&lt;/a&gt;. &amp;nbsp;Rumor also has it that another Nexus tablet featuring Jelly Bean will be given out at &lt;a href="https://developers.google.com/events/io/" target="_blank"&gt;Google I/O 2012&lt;/a&gt;. &amp;nbsp;If that rumor is real, which I think is highly likely, I will have in total 3 free tablets from Google.&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
When I received the second tablet at the ADL event, I promised to write at least one app designed for tablets as a token of appreciation for Google's generosity. &amp;nbsp;So in addition to what I am already doing at work, I have written in my free time a simple news reader app to demonstrate how a single APK can be written to support devices of all platform versions and all physical sizes.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The Taiwanese-targeting app aggregates news content from several mainstream Taiwanese news media and provides a unified reading experience. &amp;nbsp;Several highlighted features include:&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-Qt0yOu6g564/T97aXlW8o4I/AAAAAAAAZ6c/jH_xqkHCkZk/s1600/device-2012-06-14-015149.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://2.bp.blogspot.com/-Qt0yOu6g564/T97aXlW8o4I/AAAAAAAAZ6c/jH_xqkHCkZk/s400/device-2012-06-14-015149.png" width="222" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;It supports Android devices of all sizes. &amp;nbsp;It is optimized for best layout and resolution on even large size tablets.&lt;/li&gt;
&lt;li&gt;A ListView-like home widget is provided so that real time news can be easily browsed right from the home screen.&lt;/li&gt;
&lt;li&gt;Users can pick and reorder news categories for a more customized reading experience.&lt;/li&gt;
&lt;li&gt;Full-fledged sharing integration using the new &lt;a href="http://developer.android.com/reference/android/widget/ShareActionProvider.html" target="_blank"&gt;ShareActionProvider&lt;/a&gt; API is included to allow for easy sharing of news articles.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
Some screenshots are included here to give you a better idea of what it looks like:&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-UTCoc9pamnM/T97adAzm9eI/AAAAAAAAZ60/zFwxI7vFvfk/s1600/device-2012-06-14-211908.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://2.bp.blogspot.com/-UTCoc9pamnM/T97adAzm9eI/AAAAAAAAZ60/zFwxI7vFvfk/s400/device-2012-06-14-211908.png" width="222" /&gt;&lt;/a&gt;&lt;a href="http://1.bp.blogspot.com/-CBc6SS1uFuU/T97abQrV3MI/AAAAAAAAZ6k/BAFMzJOKsvI/s1600/device-2012-06-14-204543.png" imageanchor="1" style="background-color: white; margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://1.bp.blogspot.com/-CBc6SS1uFuU/T97abQrV3MI/AAAAAAAAZ6k/BAFMzJOKsvI/s400/device-2012-06-14-204543.png" width="223" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-gpeT-EORfCI/T97afl6QBGI/AAAAAAAAZ7M/cQ7tv1jb5_w/s1600/device-2012-06-14-233627.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://4.bp.blogspot.com/-gpeT-EORfCI/T97afl6QBGI/AAAAAAAAZ7M/cQ7tv1jb5_w/s640/device-2012-06-14-233627.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-te5LSimq0hg/T97agwHhLyI/AAAAAAAAZ7c/vFgbNLjLYII/s1600/device-2012-06-14-233932.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://1.bp.blogspot.com/-te5LSimq0hg/T97agwHhLyI/AAAAAAAAZ7c/vFgbNLjLYII/s640/device-2012-06-14-233932.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Check it out at the &lt;a href="https://play.google.com/store/apps/details?id=com.wuman.twnewsreader" target="_blank"&gt;Android Play Market&lt;/a&gt; today and be sure to let me know your suggestions!&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=IYJW_r0bFhE:hqcMK6SnRRc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=IYJW_r0bFhE:hqcMK6SnRRc:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=IYJW_r0bFhE:hqcMK6SnRRc:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=IYJW_r0bFhE:hqcMK6SnRRc:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/IYJW_r0bFhE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/7088847399829477951/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=7088847399829477951" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7088847399829477951?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7088847399829477951?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/IYJW_r0bFhE/taiwanese-news-reader-for-android.html" title="Taiwanese News Reader for Android" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-Qt0yOu6g564/T97aXlW8o4I/AAAAAAAAZ6c/jH_xqkHCkZk/s72-c/device-2012-06-14-015149.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/06/taiwanese-news-reader-for-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkAHQn85eSp7ImA9WhJUE0g.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-6448146835952767316</id><published>2012-06-07T01:55:00.000+08:00</published><updated>2012-09-11T17:12:13.121+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-11T17:12:13.121+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="fragmentation" /><category scheme="http://www.blogger.com/atom/ns#" term="actionbar" /><category scheme="http://www.blogger.com/atom/ns#" term="actionbarsherlock" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>My Minor Frustrations with Android (Sometimes)</title><content type="html">The first announcement of the Android platform by Google was an exciting one. &amp;nbsp;It was the first mobile platform that was somewhat open at every layer of the stack. &amp;nbsp;It brought a lot of opportunities for hardware manufacturers as well as software developers.&lt;br /&gt;
&lt;br /&gt;
Everything was great. &amp;nbsp;In particular,&amp;nbsp;as a software developer I was finally given the chance to really reach out to my users and make a side profit (albeit little) out of my apps. &amp;nbsp;I also finally saw some slim hope (compared to none before) that perhaps one day I could become a millionaire by publishing a surprisingly popular mobile app. &amp;nbsp;Call me naive and all, but hey it doesn't hurt to dream.&lt;br /&gt;
&lt;br /&gt;
Yet everything was great, only for a while. &amp;nbsp;My frustration with developing apps on Android grows each day. &amp;nbsp;Here is why.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Differences Across Devices&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Some people call this fragmentation. &amp;nbsp;I don't like that word so much though. &amp;nbsp;In fact, I see the huge variety of devices an opportunity to reach out to more users and for more use cases. &amp;nbsp;I am completely accepting of the idea that there should be many different devices having different capabilities.&lt;br /&gt;
&lt;br /&gt;
When challenged with this question, some folks at Google will subtly respond by basically saying that they have designed their APIs and written guidelines to help developers write apps that work on all devices with different capabilities. &amp;nbsp;In other words, if you are having a problem, it's most likely your fault for not following the guidelines and writing the app correctly.&lt;br /&gt;
&lt;br /&gt;
While that is certainly the case for many, if not most, cases, there are still discrepancies in devices implementing those APIs. &amp;nbsp;A quick example would be how cameras made by different device manufacturers return their EXIF information differently. &amp;nbsp;If you were a developer wanting to make an app that makes use of the camera for the first time, you would have a hard time dealing with the correction of photo orientations based on the EXIF. &amp;nbsp;It's because they are not consistent across devices! &amp;nbsp;Somehow that was overlooked in the CTS. &amp;nbsp;Another example is how the default theme can be drastically different from one device to another (though should be fixed in ICS), which leads to my next point.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Backward Compatibility with Older Platforms&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Android has a very rapid iteration cycle. &amp;nbsp;About every 6 months a new major version is out and there are huge API differences between them. &amp;nbsp;Each jump from Eclair to Froyo, from Froyo to Gingerbread, from Gingerbread to Honeycomb (OMG don't even let me get started on the Honeycomb which was totally half-baked), and from Honeycomb to Ice Cream Sandwich was a pain. &amp;nbsp;For each new Android version, new theme attributes (many of which were supposed to be there in the first place) and new APIs are introduced. &amp;nbsp;It takes a lot of effort to make a decision on where the trade-off should be at: make use of new APIs for new features and cleaner code, or stick with old APIs to maintain backward compatibility with all old devices. &amp;nbsp;Sometimes you even wonder if it was worth it to stick a piece of ugly workaround into your otherwise clean code just for the sake of having a new feature on an old device. &amp;nbsp;The truth is that the market share for Eclair is still significant enough for most serious apps to maintain backward compatibility with at least Eclair. &amp;nbsp;That was more than 2 years ago!&lt;br /&gt;
&lt;br /&gt;
A great thing that Google introduced was the &lt;a href="http://developer.android.com/sdk/compatibility-library.html" target="_blank"&gt;support library&lt;/a&gt;, which packages implementations for many important features introduced in new versions of Android into a library so that they can be used on older devices. &amp;nbsp;They have failed to include one of the most important APIs in the library though: the &lt;a href="http://developer.android.com/reference/android/app/ActionBar.html" target="_blank"&gt;ActionBar&lt;/a&gt;. &amp;nbsp; &amp;nbsp;Thanks to &lt;a href="http://jakewharton.com/" target="_blank"&gt;Jake Wharton&lt;/a&gt;, the &lt;a href="http://actionbarsherlock.com/" target="_blank"&gt;ActionBarSherlock library&lt;/a&gt; adds the ActionBar API into the support library. &amp;nbsp;It works quite well but there are still a couple of real shortcomings:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;If you use a ViewPager to swipe between different fragments and you want those fragments to show different action items, you are &lt;a href="http://code.google.com/p/android/issues/detail?id=29472" target="_blank"&gt;out of luck&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you want to &lt;a href="http://developer.android.com/guide/topics/ui/menus.html#CABforListView" target="_blank"&gt;enable batch contextual actions in ListView or GridView&lt;/a&gt;, you're also out of luck because it is not yet supported by the ActionBarSherlock library.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
In a nutshell, as a developer I spend about 80% of my time on not the design or innovation for my app, but on efforts towards backward compatibility. &amp;nbsp;That is real pain.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Having said all that, I should still say that I love Android. &amp;nbsp;It has done way more good than bad. &amp;nbsp;In fact, I should commend Google for answering questions on StackOverflow and improving the platform at a rapid pace. &amp;nbsp;Keep up with the good work Google! &amp;nbsp;But before that could somebody please take a look at this &lt;a href="http://stackoverflow.com/questions/10775008/in-app-billing-item-not-found" target="_blank"&gt;in-app billing problem&lt;/a&gt; that I ran into today (seems like a bug with the &lt;a href="http://android-developers.blogspot.tw/2012/05/in-app-subscriptions-in-google-play.html" target="_blank"&gt;recent introduction of subscriptions&lt;/a&gt;)? &amp;nbsp;Thanks a lot.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;[UPDATE 2012/06/20]&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Apparently the in-app billing problem is now resolved.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;[UPDATE 2012/09/11]&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
The issue with random disappearance of action items is seemingly resolved with the workaround mentioned in &lt;a href="http://code.google.com/p/android/issues/detail?id=29472#c8" target="_blank"&gt;comment 8 of the issue report&lt;/a&gt;.&lt;/div&gt;
&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=BIejxVG1QWs:A3KL2-a7Vvs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=BIejxVG1QWs:A3KL2-a7Vvs:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=BIejxVG1QWs:A3KL2-a7Vvs:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=BIejxVG1QWs:A3KL2-a7Vvs:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/BIejxVG1QWs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/6448146835952767316/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=6448146835952767316" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/6448146835952767316?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/6448146835952767316?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/BIejxVG1QWs/my-minor-frustrations-with-android.html" title="My Minor Frustrations with Android (Sometimes)" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/06/my-minor-frustrations-with-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUYARHczfyp7ImA9WhJUEkg.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-5962056053208559216</id><published>2012-02-15T13:35:00.001+08:00</published><updated>2012-09-10T12:59:05.987+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-10T12:59:05.987+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="performance" /><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="access modifier" /><category scheme="http://www.blogger.com/atom/ns#" term="nested" /><category scheme="http://www.blogger.com/atom/ns#" term="thunk" /><category scheme="http://www.blogger.com/atom/ns#" term="textview" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Bypassing the Thunk</title><content type="html">One of the best things about reading open source code is that it allows you to learn from other developers. &amp;nbsp;I have recently experienced this first hand when I read the source code for the Android &lt;a href="http://developer.android.com/reference/android/widget/TextView.html" target="_blank"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;TextView&lt;/span&gt;&lt;/a&gt;. &amp;nbsp;Due to my &lt;a href="http://www.slideshare.net/wuman/fancy-rich-text-on-android-using-roguso-as-an-example" target="_blank"&gt;recent work on inline rich text editing for Android&lt;/a&gt;, I read most of the source code for the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;TextView&lt;/span&gt; class to study how it works.&lt;br /&gt;
&lt;br /&gt;
While I was reading the source code, I came across a comment for a method called &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;spanChange()&lt;/span&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java"&gt;    /**
     * Not private so it can be called from an inner class without going
     * through a thunk.
     */
    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
         // spanChange content
    }
&lt;/pre&gt;
&lt;br /&gt;
The &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;spanChange()&lt;/span&gt; method is an outer class (&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;TextView&lt;/span&gt;) method that is called by an inner class instance.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java"&gt;    private class ChangeWatcher implements TextWatcher, SpanWatcher {

        public void onSpanChanged(Spannable buf,
                                  Object what, int s, int e, int st, int en) {
            TextView.this.spanChange(buf, what, s, st, e, en);
        }

    }
&lt;/pre&gt;
&lt;br /&gt;
The term "thunk" was new to me but it appears that the change of access modifier for the outer method is intentional to improve performance. &amp;nbsp;So I quickly became curious and decided to dig a little deeper.&lt;br /&gt;
&lt;br /&gt;
It turns out that "thunk" in this context refers to a compiler-generated wrapper function to invoke an outer class method from an inner class. &amp;nbsp;I had always known about the existence of such wrapper functions generated by the compiler, but never knew there was a conventional term for it.&lt;br /&gt;
&lt;br /&gt;
To better understand the performance impacts of such wrapper functions, I wrote some test code to observe how the compiler behaves differently.&lt;br /&gt;
&lt;br /&gt;
Here is my test code:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java"&gt;public class OuterClass {

 private final InnerClass mInner;

 public OuterClass() {
  super();
  mInner = new InnerClass();
 }

 public void start(int loops) {
  for (int i = 0; i &amp;lt; loops; i++) {
   mInner.callOuterFunctionDirect();
  }
  for (int i = 0; i &amp;lt; loops; i++) {
   mInner.callOuterFunctionThunk();
  }
  mInner.dump();
 }

 void outerDirectMethod() {
  System.out.println("Direct outer method");
 }

 private void outerPrivateMethod() {
  System.out.println("Private outer method");
 }

 private class InnerClass {

  private TimingLogger mLoggerDirect;
  private TimingLogger mLoggerThunk;
  private int mCountDirect;
  private int mCountThunk;

  public InnerClass() {
   super();
  }

  public void dump() {
   mLoggerDirect.dumpToLog();
   mLoggerThunk.dumpToLog();
  }

  public void callOuterFunctionDirect() {
   if (mLoggerDirect == null) {
    mLoggerDirect = new TimingLogger("test",
      "callOuterFunctionDirect");
   }
   OuterClass.this.outerDirectMethod();
   mLoggerDirect.addSplit("Count: #" + (++mCountDirect));
  }

  public void callOuterFunctionThunk() {
   if (mLoggerThunk == null) {
    mLoggerThunk = new TimingLogger("test",
      "callOuterFunctionThunk");
   }
   OuterClass.this.outerPrivateMethod();
   mLoggerThunk.addSplit("Count: #" + (++mCountThunk));
  }

 }

}
&lt;/pre&gt;
&lt;br /&gt;
The compiler compiles the invocation of the outer method differently. &amp;nbsp;If the access modifier was not private, then the outer method is a virtual function which can be invoked directly:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:plain"&gt;    invoke-virtual {v0}, Lcom/test/application/OuterClass;-&amp;gt;outerDirectMethod()V
&lt;/pre&gt;
&lt;br /&gt;
On the other hand, if the access modifier was private, then the compiler automatically generates a thunk and invokes the outer method indirectly via the thunk:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:plain"&gt;    #calls: Lcom/test/application/OuterClass;-&amp;gt;outerPrivateMethod()V
    invoke-static {v0}, Lcom/test/application/OuterClass;-&amp;gt;access$0(Lcom/test/application/OuterClass;)V
&lt;/pre&gt;
&lt;br /&gt;
The &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;access$0&lt;/span&gt; method is the thunk:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:plain"&gt;.method static synthetic access$0(Lcom/test/application/OuterClass;)V
    .locals 0
    .parameter

    .prologue
    .line 5
    invoke-direct {p0}, Lcom/test/application/OuterClass;-&amp;gt;outerPrivateMethod()V

    return-void
.end method
&lt;/pre&gt;
&lt;br /&gt;
Invocation of the outer method through the thunk requires extra overhead in the call stack. &amp;nbsp;My code does not test the memory footprint of the call stack, but it does compare the execution performance of the two different ways of outer method invocation. &amp;nbsp;My test results show that on an average of 10 test runs of 10K loops, the thunk does degrade performance:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Direct: &amp;nbsp;11354ms&lt;/li&gt;
&lt;li&gt;Thunk: &amp;nbsp;16485ms&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;br /&gt;
Just thought this was kind of interesting. &amp;nbsp;I love open source. &amp;nbsp;:)&lt;/div&gt;

&lt;script src='http://syntaxhighlight-blogger-dynamic.googlecode.com/files/syntaxhighlighter-20111212.js' type='text/javascript'&gt;&lt;/script&gt;
&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=F6qnB5Rdqo4:N8GZmiTOoGY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=F6qnB5Rdqo4:N8GZmiTOoGY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=F6qnB5Rdqo4:N8GZmiTOoGY:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=F6qnB5Rdqo4:N8GZmiTOoGY:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/F6qnB5Rdqo4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/5962056053208559216/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=5962056053208559216" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/5962056053208559216?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/5962056053208559216?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/F6qnB5Rdqo4/bypassing-thunk.html" title="Bypassing the Thunk" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/02/bypassing-thunk.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMGQX8_fCp7ImA9WhRbFko.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-7643151616360327151</id><published>2012-02-08T12:13:00.001+08:00</published><updated>2012-02-08T12:13:40.144+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-08T12:13:40.144+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="vpon" /><category scheme="http://www.blogger.com/atom/ns#" term="ad" /><category scheme="http://www.blogger.com/atom/ns#" term="mat" /><category scheme="http://www.blogger.com/atom/ns#" term="leaks" /><category scheme="http://www.blogger.com/atom/ns#" term="memory" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="admob" /><title>Memory Leaks Discovered in the Vpon Android SDK</title><content type="html">Most mobile developers in Taiwan are familiar with the name &lt;a href="http://www.vpon.com/" target="_blank"&gt;Vpon&lt;/a&gt;. &amp;nbsp;It is the de facto mobile ad platform to choose (instead of &lt;a href="http://www.admob.com/" target="_blank"&gt;AdMob&lt;/a&gt;) if your app targets Taiwan and/or mainland China. &amp;nbsp;Unfortunately the quality of their &lt;a href="http://wiki.vpon.com/index.php?title=Android_SDK_Document" target="_blank"&gt;Android SDK&lt;/a&gt; and support can be&amp;nbsp;appalling.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;a href="https://plus.google.com/103043706577279283558/posts/NNdSpv9oW5T" target="_blank"&gt;A little more than a month ago&lt;/a&gt; I had discovered at least 3 places in their Android SDK that introduce serious memory leaks. &amp;nbsp;We contacted their support team, listing instances where the memory leaks were taking place (well all the ones that I found anyway). &amp;nbsp;FWIW, the root causes had been identified and a fix would be simple. &amp;nbsp;We asked for an immediate fix, but unfortunately as of today no actions nor responses have been taken/heard.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
As such I am reporting my findings in this blog post.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
I first found the memory leaks in my Android app &lt;a href="http://roguso.com/" target="_blank"&gt;Roguso&lt;/a&gt;. &amp;nbsp;In this app there is a ListView of posts that, when clicked, a corresponding details page will show up. &amp;nbsp;The page holding the ListView is implemented as &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;TimelinesActivity&lt;/span&gt;, and the details sub-pages are implemented as &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;PlurkResponsesActivity&lt;/span&gt;. &amp;nbsp;The &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;PlurkResponsesActivity&lt;/span&gt; is written so that it is reused for different posts in the ListView. &amp;nbsp;That's a fairly typical pattern.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
We placed an ad on both the list page and the details page:&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-xLS2P9wczOE/TzHw_QQ2XqI/AAAAAAAAYI8/_LvOgr6757k/s1600/timelines.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="http://4.bp.blogspot.com/-xLS2P9wczOE/TzHw_QQ2XqI/AAAAAAAAYI8/_LvOgr6757k/s400/timelines.png" width="240" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Roguso ListView page (&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;TimelinesActivity&lt;/span&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-c4wJ7DuHwxg/TzHxCqE8cZI/AAAAAAAAYJE/jbrQjWJgEKI/s1600/responses.png" imageanchor="1" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;img border="0" height="400" src="http://1.bp.blogspot.com/-c4wJ7DuHwxg/TzHxCqE8cZI/AAAAAAAAYJE/jbrQjWJgEKI/s400/responses.png" width="240" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Roguso detail page (&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;PlurkResponsesActivity&lt;/span&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div&gt;
We soon discovered that after several page clicks the app was losing free memory quickly! &amp;nbsp;Checking the &lt;a href="http://www.eclipse.org/mat/" target="_blank"&gt;MAT&lt;/a&gt; (Eclipse Memory Analyzer) we found that the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;PlurkResponsesActivity&lt;/span&gt; was not being reused and had several instances.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-qiOEkUkNOIQ/TzHxv-Vy4XI/AAAAAAAAYJM/2c19_-KoIHo/s1600/Screen+Shot+2012-02-08+at+11.18.07+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="348" src="http://2.bp.blogspot.com/-qiOEkUkNOIQ/TzHxv-Vy4XI/AAAAAAAAYJM/2c19_-KoIHo/s640/Screen+Shot+2012-02-08+at+11.18.07+AM.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Looking at strong-referenced paths to GC roots on one of the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;PlurkResponsesActivity&lt;/span&gt; instances we identified several memory leaks caused by the Vpon ad:&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-_04XsZR_gxg/TzHyrdNBTCI/AAAAAAAAYJU/24ioRQCfaCM/s1600/Screen+Shot+2012-02-08+at+11.18.58+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="488" src="http://2.bp.blogspot.com/-_04XsZR_gxg/TzHyrdNBTCI/AAAAAAAAYJU/24ioRQCfaCM/s640/Screen+Shot+2012-02-08+at+11.18.58+AM.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
After some reverse engineering on the Vpon Android SDK, I found at least 3 places in their code that introduced the memory leaks:&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;There is a static variable in the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;LocationUtils&lt;/span&gt; class that holds a long-living strong reference to Context.&lt;/li&gt;
&lt;li&gt;There is a static variable in &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;AdManager&lt;/span&gt; class that holds a long-living strong reference to Context.&lt;/li&gt;
&lt;li&gt;A Runnable with obfuscated class name &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;com.vpon.adon.android.b&lt;/span&gt; was being executed repeatedly and endlessly by the main thread Handler. &amp;nbsp;The Runnable itself holds a strong reference to &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;AdView&lt;/span&gt;, which causes a memory leak to its associated Context.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
I'm pretty sure that there are more than 3 places introducing the memory leaks though. &amp;nbsp;These are just obvious findings that I discovered fairly quickly.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
A quick workaround to this problem is to not show an ad on every page of your app. &amp;nbsp;In Roguso's case, we removed ads on all sub-pages and used the &lt;a href="http://developer.android.com/guide/topics/manifest/activity-element.html#lmode" target="_blank"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;singleTask&lt;/span&gt; launch mode&lt;/a&gt; on the main page. &amp;nbsp;That should significantly reduce the occurrences of memory leaks.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Nevertheless, we urge Vpon to patch their Android SDK soon.&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=qDpe6hvY6xA:l6VY59hZHGU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=qDpe6hvY6xA:l6VY59hZHGU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=qDpe6hvY6xA:l6VY59hZHGU:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=qDpe6hvY6xA:l6VY59hZHGU:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/qDpe6hvY6xA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/7643151616360327151/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=7643151616360327151" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7643151616360327151?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7643151616360327151?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/qDpe6hvY6xA/memory-leaks-discovered-in-vpon-android.html" title="Memory Leaks Discovered in the Vpon Android SDK" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-xLS2P9wczOE/TzHw_QQ2XqI/AAAAAAAAYI8/_LvOgr6757k/s72-c/timelines.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/02/memory-leaks-discovered-in-vpon-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU8CQH4_fCp7ImA9WhRUF04.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-4967259962439917767</id><published>2012-01-28T14:57:00.001+08:00</published><updated>2012-01-28T14:57:41.044+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-28T14:57:41.044+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="LruCache" /><category scheme="http://www.blogger.com/atom/ns#" term="cache" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>LruCache with SoftReference on Android</title><content type="html">The use of cache is commonly important in Android programming, where the target devices are often limited in terms of capabilities. &amp;nbsp;A cache improves performance by storing frequently accessed objects in memory so that they don't need to spend the extra overhead to be prepared or fetched again. &amp;nbsp;On Android we often use a cache to store data loaded from a &lt;a href="http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html"&gt;SQLite database&lt;/a&gt; or &lt;a href="http://developer.android.com/reference/android/graphics/Bitmap.html"&gt;Bitmap&lt;/a&gt;s that can be reused across different &lt;a href="http://developer.android.com/reference/android/widget/ListView.html"&gt;ListView&lt;/a&gt; items.&lt;br /&gt;
&lt;br /&gt;
Sometimes it is inevitable that we store in cache objects that themselves contain a strong reference to a &lt;a href="http://developer.android.com/reference/android/content/Context.html"&gt;Context&lt;/a&gt;. &amp;nbsp;For instance, we often store &lt;a href="http://developer.android.com/reference/android/graphics/drawable/Drawable.html"&gt;Drawable&lt;/a&gt; objects in a cache to improve performance, but these Drawable objects also hold a strong reference to an associated Context. &amp;nbsp;If the cache itself somehow outlives the Context (which is often the case if the cache is being used for multiple instances of an Activity), then we will have a memory leak.&lt;br /&gt;
&lt;br /&gt;
Another problem with cache is that it often takes up a large area of memory. &amp;nbsp;When the system is running low on memory, cache should usually be one of the best candidates for garbage collection.&lt;br /&gt;
&lt;br /&gt;
Android has a native implementation of cache called &lt;a href="http://developer.android.com/reference/android/util/LruCache.html"&gt;LruCache&lt;/a&gt;, which uses the &lt;a href="http://en.wikipedia.org/wiki/Cache_algorithm#Least_Recently_Used"&gt;LRU&lt;/a&gt; replacement policy. &amp;nbsp;It was introduced in Honeycomb. &amp;nbsp;If backward compatibility is needed, there is a &lt;a href="http://developer.android.com/reference/android/support/v4/util/LruCache.html"&gt;Support Library version&lt;/a&gt; too. &amp;nbsp;However, if used inappropriately, it is possible that we run into the aforementioned problems.&lt;br /&gt;
&lt;br /&gt;
I have rewritten the LruCache so that it uses &lt;a href="http://developer.android.com/reference/java/lang/ref/SoftReference.html"&gt;SoftReference&lt;/a&gt; on all of its cache entries. &amp;nbsp;I call it the LruSoftCache. &amp;nbsp;The great thing about the LruSoftCache is that it uses SoftReference on all of its cache entries, which means that cache entries don't get held on forever and hence will not be leaked. &amp;nbsp;The only drawback is that the cache size can now only be measured in the number of entries. &amp;nbsp;You can find the source code &lt;a href="https://github.com/wuman/LruSoftCache/blob/master/src/com/wuman/utils/LruSoftCache.java"&gt;here on GitHub&lt;/a&gt;.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=dgRk6Vw4wh8:gtI07c9F9Ng:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=dgRk6Vw4wh8:gtI07c9F9Ng:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=dgRk6Vw4wh8:gtI07c9F9Ng:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=dgRk6Vw4wh8:gtI07c9F9Ng:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/dgRk6Vw4wh8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/4967259962439917767/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=4967259962439917767" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/4967259962439917767?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/4967259962439917767?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/dgRk6Vw4wh8/lrucache-with-softreference-on-android.html" title="LruCache with SoftReference on Android" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.wu-man.com/2012/01/lrucache-with-softreference-on-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU8CR3w_cCp7ImA9WhVVFks.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-7201771111942201502</id><published>2011-12-30T16:59:00.001+08:00</published><updated>2012-05-10T23:31:06.248+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-10T23:31:06.248+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="performance" /><category scheme="http://www.blogger.com/atom/ns#" term="speech" /><category scheme="http://www.blogger.com/atom/ns#" term="gtug" /><category scheme="http://www.blogger.com/atom/ns#" term="ossf" /><category scheme="http://www.blogger.com/atom/ns#" term="memory" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="presentation" /><title>Improving Android Application Performance and Enabling Inline Rich-Text Editing on Android</title><content type="html">The month of December is kind of crazy for me. &amp;nbsp;I had two speeches and two products for launch, not mentioning the heavy workload in my day job.&lt;br /&gt;
&lt;br /&gt;
The first talk was about sharing tips and tricks on improving Android app performance. &amp;nbsp;It was given at the &lt;a href="http://www.csie.ntu.edu.tw/main.php"&gt;Department of Computer Science &amp;amp; Information Engineering&lt;/a&gt; of &lt;a href="http://www.ntu.edu.tw/"&gt;National Taiwan University&lt;/a&gt;, where I graduated from. &amp;nbsp;So there were some sentimental moments there LOL. &amp;nbsp;You can find my slides &lt;a href="http://www.slideshare.net/wuman/improving-android-application-performance-invited-talk-at-national-taiwan-university"&gt;here on SlideShare&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
The second talk was about sharing ways of enabling your Android app to use inline rich-text editing. &amp;nbsp;It was given at the &lt;a href="http://www.openfoundry.org/en/activities/details/240-open-source-apps-on-mobile"&gt;Open Source Apps on Mobile&lt;/a&gt; forum held in &lt;a href="http://www.sinica.edu.tw/main_e.shtml"&gt;Academia Sinica&lt;/a&gt;. &amp;nbsp;Thanks to my&amp;nbsp;&lt;a href="http://www.openfoundry.org/"&gt;OSSF&lt;/a&gt; friends who invited me to speak there. &amp;nbsp;You can find the slides for this talk &lt;a href="http://www.slideshare.net/wuman/fancy-rich-text-editing-on-android-invited-open-source-software-foundation-talk"&gt;here on SlideShare&lt;/a&gt; as well. &amp;nbsp;The video recording of this talk is now &lt;a href="http://www.youtube.com/watch?v=xQXyiaJxh8Y" target="_blank"&gt;available on YouTube&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;object width="320" height="266" class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="http://2.gvt0.com/vi/xQXyiaJxh8Y/0.jpg"&gt;&lt;param name="movie" value="http://www.youtube.com/v/xQXyiaJxh8Y&amp;fs=1&amp;source=uds" /&gt;
&lt;param name="bgcolor" value="#FFFFFF" /&gt;
&lt;embed width="320" height="266"  src="http://www.youtube.com/v/xQXyiaJxh8Y&amp;fs=1&amp;source=uds" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;br /&gt;&lt;/div&gt;
I'll be speaking again on the same topic (rich-text editing) at the &lt;a href="http://www.taipei-gtug.org/"&gt;Taipei Google Technology User Group&lt;/a&gt; gathering in January, but hopefully with more demos. &amp;nbsp;Be sure to check it out!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;UPDATE [January 28, 2012]&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
The rich text speech at GTUG was &lt;a href="http://registrano.com/events/4e1e31"&gt;held on January 18&lt;/a&gt;. &amp;nbsp;I used my app &lt;a href="http://roguso.com/"&gt;Roguso&lt;/a&gt; as an example to demonstrate how rich text can be implemented on Android. &amp;nbsp;The slides are &lt;a href="http://www.slideshare.net/wuman/fancy-rich-text-on-android-using-roguso-as-an-example"&gt;available for download on SlideShare&lt;/a&gt;. &amp;nbsp;The speech was also video recorded and can be watched on YouTube as &lt;a href="http://www.youtube.com/watch?v=Plq-nViUxrg"&gt;Part 1&lt;/a&gt; and &lt;a href="http://www.youtube.com/watch?v=w73HAr8DY9M"&gt;Part 2&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;object width="320" height="266" class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="http://0.gvt0.com/vi/Plq-nViUxrg/0.jpg"&gt;&lt;param name="movie" value="http://www.youtube.com/v/Plq-nViUxrg&amp;fs=1&amp;source=uds" /&gt;
&lt;param name="bgcolor" value="#FFFFFF" /&gt;
&lt;embed width="320" height="266"  src="http://www.youtube.com/v/Plq-nViUxrg&amp;fs=1&amp;source=uds" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;object width="320" height="266" class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="http://3.gvt0.com/vi/w73HAr8DY9M/0.jpg"&gt;&lt;param name="movie" value="http://www.youtube.com/v/w73HAr8DY9M&amp;fs=1&amp;source=uds" /&gt;
&lt;param name="bgcolor" value="#FFFFFF" /&gt;
&lt;embed width="320" height="266"  src="http://www.youtube.com/v/w73HAr8DY9M&amp;fs=1&amp;source=uds" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;
&lt;br /&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=0uv7Nkd7Oco:OWYbtLoUEPU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=0uv7Nkd7Oco:OWYbtLoUEPU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=0uv7Nkd7Oco:OWYbtLoUEPU:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=0uv7Nkd7Oco:OWYbtLoUEPU:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/0uv7Nkd7Oco" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/7201771111942201502/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=7201771111942201502" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7201771111942201502?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7201771111942201502?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/0uv7Nkd7Oco/improving-android-application.html" title="Improving Android Application Performance and Enabling Inline Rich-Text Editing on Android" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/12/improving-android-application.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMFQH08cSp7ImA9WhJUEkg.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-7656965203857004634</id><published>2011-12-30T16:38:00.000+08:00</published><updated>2012-09-10T13:03:31.379+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-10T13:03:31.379+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="actionbarshelock" /><category scheme="http://www.blogger.com/atom/ns#" term="compatibility" /><category scheme="http://www.blogger.com/atom/ns#" term="actionmodestyle" /><category scheme="http://www.blogger.com/atom/ns#" term="actionmode" /><category scheme="http://www.blogger.com/atom/ns#" term="actionbar" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Customizing the ActionMode Bar on Honeycomb Devices</title><content type="html">The &lt;a href="http://developer.android.com/reference/android/app/ActionBar.html"&gt;ActionBar&lt;/a&gt; API provided by Google on Honeycomb or later devices has great features, but can be a pain to use if your app needs to ensure compatibility. A recommended way to maintain compatibility while using most of the great features provided by the ActionBar API would be to use the&amp;nbsp;&lt;a href="http://actionbarsherlock.com/"&gt;ActionBarSherlock&lt;/a&gt;&amp;nbsp;library. The library is a fork of the&amp;nbsp;&lt;a href="http://developer.android.com/sdk/compatibility-library.html"&gt;Android Support Package&lt;/a&gt;&amp;nbsp;(aka. Android Compatibility Library) so in addition to the ActionBar API, you can also use other useful features such as Fragments, Loaders, etc.&lt;br /&gt;
&lt;br /&gt;
Anyway I was looking for ways to customize the style of the title and subtitle of the &lt;a href="http://developer.android.com/reference/android/view/ActionMode.html"&gt;ActionMode&lt;/a&gt; bar, but realized that the attribute &lt;i&gt;&lt;a href="http://developer.android.com/reference/android/R.attr.html#actionModeStyle"&gt;android:actionModeStyle&lt;/a&gt;&lt;/i&gt; wasn't provided until Ice Cream Sandwich. &amp;nbsp;That means you can't customize this easily in your theme/style XML files like you could with the ActionBar.&lt;br /&gt;
&lt;br /&gt;
I wrote a little hack and tested it on my Galaxy Tab 10.1 to be working. &amp;nbsp;You basically add the following piece of code in beginning of your &lt;a href="http://developer.android.com/reference/android/view/ActionMode.Callback.html#onPrepareActionMode(android.view.ActionMode, android.view.Menu)"&gt;onPrepareActionMode()&lt;/a&gt; before proceeding:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:java"&gt;if (UIUtils.isHoneycomb(true) &amp;amp;&amp;amp; !UIUtils.isICS(true)) {
    /*
     * Honeycomb devices do not allow style customization of
     * the action mode title/subtitle. We hack this by code.
     */

    final int contextId = getResources().getIdentifier(
            "action_context_bar", "id", "android");
    final int titleId = getResources().getIdentifier(
            "action_bar_title", "id", "android");
    final int subtitleId = getResources().getIdentifier(
            "action_bar_subtitle", "id", "android");

    if (contextId != 0 &amp;amp;&amp;amp; titleId != 0 &amp;amp;&amp;amp; subtitleId != 0) {
        try {
            ViewGroup decorView = (ViewGroup) getActivity()
                    .getWindow().getDecorView();
            ActionBarContextView abContext = (ActionBarContextView) decorView
                    .findViewById(contextId);
            TextView titleView = (TextView) abContext
                    .findViewById(titleId);
            TextView subtitleView = (TextView) abContext
                    .findViewById(subtitleId);

            if (titleView != null) {
                titleView.setTextAppearance(getActivity(),
                        R.style.ActionModeTitleStyle);
            }
            if (subtitleView != null) {
                subtitleView.setTextAppearance(
                        getActivity(),
                        R.style.ActionModeSubtitleStyle);
            }
        } catch (Exception e) {
            // If this is a custom device with customized
            // layouts, then forget it
        }
    }

}
&lt;/pre&gt;
&lt;br /&gt;
Obviously you will need to set the styles accordingly in your application. &amp;nbsp;UIUtils is just my own helper class to determine the device's API version.


&lt;script src='http://syntaxhighlight-blogger-dynamic.googlecode.com/files/syntaxhighlighter-20111212.js' type='text/javascript'&gt;&lt;/script&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=i7pHOHTrCVs:rt7iT_6LeWk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=i7pHOHTrCVs:rt7iT_6LeWk:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=i7pHOHTrCVs:rt7iT_6LeWk:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=i7pHOHTrCVs:rt7iT_6LeWk:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/i7pHOHTrCVs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/7656965203857004634/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=7656965203857004634" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7656965203857004634?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/7656965203857004634?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/i7pHOHTrCVs/actionbar-api-provided-by-google-on.html" title="Customizing the ActionMode Bar on Honeycomb Devices" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/12/actionbar-api-provided-by-google-on.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcFR3Y6fSp7ImA9WhdSFk8.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-4881219225368150253</id><published>2011-07-26T02:24:00.010+08:00</published><updated>2011-07-26T02:53:36.815+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-26T02:53:36.815+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="adapter" /><category scheme="http://www.blogger.com/atom/ns#" term="compatibility" /><category scheme="http://www.blogger.com/atom/ns#" term="viewpager" /><category scheme="http://www.blogger.com/atom/ns#" term="honeycomb" /><category scheme="http://www.blogger.com/atom/ns#" term="ui" /><category scheme="http://www.blogger.com/atom/ns#" term="google" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="workspace" /><title>Horizontal View/Fragment Paging in the Android Compatibility Package</title><content type="html">A while ago I &lt;a href="http://blog.wu-man.com/2011/06/efficient-tweetdeck-styled-workspace.html"&gt;published source code&lt;/a&gt; for a &lt;a href="http://developer.android.com/reference/android/view/ViewGroup.html"&gt;ViewGroup&lt;/a&gt; which pages horizontally in between multiple child views in an efficient manner using the &lt;a href="http://developer.android.com/reference/android/widget/Adapter.html"&gt;Adapter&lt;/a&gt;.  I called it the AdapterWorkspace, which extends the &lt;a href="http://developer.android.com/reference/android/widget/AdapterViewAnimator.html"&gt;AdapterViewAnimator&lt;/a&gt; class in the &lt;a href="http://developer.android.com/sdk/android-3.0.html"&gt;Honeycomb release&lt;/a&gt;.  Most readers probably did not benefit from this, as the source code for AdapterViewAnimator (as well as source code in general for all Honeycomb releases from 3.0 to 3.2) remains closed.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Fortunately, &lt;a href="http://developer.android.com/sdk/compatibility-library.html"&gt;revision 3 of the Android Compatibility Package&lt;/a&gt; has included classes that function the exact same way.  Better yet, the source code is available!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The following two classes work the same way as my AdapterWorkspace:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;ViewPager&lt;/b&gt;: A ViewGroup that manages the layout for the child views, which the user can swipe between.&lt;/li&gt;&lt;li&gt;&lt;b&gt;PagerAdapter&lt;/b&gt;: An adapter that populates the ViewPager with the views that represent each page.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Android Compatibility Package goes even farther to provide support for Fragments!  (Hey that was what I wanted to do next too!)&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;FragmentPagerAdapter&lt;/b&gt;: An extension of PagerAdapter for flipping between fragments.&lt;/li&gt;&lt;li&gt;&lt;b&gt;FragmentStatePagerAdapter&lt;/b&gt;: An extension of PagerAdapter for flipping between fragments that uses the library's support for &lt;a href="http://developer.android.com/reference/android/app/Fragment.SavedState.html"&gt;Fragment.SavedState&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Be sure to check out the sample code &lt;a href="http://developer.android.com/resources/samples/Support4Demos/index.html"&gt;here&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Why &lt;a href="http://www.google.com/"&gt;Google&lt;/a&gt; chose to make these APIs available only in the support library puzzles me.  Nevertheless, I'm thankful that they are making the source code available to all.&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=gPUEfxyFs6A:ayKM023jhz8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=gPUEfxyFs6A:ayKM023jhz8:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=gPUEfxyFs6A:ayKM023jhz8:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=gPUEfxyFs6A:ayKM023jhz8:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/gPUEfxyFs6A" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/4881219225368150253/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=4881219225368150253" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/4881219225368150253?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/4881219225368150253?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/gPUEfxyFs6A/horizontal-viewfragment-paging-in.html" title="Horizontal View/Fragment Paging in the Android Compatibility Package" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/07/horizontal-viewfragment-paging-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE4GSHw5fSp7ImA9WhdSEUU.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-8410559931205656406</id><published>2011-07-21T01:31:00.008+08:00</published><updated>2011-07-21T02:35:29.225+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-21T02:35:29.225+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="io2011" /><category scheme="http://www.blogger.com/atom/ns#" term="floss" /><category scheme="http://www.blogger.com/atom/ns#" term="coscup" /><category scheme="http://www.blogger.com/atom/ns#" term="opensource" /><category scheme="http://www.blogger.com/atom/ns#" term="iosched" /><category scheme="http://www.blogger.com/atom/ns#" term="coscup2011" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>COSCUP2011: Summary of Enhancements to the Google I/O 2011 App</title><content type="html">For some weird reason I feel compelled to summarize what I have added to and/or modified in the original &lt;a href="http://code.google.com/p/iosched/"&gt;iosched&lt;/a&gt; project to arrive at my current &lt;a href="http://code.wu-man.com/coscup2011"&gt;COSCUP2011&lt;/a&gt; project.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Parsing&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The parser in the original iosched project dealt with data that came in the form of XML spreadsheets.  The &lt;a href="http://coscup.org/2011/zh-tw/api/"&gt;COSCUP API&lt;/a&gt;, however, came in the form of JSON, which are themselves transcoded directly from &lt;a href="http://spreadsheets.google.com/"&gt;Google Spreadsheets&lt;/a&gt;.  It is then obvious that most of my work is to rewrite the parsers to handle the different format.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Sync&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The spreadsheets for the iosched project have a sync hash/timestamp for each row/record.  This makes the app much more efficient during its periodic sync.  It is able to decide which spreadsheets and also which records within those spreadsheets should be reparsed and used to replace old data stored in the local cache.  However, the COSCUP API does not offer such convenience.  So I had to rewrite this part of the app to make sure each sync will keep the local cache consistent with remote data.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As a side effect of the less efficient sync, ANR (Application Not Responding) errors start to appear each time a sync is in progress.  This is caused by the fact that each operation on the local database (and there are many of them during each sync) will trigger a foreground &lt;a href="http://developer.android.com/reference/android/database/ContentObserver.html"&gt;ContentObserver&lt;/a&gt; change.  This wasn't noticeable in the iosched project because the sync was often much shorter.  To remedy the problem, I made changes in the provider to &lt;a href="http://developer.android.com/reference/android/content/ContentResolver.html#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)"&gt;notifyChange&lt;/a&gt; only in the last operation of a sync.  This took care of the ANR problem, which would otherwise be quite annoying to the user.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I also fixed a bug in the initial launch of the app.  The iosched project invoked triggerRefresh() at the end of each onCreate() call of HomeActivity, which could be premature at times.  The DetachableResultReceiver in the SyncStatusUpdaterFragment may not have been instantiated when the triggerRefresh() call is made.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;One last change I made was to disable the refresh button when a sync is already in progress.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Schedule conflicts&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The iosched project could not handle time block conflicts properly.  This is perhaps best illustrated with the following picture.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://1.bp.blogspot.com/-0XGAdpaymkc/TicZ8RyCSfI/AAAAAAAAXbs/eMo7lni0N8s/s1600/iosched-schedule-conflict.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://1.bp.blogspot.com/-0XGAdpaymkc/TicZ8RyCSfI/AAAAAAAAXbs/eMo7lni0N8s/s320/iosched-schedule-conflict.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5631498382406207986" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 192px; height: 320px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Notice that the two conflicted breakout sessions blocks in the 5pm-6pm time slot overlap.  If they had the same end times, the block drawn in the bottom can never be clicked.  So I rewrote this part of the code to take care of conflicted blocks and the end result can be seen in the 3:30pm-4:30pm slot:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://3.bp.blogspot.com/-EbxoeTEIFUE/Tica09DK7YI/AAAAAAAAXb0/YKI-KlSqGIw/s1600/coscup-schedule-conflict.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://3.bp.blogspot.com/-EbxoeTEIFUE/Tica09DK7YI/AAAAAAAAXb0/YKI-KlSqGIw/s320/coscup-schedule-conflict.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5631499356093476226" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 192px; height: 320px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Sponsors&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Instead of showing sandbox vendors, I rewrote this part of the app to support a listing of conference sponsors.  These sponsors are organized in different levels according to the amount of their sponsorship to the conference.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Vote for my app&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thanks to original author and Google engineer &lt;a href="http://jsharkey.org/"&gt;Jeff Sharkey&lt;/a&gt;'s great work, I was able to make these changes &lt;a href="http://code.wu-man.com/coscup2011/changesets"&gt;in 5 nights&lt;/a&gt; (averaging about 2 hours per night after work).  What I love most about the experience is that I witnessed first hand how an open source project could foster more interesting projects; in this case, it's most adequate that the forked project is to be used by participants in an &lt;a href="http://coscup.org/2011/en/"&gt;open source conference&lt;/a&gt;!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you like what I have done, please support by &lt;a href="https://registrano.com/events/mda/registrations/new"&gt;voting for my app here&lt;/a&gt; and &lt;a href="http://code.wu-man.com/coscup2011"&gt;contributing with your patches here&lt;/a&gt;.&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=rSJQFJ6um6M:XcEASoydl8I:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=rSJQFJ6um6M:XcEASoydl8I:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=rSJQFJ6um6M:XcEASoydl8I:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=rSJQFJ6um6M:XcEASoydl8I:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/rSJQFJ6um6M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/8410559931205656406/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=8410559931205656406" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/8410559931205656406?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/8410559931205656406?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/rSJQFJ6um6M/coscup2011-summary-of-enhancements-to.html" title="COSCUP2011: Summary of Enhancements to the Google I/O 2011 App" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-0XGAdpaymkc/TicZ8RyCSfI/AAAAAAAAXbs/eMo7lni0N8s/s72-c/iosched-schedule-conflict.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/07/coscup2011-summary-of-enhancements-to.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UMQHkzfyp7ImA9WhdSEE0.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-2789363878918221653</id><published>2011-07-19T00:32:00.008+08:00</published><updated>2011-07-19T01:14:41.787+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-19T01:14:41.787+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="googleio" /><category scheme="http://www.blogger.com/atom/ns#" term="gtug" /><category scheme="http://www.blogger.com/atom/ns#" term="io2011" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Quick Sharing of Google I/O Sessions at Taipei GTUG</title><content type="html">A few weeks ago the &lt;a href="http://www.taipei-gtug.org/"&gt;Google Technology User Group in Taipei&lt;/a&gt; held a gathering for all the Google I/O fans who could not make it to San Francisco.  Speakers who signed up were asked to condense an hour-long Google I/O 2011 session of their choice into a 10-minute sharing.  Yes I agree with you that participants of this gathering are all pretty lazy. :p&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anyway I signed up to share two particular sessions:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Android Protips: Advanced Topics for Expert Android App Developers by &lt;a href="http://blog.radioactiveyak.com/"&gt;Reto Meier&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Memory Management for Android Apps by &lt;a href="http://dubroy.com/blog/"&gt;Patrick Dubroy&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;It was an interesting challenge for me coz condensing these sessions were almost impossible.  Nevertheless you can check out the slides &lt;a href="http://www.slideshare.net/wuman/extracts-from-android-protips-advanced-topics-for-expert-android-app-developers"&gt;here&lt;/a&gt; and &lt;a href="http://www.slideshare.net/wuman/extracts-from-memory-management-for-android-apps"&gt;here&lt;/a&gt;.  Video recordings of my sharings can be found &lt;a href="http://www.youtube.com/watch?v=J030Gfruw0Y"&gt;here&lt;/a&gt; and &lt;a href="http://www.youtube.com/watch?v=v31KHAc5K0k"&gt;here&lt;/a&gt; on YouTube.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Oh and I received this Chrome cartoon book as a token of appreciation.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://2.bp.blogspot.com/-8xUovunn13c/TiRp0ASnUlI/AAAAAAAAXaY/nTJyg-24XDg/s1600/IMG_20110719_010747.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://2.bp.blogspot.com/-8xUovunn13c/TiRp0ASnUlI/AAAAAAAAXaY/nTJyg-24XDg/s320/IMG_20110719_010747.jpg" border="0" alt="" id="BLOGGER_PHOTO_ID_5630741776272151122" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 240px; height: 320px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://4.bp.blogspot.com/--JMs8K84m48/TiRp6BwVKjI/AAAAAAAAXag/XldR-GU0OsM/s1600/IMG_20110719_010807.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://4.bp.blogspot.com/--JMs8K84m48/TiRp6BwVKjI/AAAAAAAAXag/XldR-GU0OsM/s320/IMG_20110719_010807.jpg" border="0" alt="" id="BLOGGER_PHOTO_ID_5630741879744440882" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 240px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=W8ixhQBi8W4:d_hjcfq12hA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=W8ixhQBi8W4:d_hjcfq12hA:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=W8ixhQBi8W4:d_hjcfq12hA:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=W8ixhQBi8W4:d_hjcfq12hA:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/W8ixhQBi8W4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/2789363878918221653/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=2789363878918221653" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/2789363878918221653?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/2789363878918221653?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/W8ixhQBi8W4/quick-sharing-of-google-io-sessions-at.html" title="Quick Sharing of Google I/O Sessions at Taipei GTUG" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-8xUovunn13c/TiRp0ASnUlI/AAAAAAAAXaY/nTJyg-24XDg/s72-c/IMG_20110719_010747.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/07/quick-sharing-of-google-io-sessions-at.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUADQn07eip7ImA9WhdSEUU.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-5731182436545487405</id><published>2011-07-18T23:01:00.029+08:00</published><updated>2011-07-21T02:49:33.302+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-21T02:49:33.302+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="contest" /><category scheme="http://www.blogger.com/atom/ns#" term="tablet" /><category scheme="http://www.blogger.com/atom/ns#" term="floss" /><category scheme="http://www.blogger.com/atom/ns#" term="coscup" /><category scheme="http://www.blogger.com/atom/ns#" term="opensource" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Mobile Device App Contest for COSCUP 2011</title><content type="html">&lt;a href="http://2.bp.blogspot.com/-OqbZ1xJzUZo/TiROMJ53g1I/AAAAAAAAXYE/vL6PQQ4amn4/s1600/phone-home.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 192px; height: 320px;" src="http://2.bp.blogspot.com/-OqbZ1xJzUZo/TiROMJ53g1I/AAAAAAAAXYE/vL6PQQ4amn4/s320/phone-home.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630711404843991890" /&gt;&lt;/a&gt;&lt;a href="http://coscup.org/2011/en/"&gt;COSCUP&lt;/a&gt; 是開放原碼社群中一年一度的大拜拜，每年都有來自各地的英雄好漢搶著報名。在今年的活動中，最大的樂趣除了認識朋友&lt;s&gt;&lt;a href="http://www.plurk.com/p/d5i9ye"&gt;和旁觀報名網站被DDoS搞掛&lt;/a&gt;&lt;/s&gt;外，就是主辦單位第一次嘗試提供議程的&lt;a href="http://coscup.org/2011/zh-tw/api/"&gt;API&lt;/a&gt;，並舉辦了一場各大行動平台上的&lt;a href="http://blog.coscup.org/2011/06/coscup-2011-mobile-device-app.html"&gt;應用程式競賽&lt;/a&gt;。&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;由於我平常的工作就是Android程式開發，再加上已經有一個很棒的&lt;a href="http://code.google.com/p/iosched/"&gt;開放原碼專案&lt;/a&gt;可以提供分支，讓我很快把程式雛型寫好，所以自然地就參加了。主辦單位希望參賽者寫一份圖文說明的文章，所以我只好寫這篇&lt;s&gt;交差了事&lt;/s&gt;囉～&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;懶得讀完整篇介紹文章的朋友可以直接到 &lt;a href="https://market.android.com/details?id=com.wuman.coscup2011"&gt;Android Market&lt;/a&gt; 上下載。此外，這個應用程式是完全開放原始碼的，讀者可以自行到&lt;a href="http://code.wu-man.com/coscup2011"&gt;這個網頁&lt;/a&gt;去參與此專案的開發。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;b&gt;首頁簡介&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;首頁上提供了六個選項，讓使用者可以選擇不同方式瀏覽主辦單位發布的官方訊息。左下角提供了即時倒數，讓使用者在活動開始前可以一起興奮地參與倒數。活動期間，這個區塊會改為顯示當下正在進行的議程，並提供快速連結方便使用者瀏覽。&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;此外，右下角的 Realtime Stream 選項，會展示出噗浪上所有提到 #coscup 這個關鍵字的討論串，方便使用者掌握最即時的討論。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://1.bp.blogspot.com/-SvU_tiP61ZQ/TiRP-SKZIpI/AAAAAAAAXYM/U19kq3KmJbA/s1600/phone-tagstream.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://1.bp.blogspot.com/-SvU_tiP61ZQ/TiRP-SKZIpI/AAAAAAAAXYM/U19kq3KmJbA/s320/phone-tagstream.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630713365565874834" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 192px; height: 320px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;b&gt;議程瀏覽&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;議程主要可由兩種不同方式瀏覽：Schedule 和 Sessions。Schedule 選項以時間排序，並用日曆方式顯示每個時段有哪些講題。點選某個時段區塊後，就會跳出該區段的所有講題，再進而查詢某講題的詳細資訊。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://2.bp.blogspot.com/-PKJu5stIB6c/TiRTf28w8-I/AAAAAAAAXYc/s81bGwnNAZs/s1600/phone-schedule-steps.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://2.bp.blogspot.com/-PKJu5stIB6c/TiRTf28w8-I/AAAAAAAAXYc/s81bGwnNAZs/s320/phone-schedule-steps.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630717240911393762" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 152px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;另一種查詢議程的方式是 Sessions，也就是 by tracks。依據議程主題與類型，將講題分門別類列出。使用者點選某一軌主題後，就會詳列出該主題的所有議題。這對只有單一主題興趣的使用者來說應該是較為方便的瀏覽方式。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://2.bp.blogspot.com/-15-n7pzuYyY/TiRVVHi3mUI/AAAAAAAAXYk/Yy45PLqd8tM/s1600/phone-tracks-steps.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://2.bp.blogspot.com/-15-n7pzuYyY/TiRVVHi3mUI/AAAAAAAAXYk/Yy45PLqd8tM/s320/phone-tracks-steps.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630719255410874690" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 148px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"&gt;議程標記&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;通常我們在參加研討會時，總會有幾個講題是我們極為確認一定要參加的。也有另一種情況是我們在同一時段有兩個講題都想參加，但還沒辦法決定到底要參加哪一個講題，想要晚點再來決定。不管是哪一種情況，如果能把這些講題都先標記起來，就可以方便之後快速瀏覽。所以這個應用程式也提供了標記的功能，方便使用者快速回顧所有曾被標記星號的議題。&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://2.bp.blogspot.com/-_Lh2KaK_TSQ/TiRYmxOU4CI/AAAAAAAAXYs/xRUx6r2RwPY/s1600/phone-starred.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://2.bp.blogspot.com/-_Lh2KaK_TSQ/TiRYmxOU4CI/AAAAAAAAXYs/xRUx6r2RwPY/s320/phone-starred.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630722857191661602" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 192px; height: 320px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;b&gt;搜尋議程&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;有時候我們會想快速搜尋和某個關鍵字相關的所有議程。例如，或許我們會突然想知道今年 COSCUP 的鑽石贊助商之一的 HTC 有些什麼講題，就可以按下搜尋按鈕，然後輸入 HTC，就可以查詢到所有和 HTC 這個關鍵字有相關的講題。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://1.bp.blogspot.com/-RF3F626m9Hw/TiRaBbzRl6I/AAAAAAAAXY8/xUZCXX4Unss/s1600/phone-search-steps.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://1.bp.blogspot.com/-RF3F626m9Hw/TiRaBbzRl6I/AAAAAAAAXY8/xUZCXX4Unss/s320/phone-search-steps.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630724414809151394" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 151px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"&gt;贊助商瀏覽&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;COSCUP 是免費的活動，因此每年都需要廠商的大力贊助。使用者可以依照贊助層級來瀏覽每個贊助廠商的官方資訊。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://4.bp.blogspot.com/-5UYTUafN0d4/TiRar3xq4pI/AAAAAAAAXZE/ikZurJd-7_o/s1600/phone-sponsors.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://4.bp.blogspot.com/-5UYTUafN0d4/TiRar3xq4pI/AAAAAAAAXZE/ikZurJd-7_o/s320/phone-sponsors.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630725143873118866" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 192px; height: 320px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://3.bp.blogspot.com/-9MhDN_vTVak/TiRawkBmILI/AAAAAAAAXZM/1gD8FJ_42TM/s1600/phone-sponsor-0xlab.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://3.bp.blogspot.com/-9MhDN_vTVak/TiRawkBmILI/AAAAAAAAXZM/1gD8FJ_42TM/s320/phone-sponsor-0xlab.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630725224470552754" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 192px; height: 320px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"&gt;官方部落格公告&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Bulletin 這個選項可以方便使用者瀏覽官方部落格的文章，若議程進行間主辦單位有新的訊息發布，這就是很好得知的一個管道。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://1.bp.blogspot.com/-Fm65DsXMfWs/TiRZy6-7n-I/AAAAAAAAXY0/hV1sPWm5eFc/s1600/phone-bulletin.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://1.bp.blogspot.com/-Fm65DsXMfWs/TiRZy6-7n-I/AAAAAAAAXY0/hV1sPWm5eFc/s320/phone-bulletin.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630724165481504738" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 192px; height: 320px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"&gt;大尺寸平板支援&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;最後，就是大尺寸平板的支援囉。附上一些擷圖給沒有平板的使用者看看囉～&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://3.bp.blogspot.com/-eGxX-BQ9a7c/TiRcEFHMWBI/AAAAAAAAXZU/_laNifVLD4Q/s1600/tablet-home.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://3.bp.blogspot.com/-eGxX-BQ9a7c/TiRcEFHMWBI/AAAAAAAAXZU/_laNifVLD4Q/s320/tablet-home.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630726659281541138" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 200px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://3.bp.blogspot.com/-aypyb_MqDGs/TiRcI1O9ZPI/AAAAAAAAXZc/deMs7Y8HilI/s1600/tablet-schedule.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://3.bp.blogspot.com/-aypyb_MqDGs/TiRcI1O9ZPI/AAAAAAAAXZc/deMs7Y8HilI/s320/tablet-schedule.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630726740918494450" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 200px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://3.bp.blogspot.com/-lvVCQSHqVdc/TiRcMqkS9cI/AAAAAAAAXZk/GkJAZXwKZ7s/s1600/tablet-sessions.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://3.bp.blogspot.com/-lvVCQSHqVdc/TiRcMqkS9cI/AAAAAAAAXZk/GkJAZXwKZ7s/s320/tablet-sessions.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630726806774674882" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 200px; " /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://1.bp.blogspot.com/-vQ9z910ITzI/TiRcPn7sYUI/AAAAAAAAXZs/a448LtIAKAI/s1600/tablet-sponsors.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://1.bp.blogspot.com/-vQ9z910ITzI/TiRcPn7sYUI/AAAAAAAAXZs/a448LtIAKAI/s320/tablet-sponsors.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5630726857607110978" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 200px; " /&gt;&lt;/a&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"&gt;地圖功能&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;最後沒有介紹的地圖功能，因為我的 Photoshop 功力沒有很好，所以需要一點時間製作。後續會再補上這個功能的，請各位大大見諒。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"&gt;結語&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;雖然工作很繁忙，可是心裡還是有不少想對這個應用程式增加的功能。活動前希望還能利用一些空閒的時間來做進去！有興趣的大大可以先到 &lt;a href="https://market.android.com/details?id=com.wuman.coscup2011"&gt;Android Market&lt;/a&gt; 上下載。所有的程式碼都開放在這個&lt;a href="http://code.wu-man.com/coscup2011"&gt;空間&lt;/a&gt;。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;UPDATE [7/21]:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;我把我的貢獻的部份整理在&lt;a href="http://blog.wu-man.com/2011/07/coscup2011-summary-of-enhancements-to.html"&gt;這篇新的文章&lt;/a&gt;中。&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=keFzMzL_MEM:2f9dFJToD7w:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=keFzMzL_MEM:2f9dFJToD7w:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=keFzMzL_MEM:2f9dFJToD7w:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=keFzMzL_MEM:2f9dFJToD7w:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/keFzMzL_MEM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/5731182436545487405/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=5731182436545487405" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/5731182436545487405?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/5731182436545487405?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/keFzMzL_MEM/mobile-device-app-contest-for-coscup.html" title="Mobile Device App Contest for COSCUP 2011" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-OqbZ1xJzUZo/TiROMJ53g1I/AAAAAAAAXYE/vL6PQQ4amn4/s72-c/phone-home.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/07/mobile-device-app-contest-for-coscup.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUQGQnk_eCp7ImA9WhZbE04.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-5337927052077733785</id><published>2011-06-18T01:36:00.012+08:00</published><updated>2011-06-18T03:02:03.740+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-18T03:02:03.740+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="fastboot" /><category scheme="http://www.blogger.com/atom/ns#" term="galaxy" /><category scheme="http://www.blogger.com/atom/ns#" term="tablet" /><category scheme="http://www.blogger.com/atom/ns#" term="samsung" /><category scheme="http://www.blogger.com/atom/ns#" term="bootloader" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Secret Modes in Samsung Galaxy Tab 10.1 Limited I/O Edition</title><content type="html">I have a Samsung Galaxy Tab 10.1 Limited Edition, which was given to me and 5500 other attendees of Google I/O 2011.  I was playing with it today and found out the key sequences to get into some of the secret modes, though I have no idea what some of these modes are for.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Safe Mode&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Press the Power button to start the device as normal.&lt;/li&gt;&lt;li&gt;As soon as you see the Samsung Galaxy Tab 10.1 logo during boot up, press and hold the volume down (left) button.&lt;/li&gt;&lt;li&gt;After the Samsung logo comes up, you should feel a vibrate.  You can then release all the keys and wait for the boot to complete.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;a href="http://2.bp.blogspot.com/-1OhWkhuYIuA/TfuStXGflYI/AAAAAAAAXHg/dYFs-lyZTUo/s1600/safemode_lockscreen.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/-1OhWkhuYIuA/TfuStXGflYI/AAAAAAAAXHg/dYFs-lyZTUo/s320/safemode_lockscreen.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5619246268067845506" /&gt;&lt;/a&gt;&lt;div&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-h1-X-G-iXo4/TfuTqPFLiyI/AAAAAAAAXHw/PJXMRS64RWA/s1600/safemode_apps.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://1.bp.blogspot.com/-h1-X-G-iXo4/TfuTqPFLiyI/AAAAAAAAXHw/PJXMRS64RWA/s320/safemode_apps.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5619247313886874402" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-ZskuIa-zL3M/TfuTgSCXINI/AAAAAAAAXHo/5GAPXNM-NbY/s1600/safemode_homescreen.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/-ZskuIa-zL3M/TfuTgSCXINI/AAAAAAAAXHo/5GAPXNM-NbY/s320/safemode_homescreen.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5619247142881665234" /&gt;&lt;/a&gt;&lt;br /&gt;As you can see, you should see the words "Safe mode" on the bottom left of the screen.  The only thing that's different from this mode to normal start up is that all the apps that I downloaded from the Android Market are gone.  The launcher will show only the apps that came bundled with the device.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Upload Mode&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Press and hold the power button to boot the device.&lt;/li&gt;&lt;li&gt;As soon as you see the Samsung Galaxy Tab 10.1 logo, press and hold the volume down button (along with the power button).&lt;/li&gt;&lt;li&gt;As soon as the logo disappears, release all the buttons.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;You should then see the following on the top left corner of the screen, indicating that the device is in Upload mode, which I have no idea what it is for.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;a href="http://3.bp.blogspot.com/-QA4aMIjXOlE/TfuibGBtr5I/AAAAAAAAXH4/CplXEiCDERM/s1600/IMG_3551.JPG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://3.bp.blogspot.com/-QA4aMIjXOlE/TfuibGBtr5I/AAAAAAAAXH4/CplXEiCDERM/s320/IMG_3551.JPG" border="0" alt="" id="BLOGGER_PHOTO_ID_5619263546432794514" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div&gt;&lt;b&gt;Bootloader (Fastboot Mode and ODIN3 Download Mode)&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Press and hold the volume down key.&lt;/li&gt;&lt;li&gt;Press and hold the power button.&lt;/li&gt;&lt;li&gt;The device will then boot into the following screen.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;a href="http://1.bp.blogspot.com/-OqN-YD4E8Og/Tfui90wcQtI/AAAAAAAAXIA/7wQ7belHmCk/s1600/IMG_3550.JPG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://1.bp.blogspot.com/-OqN-YD4E8Og/Tfui90wcQtI/AAAAAAAAXIA/7wQ7belHmCk/s320/IMG_3550.JPG" border="0" alt="" id="BLOGGER_PHOTO_ID_5619264143092368082" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div&gt;As you can see there are two options for you to choose from.  You use the volume down (left) key to change your selection and the volume up (right) key to confirm your selection.  The USB icon on the left will make you go into Fastboot mode.  The Downloading icon on the right will make you go into ODIN3 Download mode.  Now Fastboot mode is obvious; it allows you to do all your fastboot commands if the bootloader is unlocked.  I don't know what the Download mode does though.&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=KQRgpqBtUS8:NBUH8dIp9EQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=KQRgpqBtUS8:NBUH8dIp9EQ:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=KQRgpqBtUS8:NBUH8dIp9EQ:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=KQRgpqBtUS8:NBUH8dIp9EQ:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/KQRgpqBtUS8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/5337927052077733785/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=5337927052077733785" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/5337927052077733785?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/5337927052077733785?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/KQRgpqBtUS8/secret-modes-in-samsung-galaxy-tab-101.html" title="Secret Modes in Samsung Galaxy Tab 10.1 Limited I/O Edition" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-1OhWkhuYIuA/TfuStXGflYI/AAAAAAAAXHg/dYFs-lyZTUo/s72-c/safemode_lockscreen.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/06/secret-modes-in-samsung-galaxy-tab-101.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkUGSXs6eip7ImA9WhZaEk0.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-6877124119032647251</id><published>2011-06-16T16:39:00.010+08:00</published><updated>2011-06-28T02:43:48.512+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-28T02:43:48.512+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="お父さん扇風機" /><category scheme="http://www.blogger.com/atom/ns#" term="dogfan" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Super Cute DogFan (お父さん扇風機) Ported on Android</title><content type="html">&lt;a href="http://1.bp.blogspot.com/-PqMEJJEyKEI/TfnEFocXJLI/AAAAAAAAXHI/0wIqeYW8SmE/s1600/dogfan_screenshot.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 192px; height: 320px;" src="http://1.bp.blogspot.com/-PqMEJJEyKEI/TfnEFocXJLI/AAAAAAAAXHI/0wIqeYW8SmE/s320/dogfan_screenshot.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5618737611156169906" /&gt;&lt;/a&gt;&lt;br /&gt;Yesterday I saw in &lt;a href="http://www.plurk.com/vgod"&gt;@vgod&lt;/a&gt;'s &lt;a href="http://www.plurk.com/p/cod8b0"&gt;plurk&lt;/a&gt; this &lt;a href="http://dogfan.heroku.com/index.html"&gt;cute HTML5 page&lt;/a&gt;.  It's a hilarious (apparently also very popular in Japan) dog fan that was released by SoftBank.  You can check out the original &lt;a href="http://www.youtube.com/watch?v=j_6qn9FDQno"&gt;CM&lt;/a&gt; on YouTube and its &lt;a href="http://ja.wikipedia.org/wiki/%E3%82%AB%E3%82%A4%E3%81%8F%E3%82%93"&gt;Wikipedia article&lt;/a&gt; to learn more.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I thought it was kind of cute and decided to port &lt;a href="http://www.twitter.com/vincicat"&gt;@vincicat&lt;/a&gt;'s HTML5/CSS/JS version to Android.  I will refine and add more features in later releases when I have more free time.  For now you can check out the source code on my &lt;a href="https://github.com/wuman/DogFan"&gt;GitHub&lt;/a&gt; or download from the &lt;a href="https://market.android.com/details?id=com.wuman.dogfan"&gt;Android Market&lt;/a&gt;.   Be warned though that I only tested with my Nexus One.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;[Update 2002/06/28]&lt;/b&gt;  I just &lt;a href="https://market.android.com/details?id=com.wuman.dogfan"&gt;updated the app&lt;/a&gt; to support sound effects.  You can finally hear the infamous "ahahah~" noise when you turn the fan on.&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=-uBBILknNW8:fzAszhaw0Ys:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=-uBBILknNW8:fzAszhaw0Ys:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=-uBBILknNW8:fzAszhaw0Ys:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=-uBBILknNW8:fzAszhaw0Ys:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/-uBBILknNW8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/6877124119032647251/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=6877124119032647251" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/6877124119032647251?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/6877124119032647251?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/-uBBILknNW8/super-cute-dogfan-ported-on-android.html" title="Super Cute DogFan (お父さん扇風機) Ported on Android" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-PqMEJJEyKEI/TfnEFocXJLI/AAAAAAAAXHI/0wIqeYW8SmE/s72-c/dogfan_screenshot.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/06/super-cute-dogfan-ported-on-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak4MRXg6fip7ImA9WhZbEUQ.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-8713346441802346905</id><published>2011-06-16T12:02:00.006+08:00</published><updated>2011-06-16T12:36:24.616+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-16T12:36:24.616+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="canucks" /><category scheme="http://www.blogger.com/atom/ns#" term="vancouver" /><title>Thank You Vancouver Canucks</title><content type="html">So the Stanley Cup final has ended and the &lt;a href="http://canucks.nhl.com/"&gt;Vancouver Canucks&lt;/a&gt; has lost Game 7.  Many Canucks fans are upset about the final outcome, and I'm one of them.  This blog post, however, is not about that.  I wanted to spend some time to talk about how proud I am to be a Canucks fan.&lt;br /&gt;&lt;br /&gt;The Vancouver Canucks has inspired me in many ways.  Unlike many bandwagoners, I have actually been watching their games since day 1.  I witnessed how this team has worked hard to get to where they are now.  There have been ups and downs along this long journey (which will definitely be marked down in history and remembered by all), but it is their persistence to fight for their dream that has really brought the team together and made them grow.  This fighting spirit has encouraged me to overcome my own setbacks on numerous occasions.&lt;br /&gt;&lt;br /&gt;Another thing that makes me proud as a Canucks fan is that the team has really brought the city (and &lt;a href="http://www.timescolonist.com/news/canada-in-afghanistan/Canucks+Canadian+troops+bring+playoffs+spirit+Afghanistan/4896894/story.html"&gt;perhaps the nation&lt;/a&gt;) together.  Enormous crowds gather both inside and outside of the arena to cheer for our favorite team.  At a &lt;a href="http://brassmonkeytaipei.com/"&gt;local sports bar in Taipei&lt;/a&gt; (where I live), many expats gather for the final game and &lt;a href="http://www.youtube.com/watch?v=0ueCZHi0m7Q"&gt;sing the opening anthem together&lt;/a&gt;.  If you were there you would be moved by it too.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So there you go.  Thank you Canucks for a great season.  You guys didn't win the cup, but you surely accomplished lots.  I will continue to support and proudly shout "Go Canucks Go"!&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=UeohX_KezIA:PJXA5TI_e-A:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=UeohX_KezIA:PJXA5TI_e-A:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=UeohX_KezIA:PJXA5TI_e-A:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=UeohX_KezIA:PJXA5TI_e-A:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/UeohX_KezIA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/8713346441802346905/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=8713346441802346905" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/8713346441802346905?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/8713346441802346905?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/UeohX_KezIA/thank-you-vancouver-canucks.html" title="Thank You Vancouver Canucks" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/06/thank-you-vancouver-canucks.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMASXs6fSp7ImA9WhJUEkg.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-2853968735867678971</id><published>2011-06-03T00:18:00.012+08:00</published><updated>2012-09-10T13:04:08.515+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-10T13:04:08.515+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="layout" /><category scheme="http://www.blogger.com/atom/ns#" term="launcher" /><category scheme="http://www.blogger.com/atom/ns#" term="feedly" /><category scheme="http://www.blogger.com/atom/ns#" term="honeycomb" /><category scheme="http://www.blogger.com/atom/ns#" term="widget" /><category scheme="http://www.blogger.com/atom/ns#" term="tweetdeck" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><category scheme="http://www.blogger.com/atom/ns#" term="workspace" /><title>Efficient Tweetdeck-styled Workspace using AdapterViewAnimator</title><content type="html">If you have ever used &lt;a href="https://market.android.com/details?id=com.thedeck.android.app"&gt;TweetDeck&lt;/a&gt;, you would grow to love its horizontal pager.  It's actually a layout that is found common in many applications such as &lt;a href="https://market.android.com/details?id=com.devhd.feedly"&gt;feedly&lt;/a&gt;, &lt;a href="https://market.android.com/details?id=com.google.android.youtube"&gt;YouTube&lt;/a&gt;, and &lt;a href="http://www.androidguys.com/2010/02/16/android-21-news-weather-app-ported-android-15/"&gt;News &amp;amp; Weather&lt;/a&gt;, just to name a few.  In fact, the stock Android launcher's home screen works the exact same way.  It allows the user to horizontally swipe across the display to quickly switch in between screens.  The layout in use there is called &lt;span class="Apple-style-span"&gt;Workspace&lt;/span&gt; and the source code can be found &lt;a href="http://www.java2s.com/Open-Source/Android/android-platform-apps/Launcher/com/android/launcher/Workspace.java.htm"&gt;here&lt;/a&gt;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;However, the Workspace layout actually has a fatal limitation.  It requires you to inflate and add all of the child views to the Workspace layout beforehand.  This is probably fine if you have only very limited number of child views.  If you need to work with more than a handful child views, particularly if your child views are themselves hierarchically complex layouts, you might run into performance and memory problems.  There are several overheads.  The inflation of all the child views could block the UI thread.  Secondly, all the child views are present in the Workspace hierarchy even though most of them are not drawn in the screen; that means you waste unnecessary tree traversals (and there are lots of them since you probably only need to traverse at most two branches of the tree at any given time).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I ran into this exact problem when I was working on a secret project, where I needed to provide the horizontal paging functionality to a fairly large number of complex child views.  With the introduction of Honeycomb, fortunately there is a new layout called &lt;a href="http://d.android.com/reference/android/widget/AdapterViewAnimator.html"&gt;AdapterViewAnimator&lt;/a&gt; that comes handy.  The concept is simple.  It extends the &lt;a href="http://d.android.com/reference/android/widget/AdapterView.html"&gt;AdapterView&lt;/a&gt; and allows you to switch (and animate) between views provided by an &lt;a href="http://d.android.com/reference/android/widget/Adapter.html"&gt;Adapter&lt;/a&gt;.  One can extend the AdapterViewAnimator to have a customized layout that is functionally equivalent to the Workspace layout.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That's exactly what I did.  A drawback is that my implementation needs to be placed in the same package namespace as the AdapterViewAnimator as some of the instance methods and fields are restricted to default access (which in my opinion is an extensibility hindrance found in many of the android.widget.* classes).  I suppose when the source code for honeycomb (which I heard will not happen) or ice cream sandwich comes out, you can copy out the AdapterViewAnimator class to your own package and extend it then.  But for now, here are my two cents. Btw, I call it AdapterWorkspace.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.wuman.widget;&lt;br /&gt;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.Map.Entry;&lt;br /&gt;&lt;br /&gt;import android.content.Context;&lt;br /&gt;import android.graphics.Canvas;&lt;br /&gt;import android.graphics.Rect;&lt;br /&gt;import android.util.AttributeSet;&lt;br /&gt;import android.view.MotionEvent;&lt;br /&gt;import android.view.VelocityTracker;&lt;br /&gt;import android.view.View;&lt;br /&gt;import android.view.ViewConfiguration;&lt;br /&gt;import android.widget.Scroller;&lt;br /&gt;&lt;br /&gt;public class AdapterWorkspace extends AdapterViewAnimator {&lt;br /&gt;&lt;br /&gt;    private static final int INVALID_CHILD = -1;&lt;br /&gt;    private static final int INVALID_POINTER = -1;&lt;br /&gt;&lt;br /&gt;    private boolean mFirstLayout = true;&lt;br /&gt;    private int mNextChild = INVALID_CHILD;&lt;br /&gt;    private Scroller mScroller;&lt;br /&gt;    private VelocityTracker mVelTracker;&lt;br /&gt;    private float mTouchX;&lt;br /&gt;    private float mLastMotionX;&lt;br /&gt;    private float mLastMotionY;&lt;br /&gt;    private int mMotionAction = MotionEvent.ACTION_DOWN;&lt;br /&gt;    private boolean mAllowLongPress = true;&lt;br /&gt;    private int mTouchSlop;&lt;br /&gt;    private int mLayoutTouchSlop;&lt;br /&gt;    private int mMaximumVelocity;&lt;br /&gt;    private int mActivePointerId = INVALID_POINTER;&lt;br /&gt;&lt;br /&gt;    public AdapterWorkspace(Context context) {&lt;br /&gt;        super(context);&lt;br /&gt;        initAdapterWorkspace();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public AdapterWorkspace(Context context, AttributeSet attrs) {&lt;br /&gt;        super(context, attrs);&lt;br /&gt;        initAdapterWorkspace();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private void initAdapterWorkspace() {&lt;br /&gt;        configureViewAnimator(3, 1);&lt;br /&gt;        setHapticFeedbackEnabled(false);&lt;br /&gt;        setHorizontalFadingEdgeEnabled(false);&lt;br /&gt;        mScroller = new Scroller(getContext());&lt;br /&gt;        final ViewConfiguration vc = ViewConfiguration.get(getContext());&lt;br /&gt;        mTouchSlop = vc.getScaledTouchSlop();&lt;br /&gt;        mMaximumVelocity = vc.getScaledMaximumFlingVelocity();&lt;br /&gt;        // min slop to switch touch handling owner to layout itself&lt;br /&gt;        mLayoutTouchSlop = 50;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void addFocusables(ArrayList&amp;lt;View&gt; views, int direction,&lt;br /&gt;        int focusableMode) {&lt;br /&gt;        getCurrentView().addFocusables(views, direction);&lt;br /&gt;        if ( direction == FOCUS_LEFT ) {&lt;br /&gt;            if ( mWhichChild &gt; 0 ) {&lt;br /&gt;                getViewAtRelativeIndex(mActiveOffset - 1).addFocusables(views,&lt;br /&gt;                    direction);&lt;br /&gt;            }&lt;br /&gt;        } else if ( direction == FOCUS_RIGHT ) {&lt;br /&gt;            if ( mWhichChild &lt; getWindowSize() - 1 ) {&lt;br /&gt;                getViewAtRelativeIndex(mActiveOffset + 1).addFocusables(views,&lt;br /&gt;                    direction);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean allowLongPress() {&lt;br /&gt;        return mAllowLongPress;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    void transformViewForTransition(int fromIndex, int toIndex, View view,&lt;br /&gt;        boolean animate) {&lt;br /&gt;        // Do not animate as we are doing horizontal scrolls&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    void showOnly(int childIndex, boolean animate) {&lt;br /&gt;        super.showOnly(childIndex, animate);&lt;br /&gt;&lt;br /&gt;        mNextChild = Math.max(0, Math.min(childIndex, getWindowSize() - 1));&lt;br /&gt;        int screenDelta = Math.abs(mNextChild - mWhichChild);&lt;br /&gt;&lt;br /&gt;        View focusedChild = getFocusedChild();&lt;br /&gt;        if ( focusedChild != null &amp;&amp; screenDelta != 0&lt;br /&gt;            &amp;&amp; focusedChild == getCurrentView() ) {&lt;br /&gt;            focusedChild.clearFocus();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        if ( mViewsMap.containsKey(mNextChild) ) {&lt;br /&gt;            final int nextRelativeIndex = mNextChild - mCurrentWindowStart;&lt;br /&gt;&lt;br /&gt;            int scrollOffset = nextRelativeIndex * getMeasuredWidth()&lt;br /&gt;                - getScrollX();&lt;br /&gt;            awakenScrollBars(screenDelta * 300);&lt;br /&gt;            int duration;&lt;br /&gt;            if ( screenDelta == 0 ) {&lt;br /&gt;                duration = Math.abs(scrollOffset);&lt;br /&gt;            } else {&lt;br /&gt;                duration = screenDelta * 300;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            if ( !mScroller.isFinished() ) {&lt;br /&gt;                mScroller.abortAnimation();&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            mScroller.startScroll(getScrollX(), 0, scrollOffset, 0, duration);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        invalidate();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void computeScroll() {&lt;br /&gt;        if ( mScroller.computeScrollOffset() ) {&lt;br /&gt;            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());&lt;br /&gt;            postInvalidate();&lt;br /&gt;        } else if ( mNextChild != INVALID_CHILD ) {&lt;br /&gt;            mWhichChild = modulo(mNextChild, getWindowSize());&lt;br /&gt;            mNextChild = INVALID_CHILD;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void setDisplayedChild(int whichChild) {&lt;br /&gt;        if ( !mScroller.isFinished() ) {&lt;br /&gt;            mScroller.abortAnimation();&lt;br /&gt;        }&lt;br /&gt;        super.setDisplayedChild(whichChild);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {&lt;br /&gt;        super.onMeasure(widthMeasureSpec, heightMeasureSpec);&lt;br /&gt;&lt;br /&gt;        if ( mFirstLayout ) {&lt;br /&gt;            setHorizontalScrollBarEnabled(false);&lt;br /&gt;            int widthOffset = getMeasuredWidth() * mActiveOffset;&lt;br /&gt;            scrollTo(widthOffset, 0);&lt;br /&gt;            setHorizontalScrollBarEnabled(true);&lt;br /&gt;            mFirstLayout = false;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    protected void onSizeChanged(int w, int h, int oldw, int oldh) {&lt;br /&gt;        super.onSizeChanged(w, h, oldw, oldh);&lt;br /&gt;        setDisplayedChild(mWhichChild);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    protected void onLayout(boolean changed, int left, int top, int right,&lt;br /&gt;        int bottom) {&lt;br /&gt;        checkForAndHandleDataChanged();&lt;br /&gt;&lt;br /&gt;        final int currentWindowSize = mCurrentWindowEnd - mCurrentWindowStart&lt;br /&gt;            + 1;&lt;br /&gt;&lt;br /&gt;        for ( int i = 0; i &lt; currentWindowSize; i++ ) {&lt;br /&gt;            final int rangeIndex = modulo(mCurrentWindowStart + i,&lt;br /&gt;                getWindowSize());&lt;br /&gt;            final int relativeIndex = mCurrentWindowStart + i&lt;br /&gt;                - mCurrentWindowStartUnbounded;&lt;br /&gt;&lt;br /&gt;            if ( mViewsMap.containsKey(rangeIndex) &amp;&amp; relativeIndex &gt;= 0&lt;br /&gt;                &amp;&amp; relativeIndex &lt;= getNumActiveViews() - 1 ) {&lt;br /&gt;&lt;br /&gt;                final View child = getViewAtRelativeIndex(relativeIndex);&lt;br /&gt;&lt;br /&gt;                final int childBottom = getPaddingTop()&lt;br /&gt;                    + child.getMeasuredHeight();&lt;br /&gt;&lt;br /&gt;                final int realLeft = getMeasuredWidth() * i + getPaddingLeft();&lt;br /&gt;&lt;br /&gt;                child.layout(realLeft, getPaddingTop(),&lt;br /&gt;                    realLeft + child.getMeasuredWidth(), childBottom);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    protected void dispatchDraw(Canvas canvas) {&lt;br /&gt;&lt;br /&gt;        final boolean fastDraw = mMotionAction != MotionEvent.ACTION_UP&lt;br /&gt;            &amp;&amp; mNextChild == INVALID_CHILD;&lt;br /&gt;&lt;br /&gt;        if ( fastDraw ) {&lt;br /&gt;            drawChild(canvas, getCurrentView(), getDrawingTime());&lt;br /&gt;        } else {&lt;br /&gt;            long drawingTime = getDrawingTime();&lt;br /&gt;            if ( mNextChild &gt;= 0 &amp;&amp; mNextChild &lt; getWindowSize()&lt;br /&gt;                &amp;&amp; Math.abs(mWhichChild - mNextChild) == 1 ) {&lt;br /&gt;                drawChild(canvas, getCurrentView(), drawingTime);&lt;br /&gt;                drawChild(canvas, getViewAtRelativeIndex(mNextChild&lt;br /&gt;                    - mWhichChild + mActiveOffset), drawingTime);&lt;br /&gt;            } else {&lt;br /&gt;                for ( ViewAndIndex vi : mViewsMap.values() ) {&lt;br /&gt;                    drawChild(canvas, vi.view, drawingTime);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public boolean dispatchUnhandledMove(View focused, int direction) {&lt;br /&gt;        if ( direction == FOCUS_LEFT ) {&lt;br /&gt;            setDisplayedChild(mWhichChild - 1);&lt;br /&gt;            return true;&lt;br /&gt;        } else if ( direction == FOCUS_RIGHT ) {&lt;br /&gt;            setDisplayedChild(mWhichChild + 1);&lt;br /&gt;            return true;&lt;br /&gt;        }&lt;br /&gt;        return super.dispatchUnhandledMove(focused, direction);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void focusableViewAvailable(View v) {&lt;br /&gt;        final View current = getCurrentView();&lt;br /&gt;        View view = v;&lt;br /&gt;        while ( view != this ) {&lt;br /&gt;            if ( view == current ) {&lt;br /&gt;                super.focusableViewAvailable(v);&lt;br /&gt;                return;&lt;br /&gt;            } else if ( view != this &amp;&amp; view.getParent() != null&lt;br /&gt;                &amp;&amp; view.getParent() instanceof View ) {&lt;br /&gt;                view = (View) view.getParent();&lt;br /&gt;            } else {&lt;br /&gt;                return;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void requestChildFocus(View child, View focused) {&lt;br /&gt;        super.requestChildFocus(child, focused);&lt;br /&gt;&lt;br /&gt;        if ( !isInTouchMode() ) {&lt;br /&gt;            for ( Entry&amp;lt;Integer, ViewAndIndex&gt; entry : mViewsMap.entrySet() ) {&lt;br /&gt;                int index = entry.getKey();&lt;br /&gt;                ViewAndIndex vi = entry.getValue();&lt;br /&gt;                View view = vi.view;&lt;br /&gt;&lt;br /&gt;                if ( child == view ) {&lt;br /&gt;                    setDisplayedChild(index);&lt;br /&gt;                    break;&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,&lt;br /&gt;        boolean immediate) {&lt;br /&gt;        if ( !isInTouchMode() ) {&lt;br /&gt;            for ( Entry&amp;lt;Integer, ViewAndIndex&gt; entry : mViewsMap.entrySet() ) {&lt;br /&gt;                int index = entry.getKey();&lt;br /&gt;                ViewAndIndex vi = entry.getValue();&lt;br /&gt;                View view = vi.view;&lt;br /&gt;&lt;br /&gt;                if ( child == view&lt;br /&gt;                    &amp;&amp; ( index != mWhichChild || !mScroller.isFinished() ) ) {&lt;br /&gt;                    setDisplayedChild(index);&lt;br /&gt;                    break;&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    protected boolean onRequestFocusInDescendants(int direction,&lt;br /&gt;        Rect previouslyFocusedRect) {&lt;br /&gt;        View screen;&lt;br /&gt;        if ( mNextChild != INVALID_CHILD ) {&lt;br /&gt;            screen = getViewAtRelativeIndex(mNextChild - mWhichChild&lt;br /&gt;                + mActiveOffset);&lt;br /&gt;        } else {&lt;br /&gt;            screen = getCurrentView();&lt;br /&gt;        }&lt;br /&gt;        if ( screen != null ) {&lt;br /&gt;            screen.requestFocus(direction, previouslyFocusedRect);&lt;br /&gt;        }&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private void onSecondaryPointerUp(MotionEvent ev) {&lt;br /&gt;        final int pointerIndex = ( ev.getAction() &amp; MotionEvent.ACTION_POINTER_INDEX_MASK ) &gt;&gt; MotionEvent.ACTION_POINTER_INDEX_SHIFT;&lt;br /&gt;        final int pointerId = ev.getPointerId(pointerIndex);&lt;br /&gt;        if ( pointerId == mActivePointerId ) {&lt;br /&gt;            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;&lt;br /&gt;            mTouchX = mLastMotionX = ev.getX(newPointerIndex);&lt;br /&gt;            mLastMotionY = ev.getY(newPointerIndex);&lt;br /&gt;            mActivePointerId = ev.getPointerId(newPointerIndex);&lt;br /&gt;            if ( mVelTracker != null ) {&lt;br /&gt;                mVelTracker.clear();&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public boolean onInterceptTouchEvent(MotionEvent ev) {&lt;br /&gt;        int action = ev.getAction();&lt;br /&gt;        if ( action == MotionEvent.ACTION_MOVE&lt;br /&gt;            &amp;&amp; mMotionAction != MotionEvent.ACTION_DOWN ) {&lt;br /&gt;            return true;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        switch ( action &amp; MotionEvent.ACTION_MASK ) {&lt;br /&gt;        case MotionEvent.ACTION_MOVE: {&lt;br /&gt;            int pointerIndex = ev.findPointerIndex(mActivePointerId);&lt;br /&gt;            float x = ev.getX(pointerIndex);&lt;br /&gt;            float y = ev.getY(pointerIndex);&lt;br /&gt;            int xDiff = (int) Math.abs(x - mLastMotionX);&lt;br /&gt;            int yDiff = (int) Math.abs(y - mLastMotionY);&lt;br /&gt;&lt;br /&gt;            if ( xDiff &gt; mTouchSlop || yDiff &gt; mTouchSlop ) {&lt;br /&gt;                if ( xDiff &gt; mLayoutTouchSlop ) {&lt;br /&gt;                    mMotionAction = MotionEvent.ACTION_UP;&lt;br /&gt;                    mLastMotionX = x;&lt;br /&gt;                }&lt;br /&gt;                if ( mAllowLongPress ) {&lt;br /&gt;                    mAllowLongPress = false;&lt;br /&gt;                    getCurrentView().cancelLongPress();&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_DOWN: {&lt;br /&gt;            mTouchX = mLastMotionX = ev.getX();&lt;br /&gt;            mLastMotionY = ev.getY();&lt;br /&gt;            mActivePointerId = ev.getPointerId(0);&lt;br /&gt;            mAllowLongPress = true;&lt;br /&gt;            if ( mScroller.isFinished() ) {&lt;br /&gt;                mMotionAction = MotionEvent.ACTION_DOWN;&lt;br /&gt;            } else {&lt;br /&gt;                mMotionAction = MotionEvent.ACTION_UP;&lt;br /&gt;            }&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_UP:&lt;br /&gt;        case MotionEvent.ACTION_CANCEL: {&lt;br /&gt;            mMotionAction = MotionEvent.ACTION_DOWN;&lt;br /&gt;            mAllowLongPress = false;&lt;br /&gt;            mActivePointerId = INVALID_POINTER;&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_POINTER_UP: {&lt;br /&gt;            onSecondaryPointerUp(ev);&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_OUTSIDE:&lt;br /&gt;        case MotionEvent.ACTION_POINTER_DOWN:&lt;br /&gt;        default: {&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return ( mMotionAction != MotionEvent.ACTION_DOWN );&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public boolean onTouchEvent(MotionEvent event) {&lt;br /&gt;        if ( mVelTracker == null ) {&lt;br /&gt;            mVelTracker = VelocityTracker.obtain();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        mVelTracker.addMovement(event);&lt;br /&gt;&lt;br /&gt;        int action = event.getAction();&lt;br /&gt;&lt;br /&gt;        switch ( action &amp; MotionEvent.ACTION_MASK ) {&lt;br /&gt;        case MotionEvent.ACTION_DOWN: {&lt;br /&gt;            if ( !mScroller.isFinished() ) {&lt;br /&gt;                mScroller.abortAnimation();&lt;br /&gt;            }&lt;br /&gt;            mTouchX = mLastMotionX = event.getX();&lt;br /&gt;            mActivePointerId = event.getPointerId(0);&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_MOVE: {&lt;br /&gt;            if ( mMotionAction == MotionEvent.ACTION_UP ) {&lt;br /&gt;                int pointerIndex = event.findPointerIndex(mActivePointerId);&lt;br /&gt;                float x = event.getX(pointerIndex);&lt;br /&gt;                int xDiff = (int) ( mLastMotionX - x );&lt;br /&gt;                mLastMotionX = x;&lt;br /&gt;                int scrollX = getScrollX();&lt;br /&gt;&lt;br /&gt;                if ( xDiff &lt; 0 ) {&lt;br /&gt;                    if ( scrollX &gt; 0 ) {&lt;br /&gt;                        scrollBy(Math.max(-scrollX, xDiff), 0);&lt;br /&gt;                    }&lt;br /&gt;                } else if ( xDiff &gt; 0 ) {&lt;br /&gt;                    View lastChild;&lt;br /&gt;                    if ( mCurrentWindowEnd == getWindowSize() - 1&lt;br /&gt;                        &amp;&amp; mViewsMap.containsKey(mCurrentWindowEnd)&lt;br /&gt;                        &amp;&amp; mViewsMap.get(mCurrentWindowEnd).index == mActiveOffset ) {&lt;br /&gt;                        lastChild = getViewAtRelativeIndex(mActiveOffset);&lt;br /&gt;                    } else {&lt;br /&gt;                        lastChild = getViewAtRelativeIndex(mActiveOffset + 1);&lt;br /&gt;                    }&lt;br /&gt;&lt;br /&gt;                    scrollX = lastChild.getRight() - scrollX;&lt;br /&gt;                    scrollX -= getWidth();&lt;br /&gt;                    if ( scrollX &gt; 0 ) {&lt;br /&gt;                        scrollBy(Math.min(scrollX, xDiff), 0);&lt;br /&gt;                    }&lt;br /&gt;                } else {&lt;br /&gt;                    awakenScrollBars();&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_UP: {&lt;br /&gt;            if ( mMotionAction == MotionEvent.ACTION_UP ) {&lt;br /&gt;                int pointerIndex = event.findPointerIndex(mActivePointerId);&lt;br /&gt;                float x = event.getX(pointerIndex);&lt;br /&gt;                mVelTracker.computeCurrentVelocity(1000,&lt;br /&gt;                    (float) mMaximumVelocity);&lt;br /&gt;                int xVelocity = (int) mVelTracker.getXVelocity(mActivePointerId);&lt;br /&gt;                boolean flingedEnough = ( Math.abs(mTouchX - x) &gt; 100.0f );&lt;br /&gt;&lt;br /&gt;                final int globalScrollX = mCurrentWindowStart * getWidth()&lt;br /&gt;                    + getScrollX();&lt;br /&gt;&lt;br /&gt;                int whichScreen = ( globalScrollX + getWidth() / 2 )&lt;br /&gt;                    / getWidth();&lt;br /&gt;&lt;br /&gt;                if ( flingedEnough &amp;&amp; xVelocity &gt; 500 &amp;&amp; mWhichChild &gt; 0 ) {&lt;br /&gt;                    // Fling hard enough to move left.&lt;br /&gt;                    // Don't fling across more than one screen at a time.&lt;br /&gt;                    setDisplayedChild(Math.min(whichScreen, mWhichChild - 1));&lt;br /&gt;                } else if ( flingedEnough &amp;&amp; xVelocity &lt; -500&lt;br /&gt;                    &amp;&amp; mWhichChild &lt; getWindowSize() - 1 ) {&lt;br /&gt;                    // Fling hard enough to move right&lt;br /&gt;                    // Don't fling across more than one screen at a time.&lt;br /&gt;                    setDisplayedChild(Math.max(whichScreen, mWhichChild + 1));&lt;br /&gt;                } else {&lt;br /&gt;                    setDisplayedChild( ( globalScrollX + getMeasuredWidth() / 2 )&lt;br /&gt;                        / getMeasuredWidth());&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                if ( mVelTracker != null ) {&lt;br /&gt;                    mVelTracker.recycle();&lt;br /&gt;                    mVelTracker = null;&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            mMotionAction = MotionEvent.ACTION_DOWN;&lt;br /&gt;            mActivePointerId = INVALID_POINTER;&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_CANCEL: {&lt;br /&gt;            mMotionAction = MotionEvent.ACTION_DOWN;&lt;br /&gt;            mActivePointerId = INVALID_POINTER;&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_POINTER_UP: {&lt;br /&gt;            onSecondaryPointerUp(event);&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        case MotionEvent.ACTION_OUTSIDE:&lt;br /&gt;        case MotionEvent.ACTION_POINTER_DOWN:&lt;br /&gt;        default: {&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;


&lt;script src='http://syntaxhighlight-blogger-dynamic.googlecode.com/files/syntaxhighlighter-20111212.js' type='text/javascript'&gt;&lt;/script&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=OG5Pavh9YCA:pGSH0JgI9iw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=OG5Pavh9YCA:pGSH0JgI9iw:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=OG5Pavh9YCA:pGSH0JgI9iw:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=OG5Pavh9YCA:pGSH0JgI9iw:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/OG5Pavh9YCA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/2853968735867678971/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=2853968735867678971" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/2853968735867678971?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/2853968735867678971?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/OG5Pavh9YCA/efficient-tweetdeck-styled-workspace.html" title="Efficient Tweetdeck-styled Workspace using AdapterViewAnimator" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/06/efficient-tweetdeck-styled-workspace.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYDSH0yeyp7ImA9WhZXFEw.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-9222590756760684075</id><published>2011-05-03T18:14:00.004+08:00</published><updated>2011-05-03T18:22:59.393+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-03T18:22:59.393+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="led" /><category scheme="http://www.blogger.com/atom/ns#" term="android" /><title>Controlling the LED Flashlight in Android</title><content type="html">Today my friend &lt;a href="http://www.facebook.com/lzyblah"&gt;lzy&lt;/a&gt; asked me if I could help him by writing an Android app that controls the LED on Android devices. He needs this because he needs to do a demo of his customized LED driver to some vendor.&lt;br /&gt;&lt;br /&gt;He needed an app that has 4 buttons: one that turns off the LED and the other three flashing the LED at different frequencies.  It sounds like an easy task so I wrote it up quickly.  The source code is released &lt;a href="https://github.com/wuman/Flashlight-Controller"&gt;here&lt;/a&gt;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The trick is to write to the file &lt;span class="Apple-style-span" &gt;/sys/class/leds/flashlight/brightness&lt;/span&gt;.&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=sJWxoBP-G2Q:6Pr2nx-k5wk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=sJWxoBP-G2Q:6Pr2nx-k5wk:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=sJWxoBP-G2Q:6Pr2nx-k5wk:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=sJWxoBP-G2Q:6Pr2nx-k5wk:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/sJWxoBP-G2Q" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/9222590756760684075/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=9222590756760684075" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/9222590756760684075?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/9222590756760684075?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/sJWxoBP-G2Q/controlling-led-flashlight-in-android.html" title="Controlling the LED Flashlight in Android" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/05/controlling-led-flashlight-in-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0YFQng9fSp7ImA9WhdVEEs.&quot;"><id>tag:blogger.com,1999:blog-12998912.post-6356716545252643311</id><published>2011-04-29T15:48:00.006+08:00</published><updated>2011-09-15T12:31:53.665+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-15T12:31:53.665+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="macflickr" /><category scheme="http://www.blogger.com/atom/ns#" term="dreamerslab" /><category scheme="http://www.blogger.com/atom/ns#" term="courage" /><category scheme="http://www.blogger.com/atom/ns#" term="dream" /><title>Courage to Do What You Believe In</title><content type="html">Last night I was fortunate to have met a new friend at our weekly &lt;a href="http://www.facebook.com/group.php?gid=173053565674"&gt;Hacking Thursday&lt;/a&gt; gathering.  His name is &lt;a href="http://dreamerslab.com/tw/about#main=1"&gt;Ben Lin&lt;/a&gt;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;He demoed his recent project called &lt;a href="http://www.macflickr.com"&gt;MacFlickr&lt;/a&gt;, which is a web client for browsing the popular photo service &lt;a href="http://www.flickr.com/"&gt;Flickr&lt;/a&gt;.  What's special about his client is that the interface mimics that of a Mac desktop, but everything is chromed within the web browser and written in regular HTML/JavaScript.  The interaction is so blazing fast that it can really fool you into thinking that you're in a native desktop environment if the browser is switched to full-screen mode.  It wasn't until after he restored the browser window back to normal size had I realize that it was a web app!  As a programmer I could immediately tell how much time and effort was spent on this project to make all the kinky details work elegantly like this.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That's not the end of the story though.  He continued to share with us his background and how he came about ideas for this project.  As a matter of fact he is not even a computer science major; he learned all about coding by himself simply because he had ideas that he wanted implemented but could not find programmers to help him materialize those ideas into a product.  He started off by using OTS libraries to help him implement the UI effects.  Iteration after iteration he would realize that some of these libraries don't always support 100% of what he needs, so he would go into the source code of these libraries to make major modifications that tailor to his own project needs.  This is what we do on a daily basis as a free software programmer, but for someone without any programming background you can imagine how impressive that really is.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What I like the most about his story is that he quit an old, stable job to pursue his own entrepreneurial dream.  His courage to do what he believes in and his determination to refine a product to near perfection are things that I will take home with and always remember.  Thanks for the inspiration Ben!&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/wuman?a=-TEJ1oTGDDo:v5CLkPfbnF0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=-TEJ1oTGDDo:v5CLkPfbnF0:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=-TEJ1oTGDDo:v5CLkPfbnF0:7Q72WNTAKBA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=7Q72WNTAKBA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/wuman?a=-TEJ1oTGDDo:v5CLkPfbnF0:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/wuman?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/wuman/~4/-TEJ1oTGDDo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.wu-man.com/feeds/6356716545252643311/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=12998912&amp;postID=6356716545252643311" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/6356716545252643311?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/12998912/posts/default/6356716545252643311?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/wuman/~3/-TEJ1oTGDDo/courage-to-do-what-you-believe-in.html" title="Courage to Do What You Believe In" /><author><name>David Wu</name><uri>https://plus.google.com/103043706577279283558</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-dppPlqbRuYQ/AAAAAAAAAAI/AAAAAAAAbdc/QswMtBiCl9k/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.wu-man.com/2011/04/courage-to-do-what-you-believe-in.html</feedburner:origLink></entry></feed>
