Saturday, March 22, 2008

Automating port redirection in Android

If you are developing an application that uses port redirection you should open the emulator's console and issue the redir commands every time you restart the emulator. This is documented by Google in android emulator page.
To redirect port you need to
telnet localhost 5554

to open the emulator's console and then add the desired redirection commands, like
redir add tcp:5000:5000

But, is there another way than typing the commands every time ?
It's much likely that you make a mistake and wonder why your application stopped working until you realize your typing mistake.
I couldn't find any other documented way of automating port redirection, so I decided to go my way.

Python to the rescue
This is clearly a task for expect, but instead of trying to remember how to program TCL I decided to go with the python alternative: pexpect.

The script is named, pretty obviously, android-redir.py:

Download the script from here.

#!/usr/bin/env python
"""
$Id$
"""

__license__ = """

Copyright (C) 2008 Diego Torres Milano

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA
"""

__name__ = 'android port redirection'
__version__ = '0.3'
__rev__ = '$Revision$'

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# redirections can be added to ~/android/redir.conf, one per line,
# as an example, 'tcp:5000:6000' will route any packet sent to the host's
# TCP port 5000 to TCP port 6000 of the emulated device
#
# default emulated device:
host = 'localhost'
port = 5554
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

import sys
import os
from optparse import OptionParser
import pexpect

usage = "usage: %prog [options] [{tcp|udp}:hostport:emuport] ..."

parser = OptionParser(usage=usage, version="%prog " + __version__)
parser.add_option("-v", "--verbose", dest="verbose", default=False,
action="store_true",
help="verbose output [default: %default]")
parser.add_option("", "--host", dest="host", default=host,
help="HOST where emulator is running [default: %default]")
parser.add_option("-p", "--port", dest="port", default=port,
help="emulator console PORT [default: %default]")
parser.add_option("-n", "--dry-run", dest="dry_run", default=False,
action="store_true",
help="Don't execute the commands")
(options, args) = parser.parse_args()

redirs = []
cmd = 'telnet %s %d' % (options.host, options.port)

try:
for redir in open(os.path.expanduser("~") + "/.android/redir.conf"):
redirs.append(redir[:-1])
except:
pass

for redir in args:
redirs.append(redir)

if options.verbose:
print cmd
if len(redirs) > 0:
print "adding redirections:"
for redir in redirs:
print "\t" + redir

if options.dry_run:
sys.exit(0)

if len(redirs) == 0:
print >>sys.stderr, "ERROR: redirection list is empty."
sys.exit(1)

child = pexpect.spawn(cmd)
child.expect("OK")

for redir in redirs:
child.sendline("redir add " + redir)
r = child.expect(["OK", "KO: host port already active", "KO:.*"])
if r == 2:
raise Exception(child.after)

if options.verbose:
child.sendline("redir list")
child.expect("OK")
print child.before

child.sendline("quit")


Usage
This is the script help showing its usage, accessible by
$ android-redir.py --help
Usage: android-redir.py [options] [{tcp|udp}:hostport:emuport] ...

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-v, --verbose verbose output [default: False]
--host=HOST HOST where emulator is running [default: localhost]
-p PORT, --port=PORT emulator console PORT [default: 5554]
-n, --dry-run Don't execute the command


You can also create ~/.android/redir.conf file and add the redirections you want, one per line
tcp:5000:5000
udp:50001:50001


and they will be read every time the script is run.


Next time we will be looking at how to integrate this with the emulator to run both commands at the same time.

Tuesday, March 11, 2008

Test Driven Development and GUI Testing on the Android platform: Temperature Converter sample revisited

Test Driven Development and GUI Testing on the Android platform: Temperature Converter sample revisited

This document is published at http://docs.google.com/Doc?id=ddwc44gs_41dhjhbdd2

WARNING: Blogspot engine change the link, so you have to cut and paste.

Functional tests

The new Google Android SDK provides some improvements to the Instrumentation code and the ways of handling it. Using Dev Tools, now you can find and Instrumentation list entry that shows all of the classes providing Instrumentation and a way to run it.

Positron

