Tuesday, December 22, 2009

Android Testing: ContentProvider integration tests using mock con

Android Testing: ContentProvider integration test using mock content



















This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_221tm2kqmgp), cut and paste link if you have problems accessing it.












But when we try to produce functional or integration tests of your Activities against your ContentProvider it's not so evident what test case to use. The most obvious choice is  ActivityInstrumentationTestCase2 mainly if your functional tests simulate user experience because you may need sendKeys() or similar methods, which are readily available on these tests.





The first problem you may encounter is that's no clear where to inject a MockContentResolver in your test to be able to use a test database instance or database fixture with your ContentProvider. There's no way to inject a MockContext either.


We have visited Mock Objects before and evaluated Test Driven Development. You may want to take a look if you are not familiar with mock objects on Android.

An alternative approach to this problem is introduced by tenacious33 in Android functional testing with MockContext.







mocking content provider operations


In order to create an ActivityInstrumentationTestCase2 and be able to mock content provider operations and optionally use a database fixture we need a different approach.



















Download:

Binaries and source code of
android-mock and MockContentResolverActivity main and test sample projects can be downloaded from http://sites.codtech.com/android/Home/source-code




android-mock



android-mock is a library encapsulating some of the needed functionality to simplify functional and integration tests of activities that use content providers.

We are introducing the library here and we will be presenting a practical example later.
This is the UML Class Diagram of the library. Green classes belong to android library.













ActivityAndContentProviderInstrumentationTestCase2<T extends Activity, P extends ContentProvider>


This is a functional or integration test of an Activity that uses a MockContextWithMockContentResolver context and uses the specified ContentProvider under tests.

You should extend this class to create your tests and everything will be setup automatically.

During the setup process the abstract method createDatabaseFixture(Context mockContext) is invoked to give you the opportunity of creating your database fixture.

The last step of the setup process invokes your Activity's onCreate() method.

MockContextWithMockContentResolver<P extends ContentProvider>


A mock context, with a mock content resolver using the specified ConentProvider under test. The mock context is a RenamingDelegatingContext that renames file and database operations prefixing them with "test." by default.

This provides a way to inject a ContentResolver into an ActivityAndContentProviderInstrumentationTestCase2 permitting the introduction of database fixtures.

GenericMockContentResolver<P extends ConentProvider>


A generic mock content resolver using the specified ConentProvider under test. This content resolver uses the specified content provider to resolve data for authority.



examples


To demonstrate the use of this library we have an Activity that presents some data using a ContentProvider. They are very simple an similar to other examples with the same functionality. In this case we are using a content provider with authority on a simple table Samples.AUTHORITY.
Our Activity in the following example is MyMockContentResolverActivity and the content provider is MyContentProvider.








We will concentrate now on the tests.

content provider unit tests


This tests will be as usual implemented using ProviderTestCase2<P>. This is pretty standard so we are not pay much attention to it.



activity and content provider functional tests



We reach the point. MyMockContentResolverActivityTests class implements the tests extending ActivityAndContentProviderInstrumentationTestCase2 and specifying the Activity and ContentProvider under test as well as the authority, Samples.AUTHORITY in this particular example.
These tests can be run completely isolated from real data, so you can be confident and exercise your database through your Activity not worrying about your real data.















