2013-10-14

Script for doing a backup of an Android app and unpacking it

Recent Android versions added the ability to create backups via "adb backup". I use this during development if I want to look inside the database files (and other files written to the app's private directories).

Quite useful. Obviously you shouldn't encrypt the backup :)


Creating a System Overlay (Always on Top over all Apps) in Android

I don't think this is something I want to see overused by apps but I guess there are some very cool things possible with this.

I have already seen apps using this concept in a good way and I already have ideas what to do with this (once I get enough spare time).

The code is based on the solution outlined at http://stackoverflow.com/questions/4481226/creating-a-system-overlay-always-on-top-button-in-android but this one also adds support for moving the view around by dragging it.


A fluent API for ContentResolver#query

One thing that always felt ugly was using ContentResolver#query. A line like this isn't very readable and changing the query often introduce new bugs:

c =  getContentResolver().query(Contacts.CONTENT_URI, new String[]{Contacts._ID,Contacts.DISPLAY_NAME}, "("+Contacts.HAS_PHONE_NUMBER+"=? AND "+Contacts.STARRED+"=?) OR ("+Contacts.HAS_PHONE_NUMBER+"=? AND "+Contacts.STARRED+"<>?)", new String[]{"1","0","1","0"}, Contacts.DISPLAY_NAME);

So I thought it would be nice if you could write this query like this:

   c = Query.create().select(Contacts._ID,Contacts.DISPLAY_NAME).
   where( Query.create().
   where(Contacts.HAS_PHONE_NUMBER).eq("1").
   and().where(Contacts.STARRED).eq("0")
   ).or().
   where( Query.create().
   where(Contacts.HAS_PHONE_NUMBER).eq("1").
   and().where(Contacts.STARRED).ne("0")
   ).
   orderBy(Contacts.DISPLAY_NAME).
   build().execute(getContentResolver(), Contacts.CONTENT_URI);

IMHO this is easier to read, easier to understand and easier to maintain. So I did a quick proof of concept and it turned out to be pretty easy.



2013-06-20

Pull to Reveal Additional Options

Recently I have seen some screen designs for an Android app where the user can pull a listview to reveal additional options e.g. sort options or something like that. So I started to think about a possible approach for this.

Header views are not what we want so I thought about inheriting from ListView and add the functionality but aber a look at the source I thought there must be an easier solution to this.

So my solution was to create a subclass of LinearLayout which intercepts touch events. There are two children. A container for the additional options at the top and the ListView of cause.

And this turned out to be an easy and nice solution IMHO.

Here is the source of that touch interception LinearLayout:


 package de.mobilej.widget;  
 import android.content.Context;  
 import android.os.Bundle;  
 import android.os.Parcelable;  
 import android.util.AttributeSet;  
 import android.view.MotionEvent;  
 import android.view.MotionEvent.PointerCoords;  
 import android.view.View;  
 import android.view.animation.OvershootInterpolator;  
 import android.widget.LinearLayout;  
 import android.widget.ListView;  
 import de.mobilej.listboxtest.R;  
 public class TopDrawer extends LinearLayout {  
   private boolean headerVisible;  
   private ListView lv;  
   private int originalLvBottom;  
   PointerCoords pointerMoveStart = new PointerCoords();  
   boolean scrolling = false;  
   private View v;  
   public TopDrawer(Context context) {  
     super(context);  
   }  
   public TopDrawer(Context context, AttributeSet attrs) {  
     super(context, attrs);  
   }  
   public TopDrawer(Context context, AttributeSet attrs, int defStyle) {  
     super(context, attrs, defStyle);  
   }  
   @Override  
   protected void onLayout(boolean changed, int l, int t, int r, int b) {  
     super.onLayout(changed, l, t, r, b);  
     if (v == null) {  
       v = findViewById(R.id.header);  
       lv = (ListView) findViewById(R.id.mylist);  
     }  
     originalLvBottom = lv.getBottom();  
     if (!headerVisible) {  
       v.setTranslationY(-1 * v.getHeight());  
       lv.setTranslationY(-1 * v.getHeight());  
       lv.setBottom(originalLvBottom + v.getHeight());  
     }  
   }  
   public void colapse() {  
     headerVisible = false;  
     lv.setBottom(originalLvBottom + v.getHeight());  
     float curTrans = v.getTranslationY();  
     float destTrans = -1 * v.getHeight();  
     if (curTrans > destTrans) {  
       v.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator()).start();  
       lv.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator()).start();  
     }  
   }  
   @Override  
   public boolean onInterceptTouchEvent(MotionEvent ev) {  
     int action = ev.getActionMasked();  
     if (action == MotionEvent.ACTION_DOWN) {  
       scrolling = true;  
       ev.getPointerCoords(0, pointerMoveStart);  
     } else if (action == MotionEvent.ACTION_MOVE && scrolling) {  
       PointerCoords currentPos = new PointerCoords();  
       ev.getPointerCoords(0, currentPos);  
       if (pointerMoveStart.y - currentPos.y < 0) {  
         if (lv.getFirstVisiblePosition() == 0) {  
           View topChild = lv.getChildAt(0);  
           if (topChild.getY() == 0) {  
             return true;  
           }  
         }  
       }  
     }  
     return false;  
   }  
   @Override  
   public boolean onTouchEvent(MotionEvent ev) {  
     int action = ev.getActionMasked();  
     if (action == MotionEvent.ACTION_MOVE && scrolling && !headerVisible) {  
       PointerCoords currentPos = new PointerCoords();  
       ev.getPointerCoords(0, currentPos);  
       if (pointerMoveStart.y - currentPos.y < 0) {  
         if (lv.getFirstVisiblePosition() == 0) {  
           View topChild = lv.getChildAt(0);  
           if (topChild.getY() == 0) {  
             int hHeader = v.getHeight();  
             int moved = (int) (currentPos.y - pointerMoveStart.y);  
             int trans = -hHeader + moved;  
             if (trans > 0) {  
               trans = 0;  
               headerVisible = true;  
             }  
             v.setTranslationY(trans);  
             lv.setTranslationY(trans);  
             lv.setBottom(originalLvBottom - trans);  
             return true;  
           }  
         }  
       }  
     } else if (action == MotionEvent.ACTION_UP) {  
       scrolling = false;  
       if (!headerVisible) {  
         lv.setBottom(originalLvBottom + v.getHeight());  
         float curTrans = v.getTranslationY();  
         float destTrans = -1 * v.getHeight();  
         if (curTrans > destTrans) {  
           v.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator())  
               .start();  
           lv.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator())  
               .start();  
         }  
       }  
     }  
     return false;  
   }  
   @Override  
   protected void onRestoreInstanceState(Parcelable state) {  
     Bundle b = (Bundle) state;  
     if (b == null) {  
       return;  
     }  
     super.onRestoreInstanceState(b.getParcelable("SUPER"));  
     headerVisible = b.getBoolean("visible");  
   }  
   @Override  
   protected Parcelable onSaveInstanceState() {  
     Bundle b = new Bundle();  
     b.putParcelable("SUPER", super.onSaveInstanceState());  
     b.putBoolean("visible", headerVisible);  
     return b;  
   }  
 }  

