Thursday, March 20, 2014

My New Home

Hey Guys,

Finally I got my official blog at www.onlydevtwice.com

From now on, I will publish my blog post both in Thai and English in my new site.

See you there.

:-)

Wednesday, November 13, 2013

Unit testing Android Handler using Robolectric

I was looking for ways to do unit testing my Android code. Since Android JUnit need to be executed in emulator or real device, I want another way to run my tests outside these environments.

Then I use actual Java JUnit and Robolectric to test my code, but the problem of this approach is some code that is need to be ran inside Android environment cannot actually running while testing.

In my case, I use Handler from background Thread to sends Message back to caller Thread. I want to test that the callback method is actually executed on caller Thread but the Message is never be sent in testing because of the test environment.

Here is snippet of my TargetClass code.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

public void process(TargetClassListener listener){

  //1 grab caller thread callback.
  mListener = listener;
  mCallbackHandler = new Handler(){
    @Override
    public void handleMessage(Message msg){
      super.handleMessage(msg);
      if(mListener != null){
        mListener.onSuccess();
      }
    }
  };

  //2 start processing in worker thread.
  HandlerThread ht = new HandlerThread("",android.os.Process.THREAD_PRIORITY_BACKGROUND){
    @Override
    public void run(){
      //1 do something.
      //2 notify onSuccess().
      Message msg = mCallbackHandler.obtainMessage();
      msg.what = TargetClassListener.CALLBACK_ON_SUCCESS;
      msg.sendToTarget();
    }
  };
  ht.start();

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

From the code above, the background Thread (HandlerThread ht) will send Message to mCallbackHandler which will force caller Thread to execute handleMessage() and execute mListener.onSuccess().

While test is running, the callback method onSuccess() won't be executed because the Message cannot be sent in testing environment.

So, instead of validate test result in onSuccess() (which cannot be reached here), I change to validate Handler object.

Here is snippet from my test code.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@Test
public void process_normalCase_willExecuteCallbackOnCallerThread(){

  //1 execute class under test.
  TargetClass tc = new TargetClass();
  tc.process(this);

  //2 wait for background thread to be done.
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    fail(e.toString());
  }

  //3 validate handler object.
  assertNotNull(tc.getCallbackHandler());
  ShadowHandler handler = (ShadowHandler) Robolectric.shadowOf(tc.getCallbackHandler());
  assertTrue(handler.hasMessages(CALLBACK_ON_SUCCESS));
  assertEquals(Thread.currentThread().getId(), handler.getLooper().getThread().getId());

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

At the third step, I get the Handler object from target class and wrap it inside Robolectric's ShadowHandler. Then validate the Message that the handler contains and the Thread id that associated with its Looper.

If these test cases are passed, then I can guarantee that the TargetClass will execute the right callback on the right Thread.

It's something, isn't it?  :-)

Sunday, June 9, 2013

Take a screenshot in Raspbian Wheezy

To take a screenshot in LXDE, you will need the application ImageMagick. You can download ImageMagick using apt tool.

sudo apt-get install imagemagick

Once ImageMagic is installed, you can take a screenshot using import command.

import -window root screen.png

You can also combine this command with the delay command to make a delay time before take a screenshot.

sleep 5; import -window root screen.png

This will be delayed for 5 seconds then take a screenshot. Combine sleep command is very handy since you might need to hide the terminal window before take a shot.

Reference: http://stackoverflow.com/questions/10581953/how-do-i-take-a-screenshot-on-a-raspberry-pi-running-debian-squeeze-and-lxde

Friday, June 7, 2013

Raspbian Wheezy keyboard mapping problem

I found my Raspbian Wheezy got a wrong keyboard mapping. When I type '@' and '#' symbol, it turns out to be other characters.

After searching in the internet (using DuckDuckGo in Raspbian :-D) I found this thread http://raspberrypi.stackexchange.com/questions/1042/why-is-my-symbol-not-working

So the problem is default keyboard layout is UK and I have to change it to US by change its configuration file using nano.

sudo nano /etc/default/keyboard

change XKBLAYOUT value from "gb" to "us"

then restart the PI

sudo /sbin/reboot

after my PI is wake up again, keyboard layout is changed to US and now problem solved.