/*
 * Copyright © 2009 COD Technologies Ltd.  www.codtech.com
 * 
 * $Id$
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.codtech.android.training.mockcontentresolver.examples.test;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.TextView;

import com.codtech.android.mock.ActivityAndContentProviderInstrumentationTestCase2;
import com.codtech.android.training.mockcontentresolver.examples.MyMockContentResolverActivity;
import com.codtech.android.training.mockcontentresolver.examples.provider.MyContentProvider;
import com.codtech.android.training.mockcontentresolver.examples.provider.Samples;

public class MyMockContentResolverActivityTests extends
ActivityAndContentProviderInstrumentationTestCase2<MyMockContentResolverActivity,
                    MyContentProvider> {

private static final String TEST_CASE_1 = "test case 1";
private static final String TAG = "MyMockContentResolverActivityTests2";

public MyMockContentResolverActivityTests(String name) {
super(MyMockContentResolverActivity.class.getPackage().getName(),
                    MyMockContentResolverActivity.class, MyContentProvider.class,
                    Samples.AUTHORITY);
setName(name);
}

@Override
public void createDatabaseFixture(Context mockContext) {
final ContentResolver cr = mockContext.getContentResolver();
cr.delete(Samples.CONTENT_URI, " 1 = 1 ", null);
final ContentValues cv = new ContentValues();
cv.put(Samples.NAME, TEST_CASE_1);
final Uri uri = cr.insert(Samples.CONTENT_URI, cv);
assertNotNull(uri);
}

public void testDatabaseFixture() {
final Activity activity = getActivity();
final ContentResolver cr = activity.getContentResolver();
final Cursor c = cr.query(Samples.CONTENT_URI, new String[] { Samples._ID },
                    null, null, null);
assertTrue(c.getCount() != 0);
c.close();
}
public void testSimpleCreate() {
final Activity activity = getActivity();
assertNotNull(activity);
final TextView tv = (TextView) activity.findViewById(
                    com.codtech.android.training.mockcontentresolver.examples.R.id.TextView01);
assertNotNull(tv);
}

public void testDatabaseTestCase1() {
final Activity activity = getActivity();
assertNotNull(activity);
TextView tv = (TextView) activity.findViewById(
                    com.codtech.android.training.mockcontentresolver.examples.R.id.TextView01);
assertNotNull(tv);
assertEquals("Cursor returns 1 row\n" + TEST_CASE_1, tv.getText().toString());
sendKeys(KeyEvent.ACTION_DOWN);
}
}



createDatabaseFixture

Firstly, the database is cleaned up to start the test from a know base. Strictly speaking this could not be necessary but we prefer to err on the safe side.
Secondly, a single row is inserted to materialize our fixture.
Remember this is an abstract method in ActivityAndContentProviderInstrumentationTestCase2.

testDatabaseFixture

Simple test, just to verify that our database fixture is correct.

testSimpleCreate

Verify that our Activity has been created correctly and everything is in place.

testDatabaseTestCase1

Functional test, we verify the data presented in the Activity, check that the information is the expected one and finally we send key events simulating user interaction.
In this test we are not using the ContentResolver nor the database directly and we see everything from the Activity's perspective.





conclusion

Though this is an oversimplified example it shows the most important concepts behind android-mock library classes and how you can implement your tests relying on it.
Comments are gladly welcome.






Copyright © 2009 Diego Torres Milano. All rights reserved.

Monday, December 07, 2009

Android Testing: External libraries

We are now applying all the techniques discussed in previous posts to test our android projects.

However, all the projects reviewed were so simple that no one used external libraries so we never faced the problem of using them from our tests.

In this example we are using a dummy external library we have called libdummy-0.0.1-SANPSHOT.jar. In our Activity we are creating an object from one of the library's classes.

When we write the tests and we need to resolve the references to that objects we may feel tempted to include libdummy in test project's external jars, but doing so

[2009-12-07 16:01:14 - TCTDD11Test]Launching instrumentation android.test.InstrumentationTestRunner on device emulator-5554
[2009-12-07 16:01:25 - TCTDD11Test]Test run failed: Class ref in pre-verified class resolved to unexpected implementation
[2009-12-07 16:01:25 - TCTDD11Test]Test run complete


we receive the previous error.

The solution is to export the external jar from the main project. Open project Properties and edit Java Build Path to include the external library



and export it to be contributed to dependent test project



and this will cause no problems running the tests.

Update: Android Compatibility library: Testing Fragments
The aforementioned solution can also be applied to the android-support.jar, the library needed to support Fragments in pre-Honeycomb versions. As before, this should be exported in the main project and used in tests.


Hope this helps.


Update: Using ant
Sometimes you may want to use ant to build your projects, for example when you are building using a Continuous Integration server like Hudson, Jenkins, Bamboo, CruiseControl or similar.
To achieve this goal the easiest way is to update the main and test projects using the android update project and android update test-project commands.
The missing step using this approach is the inclusion of the external libraries we used in our previous examples. In the libs directory of the main project include the used libraries. It's preferable to create symbolic links so they are automatically updated if you update the libraries by any mean.
This is how the libs directory will look like:


$ ls -l libs
total 16
lrwxr-xr-x 1 diego staff 82 1 Aug 18:13 android-support-v4.jar -> ../../../../opt/android-sdk/extras/android/compatibility/v4/android-support-v4.jar
lrwxr-xr-x 1 diego staff 46 1 Aug 18:29 libdummy-0.0.1-SNAPSHOT.jar -> ../../libdummy/lib/libdummy-0.0.1-SNAPSHOT.jar

Having done so, you will be able to create you application using ant debug and run the tests by ant run-tests and the libraries will be automatically included in the test project.

Friday, December 04, 2009

Android Testing at Droidcon London 2009


I would like to thank Skills Matter and Novoda for the invitation to present the Android Testing tutorial at Droidcon London 2009.
A very successful event indeed, and a lot of interesting talks were presented.
Presentation slides and other resources can be found at http://android.codtech.com/droidcon2009 or if you prefer at slideshare.

I would also thank Okpala Ikenna N. Jr., who took this and other great pictures of the event that can be seen at flickr.

android-fixperms updated

Android SDK 2.0.1 was released including minor API changes, bug fixes and framework behavioral changes. For information on changes and fixes, see the Framework API section.
If you install Android SDK in a multi-user environment or in a continuous integration server, as was showed in previous posts, you should correct the permissions after updating. That is android-fixperms's raison d'ĂȘtre and as usual, it was updated to support this new SDK version.

Find more information and examples at http://sites.codtech.com/android/android-tools.

Friday, November 27, 2009

Save the date: Android Testing at Droidcon London 2009

Mark your calendars. On December 2, 2009, 17:00 I will be presenting Testing on Android a presentation that gives an introduction to Test Driven Development and Behaviour Driven Development on the Android platform, techniques that every serious development project should at least consider.

More information at Droidcon London 2009

Friday, November 13, 2009

Android Testing: Mock Contexts

AndroidTesting: Mock Context
















This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_21737cgtvfj), cut and paste link if you have problems accessing it.







Continuing with our Android Testing series, we take a short detour before we proceed to create more unit and functional tests.

We have visited Mock Objects before and evaluated our Test Driven Development purism and the concerns about not using real objects, but this is another discussion.

Sometimes, introducing mock objects in our tests is recommended, desirable, useful or even unavoidable.

Android SDK provides some classes to help us in this quest in the package android.test.mock:







mock context overview


Mock Context class implements all methods in a non-functional way and throw UnsopportedOperationException.

This can be used to inject other dependencies, mocks, or monitors into the classes under testing. A fine control can be obtained extending this class.



Extend this class to provide your desired behaviour overriding the correspondent methods.





mocking file and database operations


In some cases, all we need is to be able to mock the file and database operations. For example, if we are testing the application on a real device perhaps we don't want to affect existing files during our tests.

Such cases can take advantage of another class that is not part of the android.test.mock package but of android.test instead, that is RenamingDelegatingContext. I think it belongs to the former but it is in the parent package for apparently no reason.

This class let us mock operations and files and databases are prefixed by a prefix that is specified in the constructor. All other operations are delegated to the delegating Context that you must specify in the constructor too.



Suppose our Activity under test uses some files we want to control in some way, maybe introducing specialised content to drive our tests and we don't want or we can't use the real files, in this case we create a RenamingDelegatingContext specifying a pefix, we provide that files, and our unchanged Activity will use them.



Our Activity under test, MockContextExamplesActivity, displays the content of a file inside a TextView, what we intend is to be able to display different content during a test than during normal operation of the Activity.



MockContextExamplesActivity.java


A very simple Activity. Shows the content of the file myfile.txt inside a TextView.
















package com.codtech.android.training.mockcontext.examples;



import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;



import android.app.Activity;

import android.graphics.Color;

import android.os.Bundle;

import android.widget.TextView;



public class MockContextExamplesActivity extends Activity {

    public final static String FILE_NAME = "myfile.txt";

    

    private TextView tv;

    

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        

        tv = (TextView) findViewById(R.id.TextView);

        final byte[] buffer = new byte[1024];

        

        try {

            final FileInputStream fis = openFileInput(FILE_NAME);

            final int n = fis.read(buffer);

            tv.setText(new String(buffer, 0, n-1));

        } catch (Exception e) {

            tv.setText(e.toString());

            tv.setTextColor(Color.RED);

        }

    }

    

    public String getText() {

        return tv.getText().toString();

    }

}






MyMockContext.java















import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;



import android.content.Context;

import android.test.RenamingDelegatingContext;

import android.util.Log;





/**

 * @author diego

 *

 */