We will be using positron positron-0.8-alpha and we will introduce one small change. Positron only presents the test results in the DDMS perspective

INFO/Positron(16414): .F..F...
INFO/Positron(16414): Time: 6.46
INFO/Positron(16414): There were 2 failures:


the idea is to use an Intent to be able to present the results as an activity: AndroidTestResults.
AndroidTestResults listen to the Intent broadcasted by Positron.
First, let's introduce this changes to the base Positron
  1. we have to define the intent
  2. using a Template Method pattern with a method called doHandleResult() implement invariant and variable parts of the algorithm
  3. define a default donHandleResult() to do nothing

public abstract class Positron extends Instrumentation { public static final String VIEW_TEST_RESULTS_ACTION = "positron.action.VIEW_TEST_RESULTS"; ... @Override public void onStart() { backup(); ByteArrayOutputStream testOutput = new ByteArrayOutputStream(1024); InstrumentedTestRunner runner = new InstrumentedTestRunner(this, new PrintStream(testOutput)); InstrumentedTestResult result = (InstrumentedTestResult)runner.doRun(suite()); Log.i(TAG, testOutput.toString()); // this implements a TEMPLATE METHOD pattern // by DTM doHandleResult(result); finishAll(); pauseButton.quit(); restore(); waitForIdleSync(); finish(result.errorCount() == 0 && result.failureCount() == 0 ? 0 : 1, result.toBundle()); } ... /** handle the result in the desired way */ protected void doHandleResult(InstrumentedTestResult result) { // do nothing } }
doHandleResult gives us the ability to handle the results in the way we want.

As part of the instrumentation of TemperatureConverter tests we have implemnented a class Positron that extends positron.Positron. In this class we need to implement the abstract method suite() returning our TestSuite, but now we are also adding our Template Method.

@Override protected void doHandleResult(InstrumentedTestResult result) { Intent intent = new Intent(VIEW_TEST_RESULTS_ACTION); intent.putExtras(result.toBundle()); intent.putExtra("suite", suite().getName()); getContext().broadcastIntent(intent); }

OverallTest

Let's change our OverallTest
package com.codtech.android.tdd.validation;
import java.io.IOException; import android.content.Intent; import com.codtech.android.tdd.R; import com.codtech.android.tdd.TemperatureConverterActivity;
/** * @author diego * */ public class OverallTest extends positron.TestCase { private static final String TAG = "OverallTests"; private TemperatureConverterActivity activity; /** * @param name */ public OverallTest(String name) { super(name); } /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); Intent intent = new Intent(getTargetContext(), TemperatureConverterActivity.class); startActivity(intent.addLaunchFlags(Intent.NEW_TASK_LAUNCH)); activity = (TemperatureConverterActivity)activity(); // Is it our application ? assertEquals(getTargetContext().getString(R.string.app_name), activity().getTitle()); // Do we have focus ? assertEquals(activity.getCelsiusEditText(), activity.getCurrentFocus()); } /* (non-Javadoc) * @see junit.framework.TestCase#tearDown() */ protected void tearDown() throws Exception { finishAll(); } public void testConversion() { // Enter a temperature sendString("123"); // Convert press(DOWN, DOWN, CENTER); // Why 2 downs are needed ? // Verify correct conversion 123C -> 253F assertEquals("valid conversion", "253", activity.getFahrenheitEditText().getText().toString()); } public void testValidCharachters() { // Enter invalid characters (only digits and +/- are allowed String str = "-0123456789"; sendString(str); assertEquals("invalid characters", str, activity.getCelsiusEditText().getText().toString()); } public void testInvalidCharachters() { // Enter invalid characters (only digits and +/- are allowed // in this "digits" mode, some keys are automatically converted into its // numeric keypad equivalentes (i=>-, o=>+, p=>+, etc.) sendString("abcdefghpqrstuvwxyz"); assertEquals("invalid characters", "", activity.getCelsiusEditText().getText().toString()); } public void testInvalidConversion() { // Enter a temperature sendString("-274"); // Convert press(DOWN, DOWN, CENTER); // Why 2 downs are needed ? // Verify that the wrong value is still displayed assertEquals("invalid conversion", "-274", activity.getCelsiusEditText().getText().toString()); // FIXME // there's still no way to determine if the dialog is showed //assertTrue("exception raised and handled", activity.getFahrenheitEditText().getText().toString().contains("below absolute zero")); } public static void main(String[] args) { try { positron.RunTests.main(new String[] {"com.codtech.android.tdd", ".Positron", "/opt/java/android-sdk"}); } catch (IOException e) { e.printStackTrace(); } } }

Now, we can run our Acceptance Test just running OverallTest as a Java Application (right click, RunAs -> Java Application).
In order for this to work we need TemperatureConverter installed into the emulator and the emulator running.
The Instrumentation will run, and just before finishing, Positron will be broadcasting and intent ("positron.action.VIEW_TEST_RESULTS")which is then received by an IntentReceiver which in turn start an activity.

Here, there are tow videos that show the process of launching the tests from Dev Tools.





Monday, March 03, 2008

Android: Playing with Intents

Android: Playing with Intents



Android Intent Playground 2.0 is described in this post:
Diego Torres Milano's blog: Android Intent Playground 2.0

This document is published at http://docs.google.com/Doc?id=ddwc44gs_31gjdwgxfh

WARNING: Blogspot engine change the link, so you have to cut and paste.

Intents

From Google documentation: “An Intent is a simple message object that represents an ”intention” to do something. For example, if your application wants to display a web page, it expresses its “Intent” to view the URI by creating an Intent instance and handing it off to the system. The system locates some other piece of code (in this case, the Browser) that knows how to handle that Intent, and runs it. Intents can also be used to broadcast interesting events (such as a notification) system-wide.”

An intent is an abstract description of an operation to be performed. It can be used with startActivity to launch an Activity, broadcastIntent to send it to any interested IntentReceiver components, and startService(Intent, Bundle) or bindService(Intent, ServiceConnection, int) to communicate with a background Service.

Intents are one of the most distinctive components of the Android platform. They, among other things, provide the transition between Activities in a similar model as the browser model, where the user navigates from on page to the other and can also go back.

Intent attributes

Intent
primary attributes
action The general action to be performed, such as VIEW_ACTION, EDIT_ACTION, MAIN_ACTION, etc.
data The data to operate on, such as a person record in the contacts database, expressed as a Uri.
secondary attributes
category Gives additional information about the action to execute. For example, LAUNCHER_CATEGORY means it should appear in the Launcher as a top-level application, while ALTERNATIVE_CATEGORY means it should be included in a list of alternative actions the user can perform on a piece of data.
type Specifies an explicit type (a MIME type) of the intent data. Normally the type is inferred from the data itself. By setting this attribute, you disable that evaluation and force an explicit type.
component Specifies an explicit name of a component class to use for the intent. Normally this is determined by looking at the other information in the intent (the action, data/type, and categories) and matching that with a component that can handle it. If this attribute is set then none of the evaluation is performed, and this component is used exactly as is. By specifying this attribute, all of the other Intent attributes become optional.
extras - This is a Bundle of any additional information. This can be used to provide extended information to the component. For example, if we have a action to send an e-mail message, we could also include extra pieces of data here to supply a subject, body, etc.

The best way to explore all of this somewhat complex model is to try it out.

Official Google documentation has some mistakes that turn your exploration a bit more difficult.

For example in http://code.google.com/android/reference/android/content/Intent.html there are some mistakes.

Some examples of action/data pairs are: * VIEW_ACTION content://contacts/1 – Display information about the person whose identifier is “1”. * EDIT_ACTION content://contacts/1 – Edit information about the person whose identifier is “1”. * VIEW_ACTION content://contacts/ – Display a list of people, which the user can browse through. This example is a typical top-level entry into the Contacts application, showing you the list of people. Selecting a particular person to view would result in a new intent { VIEW_ACTION content://contacts/N } being used to start an activity to display that person. * PICK_ACTION content://contacts/ – Display the list of people, allowing the user to browse through them and pick one and return it to the parent activity. This could be used, for example, if an e-mail application wanted to allow the user to pick a person

Should say