Thursday, August 23, 2012

[Android] Which alternative image resource will be chosen by the system?

This is a note from my experiments.

Let's say I've supplied images only in drawable-xhdpi and drawable-hdpi folders and now my app is running on device with mdpi screen density. My question is which folder will be chosen by Android system to scale an image to display in mdpi device. Below is a note from my experiments about how Android find the best matching image resource.

Drawable folders structure
drawable-xhdpi (density = 320)
drawable-hdpi (density = 240)
drawable-mdpi (density = 160)
drawable-ldpi (density = 120)

CAUTION: Screen density qualifiers are not supported in Android 1.5 and lower.

Code snippet
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
            
Log.v(TAG, "Options value before decode Bitmap");
Log.v(TAG, String.format("inDensity %d, inScaled %b, inScreenDensity %d, inTargetDensity %d", options.inDensity, options.inScaled, options.inScreenDensity, options.inTargetDensity));
            
BitmapFactory.decodeResource(getResources(), R.drawable.my_image, options);
            
Log.v(TAG, "Options value after decode Bitmap");
Log.v(TAG, String.format("inDensity %d, inScaled %b, inScreenDensity %d, inTargetDensity %d", options.inDensity, options.inScaled, options.inScreenDensity, options.inTargetDensity));

Experiment #1 Does the system scales image to matches screen density?

This experiment try to answers the question "Does the system chooses image from another folder if there is no image for current device screen density?"

A.      Supply only image in xhdpi folder.

Log result

o    On xhdpi device
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 320
Meaning
System chooses image from xhdpi folder (inDensity = 320) for xhdpi density (inTargetDensity = 320)
 
o    On hdpi device
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 240
Meaning
System chooses image from xhdpi folder (inDensity = 320) and scales it to current hdpi density (inTargetDensity = 240)


Conclusion from experiment #1
System scales image to matches current screen density.


Experiment #2 Does the system chooses bitmap from the most closely matches drawable folder?

This experiment try to answers the question "Does the system choose image from drawable folder that most closely matches device screen density?"

A.      Supply image in xhdpi and hdpi

Log result

o    On mdpi device
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 160

Meaning
System chooses image from hdpi folder (inDensity = 240) which is the most closely matches current mdpi density (inTargetDensity = 160)
 
o    On ldpi device
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 120
 
Meaning
System chooses image from hdpi folder (inDensity = 240) which is the most closely matches current ldpi density (inTargetDensity = 120) than xhdpi.

B.      Supply image in hdpi and mdpi

Log result
 
o    On xhdpi device
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 320

Meaning
System chooses image from hdpi folder (inDensity = 240) which is the most closely matches current xhdpi density (inTargetDensity = 320) than mdpi.
 
o    On ldpi device
inDensity 160, inScaled true, inScreenDensity 0, inTargetDensity 120

Meaning
System chooses image from mdpi folder (inDensity = 160) which is the most closely matches current ldpi density (inTargetDensity = 120) than hdpi.
 
C.      Supply image in mdpi and ldpi

Log result

o    On xhdpi device
inDensity 160, inScaled true, inScreenDensity 0, inTargetDensity 320

Meaning
System chooses image from mdpi folder (inDensity = 160) which is the most closely matches current xhdpi density (inTargetDensity = 320) than ldpi.

o    On hdpi device
inDensity 160, inScaled true, inScreenDensity 0, inTargetDensity 240

Meaning
System chooses image from mdpi folder (inDensity = 160) which is the most closely matches current hdpi density (inTargetDensity = 240) than ldpi.

D.      Supply image in xhdpi and ldpi

Log result

o    On hdpi device
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 240

Meaning
System chooses image from xhdpi folder (inDensity = 320) which is the most closely matches current hdpi density (inTargetDensity = 240) than ldpi
 
o    On mdpi device
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 160

Meaning
System chooses image from xhdpi folder (inDensity = 320) which is not the most closely matches current mdpi density (inTargetDensity = 160).
In this case, the chosen image should be the image in ldpi folder (density = 120) which is the most closely matches mdpi but system picks an image from xhdpi folder. This could be the effect from scaling factor which describes in Android document that system prefer to scales down the image.


Conclusion from experiment #2