public class MyMockContext extends RenamingDelegatingContext {

    private static final String TAG = "MyMockContext";

    

    private static final String MOCK_FILE_PREFIX = "test.";





    /**

     * @param context

     * @param filePrefix

     */

    public MyMockContext(Context context) {

        super(context, MOCK_FILE_PREFIX);

        makeExistingFilesAndDbsAccessible();

    }



    /* (non-Javadoc)

     * @see android.test.RenamingDelegatingContext#openFileInput(java.lang.String)

     */

    @Override

    public FileInputStream openFileInput(String name)

            throws FileNotFoundException {

        Log.d(TAG, "actual location of " + name +

            " is " + getFileStreamPath(name));

        return super.openFileInput(name);

    }



    /* (non-Javadoc)

     * @see android.test.RenamingDelegatingContext#openFileOutput(java.lang.String, int)

     */

    @Override

    public FileOutputStream openFileOutput(String name, int mode)

            throws FileNotFoundException {

        Log.d(TAG, "actual location of " + name +

            " is " + getFileStreamPath(name));

        return super.openFileOutput(name, mode);

    }

}









test.myfile.txt


We also need to provide at least the text file containing the sample text.

The directory where these files are located is created during package installation, so you may want to wait till you install the package for the first time to run this command.

Notice that we are using a "test." prefix in this file creation as we did in our MyMockContext class.

