2018-05-28

Video on Android

Before I started working on the app Device Video Maker I haven't used much of Android's media api. That app let's you capture the screen and create a nice video with the screen capture rendered into a device frame together with custom text. I was surprised that quite few articles are there on the web. I think the api documentation is pretty good but to be honest I had overseen a lot of details from there and I had to learn things the hard way. E.g. that CSD thing puzzled me a lot and the errors you will see in your logs when you do something wrong are very confusing and cryptic. That's a bad experience when developing such an app. One of the best resources (beside the api documentation - you should read that carefully) is Big Flake - it helped me a lot. It's not that easy to understand everything since it references mainly CTS code and code that at least looks like CTS code. But it should definitely be your second stop after the api docs.

2018-03-10

Trying to generate downloads in an overcrowded category

We all know the gold rush in app stores is over.

I just decided to see if it's still possible to generate some downloads in an overcrowded category.

I looked for something that is really easy to implement, has much competition and the competitors feature high volume downloads: Crazy-Text

It's really simple - yes. But that's the point.

I document everything I do in a spreadsheet and I am going to write down and publish the findings.

There are almost no downloads yet - so it's an experiment. Let's see what will happen to this tiny app.

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.