·         System is not always choose the image from the most closely matches folder. In the experiment #2-D mdpi device case, system chooses to scale down the image from xhdpi folder instead of scale up the image from ldpi folder which is the most closely matches. This could be the effect from the scaling factor which scale down the image can produces better image quality than scale it up. We will try experiment more about scaling factor in the experiment #3.

·         Another interesting case is experiment #2-B ldpi device case. The system chooses image from mdpi folder and scale it down. This should not be a problem since mdpi is the most closely matches but, according to this Android document which describes this case that system would prefer to scale down the hdpi image instead of mdpi one because of scaling hdpi to ldpi by a factor of 0.5 has fewer artifacts compared to scaling mdpi to ldpi by a factor of 0.75. I'm not sure why my experiment result is conflicted with the one in Android document perhaps I'm running my test case on Android emulator (Froyo) or something else?

Experiment #3 Which one to be chosen? Scale up or scale down?

This experiment try to find the answer in the situation that the system cannot find an image for the current screen density but the images exist in drawable folders which are next to the current device screen density folder  e.g. current screen density is hdpi but the images are stored in drawable-xhdpi and drawable-mdpi, system will scale down an image from xhdpi folder or scale up an image from mdpi folder?

A.      Supply image in xhdpi and ldpi (experiment #2-D)

Log result

o    On hdpi device
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 240

Meaning
System scales down the image from xhdpi folder (inDensity = 320) to current hdpi density (inTargetDensity = 240).

o    On mdpi device
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 160

Meaning
System scales down the image from xhdpi folder (inDensity = 320) to current mdpi density (inTargetDensity = 160).

B.      Supply image in xhdpi and mdpi

Log result

o    On hdpi device
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 240

Meaning
System scales down the image from xhdpi folder (inDensity = 320) to current hdpi density (inTargetDensity = 240).

o    On ldpi device
inDensity 160, inScaled true, inScreenDensity 0, inTargetDensity 120

Meaning
System scales down the image from mdpi folder (inDensity = 160) to current ldpi density (inTargetDensity = 160) which is also the most closely matches.

C.      Supply image in hdpi and ldpi

Log result

o    On mdpi device
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 160

Meaning
System scales down image from hdpi folder (inDensity = 240) to current mdpi density (inTargetDensity = 160).

o    On xhdpi device
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 320

Meaning
System scales up image from hdpi folder (inDensity = 240) to current xhdpi density (inTargetDensity = 320) which is also the most closely matches.

Conclusion from experiment #3
System prefer to scales down the image.

Experiment #4 Three image candidates.

The goal of this experiment is to determine which image will be chosen by the system if all folders has an image but the current device density folder.

A.      Supply image in xhdpi, hdpi and mdpi

Log result on ldpi device
inDensity 160, inScaled true, inScreenDensity 0, inTargetDensity 120

Meaning
System scales down image from mdpi folder (inDensity = 160) which is most closely matches current ldpi density (inTargetDensity = 120).

B.      Supply image in xhdpi, hdpi and ldpi

Log result
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 160

Meaning
System scales down image from hdpi folder (inDensity = 240) to current mdpi density (inTargetDensity = 160).

C.      Supply image in xhdpi, mdpi and ldpi.

Log result
inDensity 320, inScaled true, inScreenDensity 0, inTargetDensity 240

Meaning
System scales down image from xhdpi folder (inDensity = 320) to current hdpi density (inTargetDensity = 240).

D.      Supply image in hdpi, mdpi and ldpi.

Log result
inDensity 240, inScaled true, inScreenDensity 0, inTargetDensity 320

Meaning
System scales up image from hdpi folder (inDensity = 240) which is most closely matches current xhdpi density (inTargetDensity = 320).

Conclusion from experiment #3
System prefer to scales down from the larger image in the most closely matches folder or scales up from the smaller image in the most closely matches folder if there are no image in higher resolution.

Conclusion
From all experiment results, below is the behavior of the Android system when finding best matching image.

Precondition
The desired image does not exist in the drawable folder of the current device screen density.

1.       System chooses the higher resolution image which is most closely matches the current device screen density.
2.       If no higher resolution image exist then chooses the lower resolution image which is most closely matches the current device screen density.