To do this just run this command:
















diego@bruce:\~$ adb shell echo "This is sample text" \

   \> /data/data/com.codtech.android.training.mockcontext.examples/files/test.myfile.txt





Remember to change package name if you are using a different one.



If you also want to create some real context to be displayed by the Activity use this command:
















diego@bruce:\~$ adb shell echo "This is REAL text" \

   \> /data/data/com.codtech.android.training.mockcontext.examples/files/myfile.txt





Notice that here there's no "test." prefix because it's the real file.





MockContextExamplesTest.java


Now when we create some tests we can use this mock context.
















import android.content.Context;

import android.content.Intent;

import android.test.ActivityUnitTestCase;



import com.codtech.android.training.mockcontext.examples.MockContextExamplesActivity;



/**

 * @author diego

 *

 */

public class MockContextExamplesTests extends ActivityUnitTestCase<MockContextExamplesActivity> {



    public MockContextExamplesTests() {

        super(MockContextExamplesActivity.class);

    }



    /**

     * @throws java.lang.Exception

     */

    protected void setUp() throws Exception {

    }



    /**

     * @throws java.lang.Exception

     */

    protected void tearDown() throws Exception {

    }



    public void testSampleTextDisplayed() {

        // mock context

        Context mockContext = new MyMockContext(getInstrumentation().getTargetContext());

        setActivityContext(mockContext);

        startActivity(new Intent(), null, null);

        final MockContextExamplesActivity activity = getActivity();

        assertNotNull(activity);

        assertEquals("This is sample text", activity.getText());

    }

    

    public void testRealTextDisplayed() {

        // real context

        setActivityContext(getInstrumentation().getTargetContext());

        startActivity(new Intent(), null, null);

        final MockContextExamplesActivity activity = getActivity();

        assertNotNull(activity);

        assertFalse("This is sample text".equals(activity.getText()));

    }

}












Conclusion


Though this is an oversimplified example it shows the most important concepts behind Mock Contexts and the way you can use Delegating Context to isolate your Activity from other parts of the system like the filesystem in this case.






Copyright © 2009 Diego Torres Milano. All rights reserved.

























































Friday, November 06, 2009

Android Testing: Instrumentation name

I was asked this question many times, so I think it should be present in a kind of FAQ in the future. Meanwhile...

Q: Where does the Instrumentation name come from ?
We here refer to the name that appears on the Instrumentation list in DevTools -> Instrumentation as showed in this screenshot.
By default we see android.test.InstrumentationTestRunner for all of our tests, so it would be very difficult to identify them



A: From the attributes of android.test.InstrumentationTestRunner in AndroidManifest.xml
That is, if the Label is not set in the manifest the default name is used. To set something meaningful for your tests set the label. In this case we shot Eclipse Android ADT manifest editor screen.

Wednesday, October 28, 2009

Upgrading to android SDK 2.0

This time the upgrade is much simpler than with previous versions. Upgrading is now supported from inside android tools itself.
Assuming you have already configured your installation to be able to install as root and run as unprivileged users as was described in previous posts, upgrading is a simple task, just run

$ sudo android


the select the desired components to install (i.e.: SDK Platform Android 2.0) and click Install Selected

Once you upgrade you still need to fix the permissions again, to do it run

$ wget -qO - http://android.codtech.com/android-tools/android-fixperms | sudo bash -s -- --sdk --sdk-dir=/opt/android

If you have installed android SDK to a different directory replace /opt/android in previous command by the correct location.

Hope this helps.

Tuesday, October 27, 2009

Japan Linux Symposium 2009 pictures





Photos from the Japan Linux Symposium. Some of them were taken at the VIP Reception at the MADO LOUNGE, which is located on the 52nd floor of Mori Tower in the Roppongi Hills. From 250 meters above sea-level, you can enjoy amazing views of Tokyo.

One of the photos taken by Keiko, who sent me the photo later, shows Klaas, Linus and me. There are other photos of Tokyo from the tower. They were taken with my Android HTC Hero, so you may understand they are not the best.
Finally, photos from the presentation that were kindly taken by Scott Trent.

Sunday, October 25, 2009

Android Tutorial at 1st Japan Linux Symposium

I would like to thank Linux Foundation for the invitation to present the Android Tutorial at the 1st Japan Linux Symposium.
A successful event indeed, and I was gladly surprised by all the interest in Android development attendees demonstrated.
Some requested me the presentation slides and I wrongly assumed they could be located at the official site. My fault. Anyway, you can find the presentation and other resources at http://android.codtech.com/jls2009 or if you prefer at slideshare.