It's not fully polished code but just a quick prove of concept. And it's also not possible currently to hide the additional views easily by e.g. overscrolling to the end of the list (in my example I have a button that calls the collape method). But all this can be added easily.

A possible layout file could look like this


 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   android:orientation="vertical"  
   android:paddingBottom="@dimen/activity_vertical_margin"  
   android:paddingLeft="@dimen/activity_horizontal_margin"  
   android:paddingRight="@dimen/activity_horizontal_margin"  
   android:paddingTop="@dimen/activity_vertical_margin"  
   tools:context=".MainActivity" >  
   <TextView  
     android:id="@+id/textView1"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:text="@string/hello_world" />  
   <de.mobilej.widget.TopDrawer  
     android:id="@+id/topDrawer"  
     android:layout_width="match_parent"  
     android:layout_height="wrap_content"  
     android:orientation="vertical" >  
     <LinearLayout  
       android:id="@+id/header"  
       android:layout_width="match_parent"  
       android:layout_height="wrap_content"  
       android:orientation="horizontal" >  
       <EditText  
         android:id="@+id/myEditText"  
         android:layout_width="0dp"  
         android:layout_height="wrap_content"  
         android:layout_weight="1" />  
       <ImageButton  
         android:id="@+id/button"  
         android:layout_width="wrap_content"  
         android:layout_height="wrap_content"  
         android:src="@android:drawable/btn_star" />  
     </LinearLayout>  
     <ListView  
       android:id="@+id/mylist"  
       android:layout_width="match_parent"  
       android:layout_height="wrap_content"  
       android:layout_alignParentLeft="true"  
       android:layout_below="@+id/textView1"  
       android:layout_marginTop="26dp" >  
     </ListView>  
   </de.mobilej.widget.TopDrawer>  
 </LinearLayout>  


The important things here are the IDs header and myList (which are not wisely choosen I have to admit).

Wth this idea also things like the Pull-To-Refresh as seen in the latest GMail for Android version are now very easy to implement.

2013-05-03

Continuations for Android

In simple words using "Continuations" mean you can pause the execution of code and restart at the same point later with all local variables. (You can think of it as "freezing" the stack)

While you can use threads and locks for a similar behavior but you can even persist a continuation and reload it later. While this is not something you need every day it can come handy sometimes.

While other programming languages have built in support for something like this it is not a standard feature of Java.

However there is a small and nice Apache project making this possible with Java:

http://commons.apache.org/sandbox/javaflow/

It comes with an Ant task to do byte code manipulation and a small runtime (one or two classes). Really nice!

I wondered if this could work on Android, too. (Ok - there is no reason why not but I wanted to give it a try)

YES! IT WORKS!

One thing that one should be aware of is that you really need byte code transformation done by an Ant task. While it's no problem to extend Android's ant builds we all need to run code right from Eclipse.

Fortunately there is a simple solution to this: Just create an Ant script doing whatever is needed and add a new "Builder" between "Java Builder" and "Android Package Builder". That's it!


Rethinking Android Unit Testing

After using Robolectric for a while I was looking for a different approach to do unit testing for Android.

While Robolectric met a lot of my requirements I had the need for a powerful mocking framework and decided to use PowerMock(ito). It's a really powerful framework but I wasn't able to use it together with Robolectric since both do a lot of classloader and byte code transformation magic.

So I decided to just run the test with "android.jar" from the SDK on the classpath and forget about Robolectric but instead mock the platform API calls with PowerMock(ito).

Unfortunately it turned out that this wasn't as easy as it sounds. So I decided to use Javassist to do some byte code manipulation to the android.jar before using it for unit testing. The problem with the original android.jar was that each and every method throws a "Sub"-runtime exception and PowerMock(ito) wasn't able to mock super-calls. (imagine an activity as the class-under-test which must call super.onCreate() in it's onCreate() implementation. Since it's not possible to mock the super.onCreate() you are out of luck)

This approach turned out to be the silver bullet of In-JVM Android unit testing for me.

Thanks to Javassist the transformation of the android.jar was a simple task and now I'm able to use the full potential of PowerMock(ito) to test Android code in Eclipse and during CI builds - including code coverage and all the other fancy stuff.