  • VIEW_ACTION content://contacts/people/1 – Display information about the person whose identifier is “1”.
  • EDIT_ACTION content://contacts/people/1 – Edit information about the person whose identifier is “1”.
  • VIEW_ACTION content://contacts/people/ – Display a list of people, which the user can browse through. This example is a typical top-level entry into the Contacts application, showing you the list of people. Selecting a particular person to view would result in a new intent { VIEW_ACTION content://contacts/people/N } being used to start an activity to display that person.
  • PICK_ACTION content://contacts/people/ – Display the list of people, allowing the user to browse through them and pick one and return it to the parent activity. This could be used, for example, if an e-mail application wanted to allow the user to pick a person

Intent Playground

To explore these relationships, I've made a simple application that allows you to play with Intents and see the results.

This application completes the intent attributes with the information provided and sometimes with other information obtained from it and then invokes the corresponding method.



AndroidIntentPlayground source and binaries can be downloaded from AndroidIntentPlayground.zip.





action

It's an AutoCompleteTextView to select an action from the list (try entering the first letter as a or c. You can also enter the desired action if it's not in the list.

See Intent attributes.

data uri

The data Uri to act on.

See Intent attributes.

type

The MIME type.

See Intent attributes.

intent

Select the kind of method to invoke

  • broadcast: use broadcastIntent(intet)
  • activity: use startActivity(intent)
  • service: use startService(intent, null)
  • resolve: use Intent.resolveActivity()

activity

When the activity is resolved the class name is showed here.

button

Press the button to send the Intent.

Examples

broadcast

Our first example is an intent broadcast. If there's an IntentReceiver which has an Intent that matches our attributes then its onReceiveIntent() method will be invoked. I've explained how to create an IntentReceiver only application in a previous post. If you have installed the SampleIntentReceiver.APK, we can try this example.

Set action to com.codtech.android.training.intent.SAMPLE_ACTION, leave the other fields blank and the intent to broadcast. Press the button and you'll see


Explanation

If we try to resolve the Intent we will receive an error, but broadcasting the intent it reaches our SampleIntentReceiver and the dialog is displayed.

This intent filter in our SampleIntentReceiver AndroidManifest.xml matches