This newest Linux Foundation event in Asia Pacific brought together developers, managers, executives, students and end users from Japan, Korea, China and many International countries.

I have had a great time in Tokyo and I hope to be here for 2010 too.

I would also thank Scott Trent, who took this and other great pictures of the event. You can find them at day 1, day 2 and day 3.

Friday, October 09, 2009

Android SDK: one step closer

Starting with the latest android 1.6 SDK release 1, Google is distributing the Linux version as a TGZ file instead of ZIP.
We mentioned the problem of distributing the Linux version as a ZIP file in previous posts, loosing ownerships and file permissions is a problem when you install the SDK or NDK in a multiuser environment.

Well, we are halfway there, still there's some permission problems that you can fix using android-fixperms that can be downloaded from this site.

Use it like this, for example to change the SDK installed at /opt/android-sdk-linux_x86-1.6_r1

$ android-fixperms --sdk --sdk-dir=/opt/android-sdk-linux_x86-1.6_r1

Hope this helps.

Wednesday, September 23, 2009

Android: android.process.acore has stopped

I've started to receive the infamous message: The process android.process.acore has stopped unexpectedly. Please try again. Is this the android counterpart of the blue screen of death ?


Lately I'm receiving this annoyingly often. Googling for it you can find some links giving some solutions I found unacceptable like hardware resetting the phone.

Notice: There could be many causes for this problem, so follow the steps presented here to determine if you are in this case.

Hence, I decided to take some action and find the reason. After some debugging this is what I've found:
09-23 16:32:13.798: DEBUG/Sync(110): skipping photo edit for unsynced contact
09-23 16:32:13.938: WARN/dalvikvm(110): threadid=37: thread exiting with uncaught exception (group=0x4000fe70)
09-23 16:32:13.938: ERROR/AndroidRuntime(110): Uncaught handler: thread SyncThread exiting due to uncaught exception
09-23 16:32:13.948: ERROR/AndroidRuntime(110): java.lang.NullPointerException
09-23 16:32:13.948: ERROR/AndroidRuntime(110): at com.android.providers.contacts.ContactsSyncAdapter.cursorToContactsElement(ContactsSyncAdapter.java:846)

Unfortunately the problem, a NullPointerException, is in ContactsSyncAdapter.java, a piece of Google maintained code AFAIK and we can only watch it happens and no much more than finding a workaround. Before introducing the workaround let's analyze one of the possible reasons for this problem.

Last message before the exception is

skipping photo edit for unsynced contact

so the problem might be an edition of a contact photo that can't be synced.

On possible workaround is to disable Auto-sync for Google data.
Try to synchronize Gmail, Calendar and Contacts and check if you receive the android.process.acore has stopped unexpectedly message.

Fortunately you can just disable Contacts synchronization. You should remember to synchronize manually your Contacts data until you find another solution, an upgrade is provided by Google, you can delete all of your Contacts and hope for the best or hard reset your phone.


Hope this helps.

Friday, September 04, 2009

Android: Sharing your mobile connection with your computer (tethe









This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_209f9dhxqd6), cut and paste link if you have problems accessing it.






This post shows how easy is to share your mobile network to your Ubuntu 9.04 computer (should work with other Linux distributions as well) using your HTC Hero.
Tethering with no rooting, no drivers to install (as with other OSs), no complex settings...
Too boring, it just works.

enable mobile network sharing in your phone

 
for this option to work USB cable must be connected






that's all

Your phone was detected as an USB network adapter and NetworkManager did all the dirty work for you.
Your computer is now connected to the mobile network !






Conclusion


If everything works just like that this blog would become very boring :-)



If you have comments, ideas, critiques or whatever just drop me a line or leave a comment in the blog.




Copyright © 2009 Diego Torres Milano. All rights reserved.



















































Tuesday, September 01, 2009

Fixing Android NDK permissions

Lately Linux is being treated more as a kind of Windows-like than a UNIX-like operating system it really is.
It seems that everybody has forgotten it is a multiuser operating system and most likely you don't want to install a single copy of every software just because some permissions are wrongly set mainly due to be distributed as a ZIP file which doesn't store this information.

Fixing Android SDK permissions was described in Ooops!..., they did it again. Here, we are fixing Android NDK permissions for your multi-user development environment.

Android NDK is a companion tool to the Android SDK that lets Android application developers build performance-critical portions of their apps in native code.

Other problem you may find during installation in Debian and Ubuntu is that Android NDK installation script invokes /bin/sh expecting to be bash. If you use bash syntax you should invoke it using /bin/bash. This problem will be fixed in next release.