        <receiver android:name=".SampleIntentReceiver">
<intent-filter>
<action android:name="com.codtech.android.training.intent.SAMPLE_ACTION"/>
</intent-filter>
</receiver>

start activity

Set action to android.intent.action.VIEW and data uri to content://contacts/people/1, set intent to activity and the press the button.


Explanation

After pressing the button we will see our first contact displayed.

We hit a bug. It's not possible to obtain the screenshot using DDMS, instead of the contact the previuos activity is displayed.

The intent was resolved to com.google.android.contacts.ViewContactActivity and this activity is invoked by startActivity(intent).

service

You need ApiDemos installed for this to work.

Set action to com.google.android.samples.app.REMOTE_SERVICE, leave the other fields blank, and set intent to service. Press the button.

And the RemoteService will be started.

The triangle in the upper left corner indicates that the service is running.

However, if we try to stop the service, opening the sliding top panel and clicking on SampleRemoteService instead of getting this service activity we'll get LocalServiceController and we cannot stop it. Another bug ?

Workaround: Go to Home, ApiDemosAppServiceRemote Service Controller and the Stop Service.

Explanation

The intent is used as a parameter of startService(intent, null) and the requested service is started if needed.

resolve

Se action to android.intent.action.WEB_SEARCH, enter some strings into data uri, for example cult thinclient. Set intent to resolve and the press the button. We can see that the action is resolved to com.google.android.browser.BrowserActivity. If we further change intent to activity and press the button again, we will see the browser starting and doing the Google search.

Explanation

The activity is resolved to the corresponding class, and if we use this information to start the activity we will see the web browser presenting the search.

Conclusion

This application, Intent Playground, has helped me a lot to discover some interactions between the Android components.

However, I was not able to intercept the intent used by the contacts application when you try to edit its photo. Could it be using an intent instantiated used an explicit class com.google.android.fallback.Fallback ?

Do you have any clue ?