Use this script to fix all of the problems, after unzipping as root:

#! /bin/bash


NDK_DIR=${NDK_DIR:-/opt/android-ndk-1.5_r1}

sudo chmod a+rx $NDK_DIR
sudo find $NDK_DIR/ -type d -exec chmod a+rx {} \;
sudo find $NDK_DIR/ ! -perm -044 -exec chmod g+r,o+r {} \;
sudo find $NDK_DIR/ -type d -name bin -exec chmod -R a+x {} \;
[ ! -d $NDK_DIR/out/apps ] && sudo mkdir $NDK_DIR/out/apps
sudo chmod a+rwx $NDK_DIR/out/apps
( cd $NDK_DIR && sudo bash build/host-setup.sh )


Hope this helps.

Monday, August 24, 2009

Google Chrome in the clouds

Recently, you may have heard a lot about Google Chrome OS, an open source, lightweight operating system that will initially be targeted at netbooks that will provide a platform to run Google Chrome browser.
This will be available for consumers in the second half of 2010 and some time earlier for developers.
Well, if you impatience triggers and you can't wait so long, here is something that may help you cope with it.


This video demonstrates a cult thin client google chrome edition software booting straight to a Google Chrome browser.





IMPORTANT: This is not Google Chrome OS and is not related in any way with Google.



The main idea behind cult google chrome edition is to move everything to the cloud, even the operating system and configuration. This started as a cult stress test, which is a platform to further develop and customize thin clients Operating Systems.
cult google chrome edition it's initially targeted at thin clients, however it can be used by other classes of hardware and virtual machines as well. Using network boot, provides a platform to run Google Chrome browser too, but avoiding OS installation.
To achieve this goal cult google chrome edition is kept as small and fast as possible.

An early preview, as showed in the video, containing the most fundamental features but there's still a lot to be done.
A LiveCD/LiveDVD ISO image (only 35MB) is available for download. This same image can also be used to network boot thin clients, PCs or virtual machines as explained in the documentation.
Download it from http://sourceforge.net/projects/cult-thinclient/files/cult-classic-custom/cult-google-chrome-edition/cult-3.1-classic-custom-tiny-google-chrome.iso/download, enjoy it and send you comments and suggestions to improve future versions.

More in depth information can be found at http://cult.codtech.com, specifically here.

Tuesday, August 18, 2009

AutoAndroid: Pizza order sample









This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_203g7xcxfr9), cut and paste link if you have problems accessing it.






We analyzed the basic concepts behind autandroid in a previous post, now let's introduce a slightly more interesting example.


pizza order example












This sample includes autoandroid.jar and can be downloaded from http://codtech.com/downloads/android/index.html#source as an Eclipse project (AutoAndroidSamples.zip).





This introductory example provides some automatic behavior of UI components.

Our objective is to obtain, writing as less code as possible, the following functionality.



A standard Activity displays some Buttons to launch different samples. Right now we have only two Buttons corresponding to our samples.













Clicking the Pizza order samples Button launches the Dialog.











Some automatic behavior has been defined in the XML file and thus it's automatically available in the Dialog.

This behavior includes:



  • If quantity is 0, then OK is disabled


  • Quantity value is automatically updated depending on the seek bar position



  • All values are exported so they can be retrieved from the parent Activity


  • Cancel and OK buttons have the corresponding default behavior




Once some values are entered, pressing the OK Button the Dialog is dismissed and the values are passed back to the invoking Activity.











pizza_order_sample.xml


This is the layout of our sample dialog.

We can do it as we normally do using ADT's Layout Editor.













However, to provide the extra behavior we have to add some properties in the XML view of the editor.










<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:auto="http://schemas.android.com/apk/res/com.codtech.android.samples.autoandroid"

    android:orientation="vertical" android:layout_height="fill_parent"

    android:layout_width="300dip"  

    android:id="@+id/LinearLayoutPizzaOrder">



    <com.codtech.android.auto.widget.AutoRadioGroup

        android:id="@+id/RadioGroupPizza"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        auto:export="true"

        auto:name="pizza">

        <RadioButton android:id="@+id/RadioButton01"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="6dip" android:text="Margherita"

            android:layout_marginTop="-6dip" android:checked="true">

        </RadioButton>

        <RadioButton android:id="@+id/RadioButton02"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="6dip" android:text="Prosciutto">

        </RadioButton>

        <RadioButton android:id="@+id/RadioButton03"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="6dip"

            android:text="Quattro Stagioni">

        </RadioButton>

    </com.codtech.android.auto.widget.AutoRadioGroup>

    

    <TextView android:id="@+id/TextView02"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" android:text="Additionals"

        android:textStyle="bold" android:layout_marginLeft="3dip"

        android:layout_marginTop="3dip"></TextView>

        

    <com.codtech.android.auto.view.AutoCheckBox 

        android:id="@+id/CheckBoxExtraMozzarella"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_marginLeft="6dip"

        android:text="Extra mozzarella"

        auto:export="true">

    </com.codtech.android.auto.view.AutoCheckBox>

        

    <com.codtech.android.auto.view.AutoCheckBox

        android:id="@+id/CheckBoxPepperoni"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_marginLeft="6dip"

        android:text="Pepperoni"

        auto:export="true">

    </com.codtech.android.auto.view.AutoCheckBox>

        

    <TextView android:id="@+id/TextView03"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" android:text="Quantity"

        android:textStyle="bold" android:layout_marginLeft="3dip"

        android:layout_marginTop="3dip"></TextView>



    <LinearLayout android:id="@+id/LinearLayout02"

        android:layout_height="wrap_content"

        android:layout_width="fill_parent"

        android:orientation="horizontal">

        <com.codtech.android.auto.view.AutoSeekBar 

            android:layout_height="wrap_content"

            android:layout_margin="6dip"

            android:layout_width="wrap_content"

            android:layout_weight="1" android:id="@+id/SeekBarQuantity"

            auto:update="@+id/TextViewQuantity"

            android:max="10"

            auto:export="true"

            auto:name="quantity"

            auto:sensitize="@+id/ButtonPizzaOrderDialogOk">

        </com.codtech.android.auto.view.AutoSeekBar>

        <TextView android:layout_width="wrap_content"

            android:layout_weight="0" android:layout_margin="6dip"

            android:layout_height="fill_parent"

            android:layout_gravity="center"

            android:gravity="center_vertical|right"

            android:textStyle="bold" android:id="@id/TextViewQuantity"

            android:maxLength="3" android:text="0"

            android:background="#555555"

            ></TextView>

    </LinearLayout>

    

    <RelativeLayout android:id="@+id/LinearLayout01"

        android:layout_height="wrap_content"

        android:layout_width="fill_parent"

        android:layout_margin="3dip"

        android:background="@color/dialog_action_background">

        <Button android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_alignParentLeft="true"

            android:text="Cancel" android:width="100dip"

            android:id="@+id/ButtonPizzaOrderDialogCancel"

            auto:dialog_action="cancel"

            android:layout_margin="3dip"></Button>

        <Button android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_alignParentRight="true"

            android:text="OK" android:width="100dip"

            android:id="@id/ButtonPizzaOrderDialogOk"

            auto:dialog_action="positive"

            android:clickable="false"

            android:enabled="false"

            android:layout_margin="3dip"

            ></Button>

    </RelativeLayout>



</LinearLayout>







Let's explain the changes:




  1. Define the namespace auto. The name that appears after http://schemas.android.com/apk/res is usually the package name of your application and will be used in an attribute definition (attrs.xml).


  2. The root layout must have an ID as it's used by some methods in the library and should be identifiable. The name here is LinearLayoutPizzaOrder but it could be whatever you like.


  3. Then, a com.codtech.android.auto.widget.AutoRadioGroup which is a class that extends RadioGroup and provides some automatic behavior.


  4. Mark the AutoRadioGroup as exported so we can obtain its value later. This is achieved using auto:export="true".



  5. As it is exported we give it a name, text in this case, to obtain the value later. auto:name="pizza" does the trick.



  6. Two AutoCheckBoxes,also exported hold additional options.


  7. An AutoSeekBar, exported under the name "quantity" provides also two other options, automatically update a text field depending on the seek bar value using auto:update="@+id/TextViewQuantity". It also automatically sensitize the OK Button using auto:sensitize="@+id/ButtonPizzaOrderDialogOk".


  8. Finally, mark the buttons using auto:dialog_action="positive" and auto:dialog_action="cancel".



Styleable attributes


The way we are adding these attributes is by defining styleable attributes in a file usually attrs.xml.










<?xml version="1.0" encoding="utf-8"?>



<resources>

<declare-styleable name="com.codtech.android.samples.autoandroid">

    <attr name="name" format="string" />

    <attr name="init" format="string" />

    <attr name="sensitize" format="reference" />

    <attr name="show" format="reference" />

    <attr name="update" format="reference" />

    <attr name="export" format="boolean" />

    <attr name="dialog_ok" format="boolean" />

    <attr name="dialog_action" format="string" />

</declare-styleable>

</resources>




AutoAndroidSamples.java


This is our sample Activity.










/*

 * Copyright © 2009 COD Technologies Ltd.  www.codtech.com


 *


 * $Id: AutoAndroidSamples.java 131 2009-08-15 00:28:47Z diego $


 *


 *


 */




package com.codtech.android.samples.autoandroid;




import android.app.Activity;


import android.app.Dialog;


import android.content.DialogInterface;


import android.content.DialogInterface.OnDismissListener;


import android.os.Bundle;


import android.view.View;


import android.view.View.OnClickListener;


import android.widget.Button;


import android.widget.Toast;




import com.codtech.android.auto.app.AutoDialog;






public class AutoAndroidSamples extends Activity implements OnClickListener, OnDismissListener {


    private static final int DIALOG_SAMPLE_ID = R.id.Button01;


    private static final int DIALOG_PIZZA_ORDER_SAMPLE_ID = R.id.Button02;


    


    private AutoDialog ad01;


    private AutoDialog ad02;




    /** Called when the activity is first created. */


    @Override


    public void onCreate(Bundle savedInstanceState) {


        super.onCreate(savedInstanceState);


        setContentView(R.layout.samples);


        


        ((Button) findViewById(R.id.Button01)).setOnClickListener(this);


        ((Button) findViewById(R.id.Button02)).setOnClickListener(this);


    }




    /* (non-Javadoc)


     * @see android.view.View.OnClickListener#onClick(android.view.View)


     */


    @Override


    public void onClick(View v) {


        showDialog(v.getId());        


    }




    


    /* (non-Javadoc)


     * @see android.app.Activity#onCreateDialog(int)


     */


    @Override


    protected Dialog onCreateDialog(int id) {


        switch (id) {


        case DIALOG_SAMPLE_ID:


            ad01 = new AutoDialog(this, R.layout.dialog_sample,


                R.string.dialog_sample_title);


            ad01.setOnDismissListener(this);


            return ad01;




        case DIALOG_PIZZA_ORDER_SAMPLE_ID:


            
ad02 = new AutoDialog(this, R.layout.pizza_order_sample,

               
R.string.dialog_pizza_order_sample_title);

            
ad02.setOnDismissListener(this);

            
return ad02;

            


        default:


            break;


        }


        


        return super.onCreateDialog(id);


    }




    /* (non-Javadoc)


     * @see android.app.Activity#onPrepareDialog(int, android.app.Dialog)


     */


    @Override


    protected void onPrepareDialog(int id, Dialog dialog) {


        // TODO Auto-generated method stub


        super.onPrepareDialog(id, dialog);


    }




    /* (non-Javadoc)


     * @see android.content.DialogInterface.OnDismissListener#
onDismiss(android.content.DialogInterface)


     */


    @Override


    public void onDismiss(DialogInterface dialog) {


        if ( dialog instanceof AutoDialog ) {


            AutoDialog ad = (AutoDialog)dialog;


            


            if ( ad.isCanceled() ) {


                return;


            }


            


            
Bundle bundle = ad.getAutoBundle();

            


            if ( ad.equals(ad01) ) {


                Toast.makeText(this, "Entered value: " + bundle.getString("text"),


                    Toast.LENGTH_SHORT).show();


            }


            else if ( ad.equals(ad02)) {


                
Toast.makeText(this, composePizzaOrderMessage(bundle),

                   
Toast.LENGTH_SHORT).show();

            }


        }


    }




    /**


     * @param bundle


     * @return


     */


    private String composePizzaOrderMessage(Bundle bundle) {


        final int quantity = bundle.getInt("quantity");


        if ( quantity > 0 ) {


            final StringBuilder msg = new StringBuilder(String.format("Make %d %s pizza%s",


               quantity, bundle.get("pizza"), (quantity > 1) ? "s" : ""));


            final boolean extraMozzarella = bundle.getBoolean("extra mozzarella");


            final boolean pepperoni = bundle.getBoolean("pepperoni");


            if ( extraMozzarella ) {


                msg.append(" with extra mozzarella");


                if ( pepperoni ) {


                    msg.append(" and pepperoni");


                }


            }


            else if ( pepperoni ) {


                msg.append(" with pepperoni");


            }


            


            return msg.toString();


        }




        return "No pizzas ordered";


    }




}



Creating the AutoDialog specifying the layout and the title in the constructor is all we need to have our Dialog working.

When the Dialog is dismissed we can obtain all of the exported values in a Bundle using the getAutoBundle() method.




Conclusion


This is a more practical example of AutoAndroid. With almost no code we have managed common behavior that frequently appears in Android applications. Some other samples will follow demonstrating other features. Stay tuned.



If you have comments, ideas, critiques or whatever just drop me a line or leave a comment in the blog.




Copyright © 2009 Diego Torres Milano. All rights reserved.