am 6b9784b0: am 288d90c2: Merge "The "current" SDK stubs jar should use the full apk that\'s exported, not the product-specific one." into gingerbread

Merge commit '6b9784b04249d42f1f2c68cfd30fa4d3322a3b75'

* commit '6b9784b04249d42f1f2c68cfd30fa4d3322a3b75':
  The "current" SDK stubs jar should use the full apk that's exported, not the product-specific one.
diff --git a/apps/Development/AndroidManifest.xml b/apps/Development/AndroidManifest.xml
index c4774c2..389ac19 100644
--- a/apps/Development/AndroidManifest.xml
+++ b/apps/Development/AndroidManifest.xml
@@ -117,7 +117,7 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="MediaScannerActivity" android:label="Media Scanner">
+        <activity android:name="MediaScannerActivity" android:label="Media Provider">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.TEST" />
diff --git a/apps/Development/res/layout/media_scanner_activity.xml b/apps/Development/res/layout/media_scanner_activity.xml
index 974f683..4806843 100644
--- a/apps/Development/res/layout/media_scanner_activity.xml
+++ b/apps/Development/res/layout/media_scanner_activity.xml
@@ -4,9 +4,9 @@
      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.
@@ -14,12 +14,45 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 	 	
-	android:layout_width="match_parent" 
-	android:layout_height="match_parent"
-	android:orientation="horizontal">
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
 
-    <TextView android:id="@+id/title" android:textSize="16sp" android:textStyle="bold"
-        android:layout_width="match_parent" android:layout_height="wrap_content" />
+    <TextView
+        android:id="@+id/title"
+        android:textSize="16sp"
+        android:textStyle="bold"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <Button
+        android:layout_width="160dip"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:text="@string/scancard"
+        android:onClick="startScan" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:layout_marginTop="30dip">
+
+        <EditText
+            android:id="@+id/numsongs"
+            android:layout_width="150dip"
+            android:layout_height="wrap_content"
+            android:hint="@string/numsongs"
+            android:numeric="integer"
+        />
+
+        <Button
+            android:id="@+id/insertbutton"
+            android:layout_width="150dip"
+            android:layout_height="wrap_content"
+            android:onClick="insertItems" />
+
+    </LinearLayout>
 
 </LinearLayout>
diff --git a/apps/Development/res/values/strings.xml b/apps/Development/res/values/strings.xml
index 181c4fa..d144b8f 100644
--- a/apps/Development/res/values/strings.xml
+++ b/apps/Development/res/values/strings.xml
@@ -208,4 +208,9 @@
     <string name="bad_behavior_anr_service_label">ANR starting a Service</string>
     <string name="bad_behavior_anr_system_label">System ANR (in ActivityManager)</string>
     <string name="bad_behavior_wedge_system_label">Wedge system (5 minute system ANR)</string>
+
+    <!-- MediaScannerActivity -->
+    <string name="scancard">Scan SD card</string>
+    <string name="numsongs"># of albums</string>
+    <string name="insertbutton">Insert %1s albums</string>
 </resources>
diff --git a/apps/Development/src/com/android/development/MediaScannerActivity.java b/apps/Development/src/com/android/development/MediaScannerActivity.java
index f78910a..04f6414 100644
--- a/apps/Development/src/com/android/development/MediaScannerActivity.java
+++ b/apps/Development/src/com/android/development/MediaScannerActivity.java
@@ -17,56 +17,239 @@
 package com.android.development;
 
 import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.BroadcastReceiver;
+import android.database.sqlite.SQLiteConstraintException;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
 import android.widget.TextView;
 
+import java.util.Random;
+
 public class MediaScannerActivity extends Activity
 {
+    private TextView mTitle;
+    private int mNumToInsert = 20;
+    private int mArtists;
+    private int mAlbums;
+    private int mSongs;
+    private ContentResolver mResolver;
+    private Uri mAudioUri;
+    ContentValues mValues[] = new ContentValues[10];
+    Random mRandom = new Random();
+    StringBuilder mBuilder = new StringBuilder();
+
     public MediaScannerActivity() {
     }
- 
+
     /** Called when the activity is first created or resumed. */
     @Override
-    public void onResume() {
-        super.onResume();
-        
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
         setContentView(R.layout.media_scanner_activity);
-        
+
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
         intentFilter.addDataScheme("file");
         registerReceiver(mReceiver, intentFilter);
-        
-        sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
-                + Environment.getExternalStorageDirectory())));
-            
+
+        EditText t = (EditText) findViewById(R.id.numsongs);
+        t.addTextChangedListener(new TextWatcher() {
+
+            public void afterTextChanged(Editable s) {
+                String text = s.toString();
+                try {
+                    mNumToInsert = Integer.valueOf(text);
+                } catch (NumberFormatException ex) {
+                    mNumToInsert = 20;
+                }
+                setInsertButtonText();
+            }
+
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+            }
+
+        });
         mTitle = (TextView) findViewById(R.id.title);
-        mTitle.setText("Sent ACTION_MEDIA_MOUNTED to trigger the Media Scanner.");
+        mResolver = getContentResolver();
+        mAudioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+
+        for (int i = 0; i < 10; i++) {
+            mValues[i] = new ContentValues();
+        }
+        setInsertButtonText();
     }
 
     /** Called when the activity going into the background or being destroyed. */
     @Override
-    public void onPause() {
-        super.onPause();
+    public void onDestroy() {
         unregisterReceiver(mReceiver);
+        mInsertHandler.removeMessages(0);
+        super.onDestroy();
     }
-    
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
-                mTitle.setText("Media Scanner started scanning " + intent.getData().getPath());     
+                mTitle.setText("Media Scanner started scanning " + intent.getData().getPath());
             }
             else if (intent.getAction().equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
-                mTitle.setText("Media Scanner finished scanning " + intent.getData().getPath());     
+                mTitle.setText("Media Scanner finished scanning " + intent.getData().getPath());
             }
         }
     };
 
-    private TextView mTitle;
+    public void startScan(View v) {
+        sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+                + Environment.getExternalStorageDirectory())));
+
+        mTitle.setText("Sent ACTION_MEDIA_MOUNTED to trigger the Media Scanner.");
+    }
+
+    private void setInsertButtonText() {
+        String label = getString(R.string.insertbutton, Integer.valueOf(mNumToInsert));
+        Button b = (Button) findViewById(R.id.insertbutton);
+        b.setText(label);
+    }
+
+
+    public void insertItems(View v) {
+        if (mInsertHandler.hasMessages(0)) {
+            mInsertHandler.removeMessages(0);
+            setInsertButtonText();
+        } else {
+            mInsertHandler.sendEmptyMessage(0);
+        }
+    }
+
+    Handler mInsertHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+
+            if (mNumToInsert-- > 0) {
+                addAlbum();
+                runOnUiThread(mDisplayUpdater);
+
+                if (!isFinishing()) {
+                    sendEmptyMessage(0);
+                }
+            }
+        }
+    };
+
+    Runnable mDisplayUpdater = new Runnable() {
+        public void run() {
+            mTitle.setText("Added " + mArtists + " artists, " + mAlbums + " albums, "
+                    + mSongs + " songs.");
+        }
+    };
+
+    // Add one more album (with 10 songs) to the database. This will be a compilation album,
+    // with one album artist for the album, and a separate artist for each song.
+    private void addAlbum() {
+        try {
+            String albumArtist = "Various Artists";
+            String albumName = getRandomWord(3);
+            int baseYear = 1969 + mRandom.nextInt(30);
+            for (int i = 0; i < 10; i++) {
+                mValues[i].clear();
+                String artist = getRandomName();
+                final ContentValues map = mValues[i];
+                map.put(MediaStore.MediaColumns.DATA,
+                        "http://bogus/" + albumName + "/" + artist + "_" + i);
+                map.put(MediaStore.MediaColumns.TITLE,
+                        getRandomWord(4) + " " + getRandomWord(2) + " " + (i + 1));
+                map.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
+
+                map.put(Audio.Media.ARTIST, artist);
+                map.put("album_artist", albumArtist);
+                map.put(Audio.Media.ALBUM, albumName);
+                map.put(Audio.Media.TRACK, i + 1);
+                map.put(Audio.Media.DURATION, 4*60*1000);
+                map.put(Audio.Media.IS_MUSIC, 1);
+                map.put(Audio.Media.YEAR, baseYear + mRandom.nextInt(10));
+            }
+            mResolver.bulkInsert(mAudioUri, mValues);
+            mSongs += 10;
+            mAlbums++;
+            mArtists += 11;
+        } catch (SQLiteConstraintException ex) {
+            Log.d("@@@@", "insert failed", ex);
+        }
+    }
+
+    /**
+     * Some code to generate random names. This just strings together random
+     * syllables, and randomly inserts a modifier between the first
+     * and last name.
+     */
+    private String[] elements = new String[] {
+            "ab", "am",
+            "bra", "bri",
+            "ci", "co",
+            "de", "di", "do",
+            "fa", "fi",
+            "ki",
+            "la", "li",
+            "ma", "me", "mi", "mo",
+            "na", "ni",
+            "pa",
+            "ta", "ti",
+            "vi", "vo"
+    };
+
+    private String getRandomWord(int len) {
+        int max = elements.length;
+        mBuilder.setLength(0);
+        for (int i = 0; i < len; i++) {
+            mBuilder.append(elements[mRandom.nextInt(max)]);
+        }
+        char c = mBuilder.charAt(0);
+        c = Character.toUpperCase(c);
+        mBuilder.setCharAt(0, c);
+        return mBuilder.toString();
+    }
+
+    private String getRandomName() {
+        boolean longfirst = mRandom.nextInt(5) < 3;
+        String first = getRandomWord(longfirst ? 3 : 2);
+        String last = getRandomWord(3);
+        switch (mRandom.nextInt(6)) {
+            case 1:
+                if (!last.startsWith("Di")) {
+                    last = "di " + last;
+                }
+                break;
+            case 2:
+                last = "van " + last;
+                break;
+            case 3:
+                last = "de " + last;
+                break;
+        }
+        return first + " " + last;
+    }
+
+
+
 }
diff --git a/build/sdk.atree b/build/sdk.atree
index f6ba300..0f9cb94 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -109,6 +109,7 @@
 development/samples/VoiceRecognitionService  samples/${PLATFORM_NAME}/VoiceRecognitionService
 development/samples/TicTacToeLib             samples/${PLATFORM_NAME}/TicTacToeLib
 development/samples/TicTacToeMain            samples/${PLATFORM_NAME}/TicTacToeMain
+development/samples/XmlAdapters              samples/${PLATFORM_NAME}/XmlAdapters
 
 # dx
 bin/dx platforms/${PLATFORM_NAME}/tools/dx
diff --git a/build/tools/windows_sdk.mk b/build/tools/windows_sdk.mk
index 9609160..057f1fc 100644
--- a/build/tools/windows_sdk.mk
+++ b/build/tools/windows_sdk.mk
@@ -38,7 +38,10 @@
 WIN_SDK_DIR   := $(subst $(HOST_OS)-$(HOST_ARCH),windows,$(LINUX_SDK_DIR))
 WIN_SDK_ZIP   := $(WIN_SDK_DIR)/$(WIN_SDK_NAME).zip
 
-$(call dist-for-goals, win_sdk, $(WIN_SDK_ZIP))
+# Also dist $(INTERNAL_SDK_TARGET), which is the original linux sdk package.
+# INTERNAL_SDK_TARGET is defined in build/core/Makefile.
+$(call dist-for-goals, win_sdk, $(WIN_SDK_ZIP) \
+    $(INTERNAL_SDK_TARGET))
 
 .PHONY: win_sdk winsdk-tools
 
diff --git a/host/windows/usb/android_winusb.inf b/host/windows/usb/android_winusb.inf
index b2daf89..331ed6d 100755
--- a/host/windows/usb/android_winusb.inf
+++ b/host/windows/usb/android_winusb.inf
@@ -38,14 +38,31 @@
 %CompositeAdbInterface%     = USB_Install, USB\VID_22B8&PID_41DB&MI_01

 ;

 ;Google NexusOne

-%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_0D02
+%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_0D02

 %CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_0D02&MI_01

-%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_4E11
-%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_4E12&MI_01
+%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_4E11

+%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_4E12&MI_01

 %CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_4E22&MI_01

-;
-; Dell's Mini5
-%CompositeAdbInterface%     = USB_Install, USB\VID_413C&PID_B007&MI_01
+;

+; Dell's Mini5

+%SingleAdbInterface%        = USB_Install, USB\VID_413C&PID_B007

+%CompositeAdbInterface%     = USB_Install, USB\VID_413C&PID_B007&MI_01

+;

+; Samsung SPH-M900, GT-I5700, SCH-R880

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_681C

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_681C&MI_01

+; Samsung GT-I7500

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_6601

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_6601&MI_01

+; Samsung GT-I5500

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_6882

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_6882&MI_01

+; Samsung SHW-M100S

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_6850

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_6850&MI_01

+; Samsung SHW-M110S

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_681D

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_681D&MI_01

 

 [Google.NTamd64]

 ; HTC Dream

@@ -61,13 +78,30 @@
 ;

 ;Google NexusOne

 %SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_0D02

-%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_0D02&MI_01
+%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_0D02&MI_01

 %SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_4E11

-%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_4E12&MI_01
+%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_4E12&MI_01

 %CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_4E22&MI_01

-;
-; Dell's Mini5
-%CompositeAdbInterface%     = USB_Install, USB\VID_413C&PID_B007&MI_01
+;

+; Dell's Mini5

+%SingleAdbInterface%        = USB_Install, USB\VID_413C&PID_B007

+%CompositeAdbInterface%     = USB_Install, USB\VID_413C&PID_B007&MI_01

+;

+; Samsung SPH-M900, GT-I5700, SCH-R880

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_681C

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_681C&MI_01

+; Samsung GT-I7500

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_6601

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_6601&MI_01

+; Samsung GT-I5500

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_6882

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_6882&MI_01

+; Samsung SHW-M100S

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_6850

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_6850&MI_01

+; Samsung SHW-M110S

+%SingleAdbInterface%        = USB_Install, USB\VID_04E8&PID_681D

+%CompositeAdbInterface%     = USB_Install, USB\VID_04E8&PID_681D&MI_01

 

 [USB_Install]

 Include = winusb.inf

diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath
index a6eca24..dbfb966 100644
--- a/ide/eclipse/.classpath
+++ b/ide/eclipse/.classpath
@@ -28,28 +28,28 @@
 	<classpathentry kind="src" path="packages/providers/DrmProvider/src"/>
 	<classpathentry kind="src" path="packages/providers/MediaProvider/src"/>
 	<classpathentry kind="src" path="packages/providers/TelephonyProvider/src"/>
-	<classpathentry kind="src" path="frameworks/base/awt"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/am/src"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/input/src"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/pm/src"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/svc/src"/>
-	<classpathentry kind="src" path="frameworks/base/common/java"/>
 	<classpathentry kind="src" path="frameworks/base/core/java"/>
 	<classpathentry kind="src" path="frameworks/base/core/config/sdk"/>
 	<classpathentry kind="src" path="frameworks/base/graphics/java"/>
+	<classpathentry kind="src" path="frameworks/base/icu4j/java"/>
 	<classpathentry kind="src" path="frameworks/base/keystore/java"/>
 	<classpathentry kind="src" path="frameworks/base/location/java"/>
 	<classpathentry kind="src" path="frameworks/base/media/java"/>
 	<classpathentry kind="src" path="frameworks/base/obex"/>
 	<classpathentry kind="src" path="frameworks/base/opengl/java"/>
 	<classpathentry kind="src" path="frameworks/base/packages/SettingsProvider/src"/>
-	<classpathentry kind="src" path="frameworks/base/policy"/>
+	<classpathentry kind="src" path="frameworks/base/policy/src"/>
 	<classpathentry kind="src" path="frameworks/base/sax/java"/>
 	<classpathentry kind="src" path="frameworks/base/services/java"/>
 	<classpathentry kind="src" path="frameworks/base/telephony/java"/>
 	<classpathentry kind="src" path="frameworks/base/test-runner/src"/>
 	<classpathentry kind="src" path="frameworks/base/vpn/java"/>
 	<classpathentry kind="src" path="frameworks/base/wifi/java"/>
+	<classpathentry kind="src" path="frameworks/opt/vcard/java"/>
 	<classpathentry kind="src" path="development/samples/ApiDemos/src"/>
 	<classpathentry kind="src" path="development/samples/ApiDemos/tests/src"/>
 	<classpathentry kind="src" path="development/samples/Compass/src"/>
@@ -66,34 +66,15 @@
 	<classpathentry kind="src" path="development/samples/Snake/src"/>
 	<classpathentry kind="src" path="development/samples/Snake/tests/src"/>
 	<classpathentry kind="src" path="development/apps/Term/src"/>
-	<classpathentry kind="src" path="libcore/annotation/src/main/java"/>
-	<classpathentry kind="src" path="libcore/archive/src/main/java"/>
-	<classpathentry kind="src" path="libcore/auth/src/main/java"/>
-	<classpathentry kind="src" path="libcore/awt-kernel/src/main/java"/>
-	<classpathentry kind="src" path="libcore/concurrent/src/main/java"/>
-	<classpathentry kind="src" path="libcore/crypto/src/main/java"/>
 	<classpathentry kind="src" path="libcore/dalvik/src/main/java"/>
-	<classpathentry kind="src" path="libcore/icu/src/main/java"/>
 	<classpathentry kind="src" path="libcore/json/src/main/java"/>
 	<classpathentry kind="src" path="libcore/junit/src/main/java"/>
-	<classpathentry kind="src" path="libcore/logging/src/main/java"/>
-	<classpathentry kind="src" path="libcore/luni-kernel/src/main/java"/>
 	<classpathentry kind="src" path="libcore/luni/src/main/java"/>
-	<classpathentry kind="src" path="libcore/math/src/main/java"/>
-	<classpathentry kind="src" path="libcore/nio_char/src/main/java"/>
-	<classpathentry kind="src" path="libcore/nio/src/main/java"/>
-	<classpathentry kind="src" path="libcore/openssl/src/main/java"/>
-	<classpathentry kind="src" path="libcore/prefs/src/main/java"/>
-	<classpathentry kind="src" path="libcore/regex/src/main/java"/>
-	<classpathentry kind="src" path="libcore/security-kernel/src/main/java"/>
-	<classpathentry kind="src" path="libcore/security/src/main/java"/>
-	<classpathentry kind="src" path="libcore/sql/src/main/java"/>
-	<classpathentry kind="src" path="libcore/suncompat/src/main/java"/>
-	<classpathentry kind="src" path="libcore/text/src/main/java"/>
-	<classpathentry kind="src" path="libcore/x-net/src/main/java"/>
 	<classpathentry kind="src" path="libcore/xml/src/main/java"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/ApiDemos_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Browser_intermediates/src/src"/>
+	<classpathentry kind="src" path="out/target/common/obj/APPS/CalendarProvider_intermediates/src/src"/>
+	<classpathentry kind="src" path="out/target/common/obj/APPS/ContactsProvider_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Email_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Music_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Phone_intermediates/src/src"/>
@@ -106,8 +87,10 @@
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/wifi/java"/>
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/services_intermediates/src"/>
 	<classpathentry kind="src" path="out/target/common/R"/>
-	<classpathentry kind="src" path="external/tagsoup/src"/>
 	<classpathentry kind="src" path="external/apache-http/src"/>
+	<classpathentry kind="src" path="external/bouncycastle/src/main/java"/>
+	<classpathentry kind="src" path="external/libphonenumber/java/src"/>
+	<classpathentry kind="src" path="external/tagsoup/src"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/google-common_intermediates/javalib.jar"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/gsf-client_intermediates/javalib.jar"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/guava_intermediates/javalib.jar"/>
diff --git a/pdk/Pdk.mk b/pdk/Pdk.mk
index 676f5dc..db6570a 100644
--- a/pdk/Pdk.mk
+++ b/pdk/Pdk.mk
@@ -77,7 +77,7 @@
 #   descriptions for the new headers and point to the new doxygen created html.
 pdk_headers := \
     $(pdk_legacy_hardware_dir)/AudioHardwareInterface.h \
-    $(pdk_legacy_hardware_dir)/gps.h \
+    $(pdk_hardware_dir)/gps.h \
     $(pdk_legacy_hardware_dir)/wifi.h \
     $(pdk_camera_dir)/CameraHardwareInterface.h \
     $(pdk_hardware_dir)/sensors.h \
diff --git a/pdk/docs/about/index.jd b/pdk/docs/about/index.jd
index 2f98b65..43d2a8d 100644
--- a/pdk/docs/about/index.jd
+++ b/pdk/docs/about/index.jd
@@ -3,10 +3,10 @@
 doc.hidenav=true
 @jd:body
 <p>Android is an open-source software stack created for mobile phones and
-other devices.  The Android Open Source Project (AOSP) is tasked with the
-maintenance and further development of Android. Many device manufacturers have
-brought to market devices running Android, and they are readibly available
-around the world.</p>
+other devices.  The Android Open Source Project (AOSP), led by Google, is
+tasked with the maintenance and further development of Android. Many device
+manufacturers have brought to market devices running Android, and they are
+readibly available around the world.</p>
 <p>Our primary purpose is to build an excellent software platform for everyday
 users. A number of companies have committed many engineers to achieve this
 goal, and the result is a full production quality consumer product whose
diff --git a/pdk/docs/about/philosophy.jd b/pdk/docs/about/philosophy.jd
index 1aa1ccf..1562e42 100644
--- a/pdk/docs/about/philosophy.jd
+++ b/pdk/docs/about/philosophy.jd
@@ -2,7 +2,7 @@
 doc.type=about
 doc.hidenav=true
 @jd:body
-<p>Android is an open-source software stack for mobile phones and similar
+<p>Android is an open-source software stack for mobile phones and other
 devices.</p>
 <h2>Origin and Goal</h2>
 <p>Android was originated by a group of companies known as the Open Handset
@@ -16,12 +16,11 @@
 ideas a reality. We wanted to make sure that there was no central point of
 failure, where one industry player could restrict or control the innovations
 of any other. The solution we chose was an open and open-source platform.</p>
-<p>But the ultimate goal, of course, is to improve the mobile experience for
-real users by facilitating innovation. Accordingly, the primary goal of the
-AOSP is to make sure Android is a success as an end user product.</p>
+<p>The goal of the Android Open Source Project is to create a successful
+real-world product that improves the mobile experience for end users.</p>
 <h2>Governance Philosophy</h2>
 <p>The companies that have invested in Android have done so on its merits,
-because we collectively believe that an open platform is necessary. Android is
+because we believe that an open platform is necessary. Android is
 intentionally and explicitly an open-source -- as opposed to free software --
 effort: a group of organizations with shared needs has pooled
 resources to collaborate on a single implementation of a shared product. 
@@ -34,20 +33,19 @@
 Anyone can (and will!) use the Android source code for any purpose, and we
 welcome all such uses. However, in order to take part in the shared
 ecosystem of applications that we are building around Android, device builders
-can take advantage of the Compatibility Program.</p>
+must participate in the Compatibility Program.</p>
 <p>Though Android consists of multiple sub-projects, this is strictly a
 project-management technique. We view and manage Android as a single,
 holistic software product, not a "distribution", specification, or collection
-of replaceable parts. Conceptually, our notion is that device builders port
+of replaceable parts. Our intent is that device builders port
 Android to a device; they don't implement a specification or curate a
 distribution.</p>
 <h2>How We Work</h2>
 <p>We know that quality does not come without hard work. Along with many
 partners, Google has contributed full-time engineers, product managers, UI
 designers, Quality Assurance, and all the other roles required to bring
-modern devices to market.  We integrate the open source administration and
+modern devices to market.  We roll the open source administration and
 maintenance into the larger product development cycle.</p>
-<p>In a nutshell:</p>
 <ul>
 <li>At any given moment, there is a current latest release of the Android
 platform. This typically takes the form of a branch in the tree.</li>
@@ -56,18 +54,9 @@
 features, and so on.</li>
 <li>In parallel, Google works internally on the next version of the
 Android platform and framework, working according to the product's needs and
-goals. Some of the work from the current latest tree will promoted into these
-releases.</li>
-<li>When the "n+1"th version is determined to be nearing completion, it will
-be published to the public source tree, and become the new latest
-release.</li>
-<li>Since Android is open source, nothing prevents device implementers from
-shipping devices on older (obsolete) Android builds. However, active work will
-be focused on the current platform release.</li>
+goals. We develop the next version of Android by working with a device partner
+on a flagship device whose specifications are chosen to push Android
+in the direction we believe it should go.</li>
+<li>When the "n+1"th version is ready, it will be published to the public
+source tree, and become the new latest release.</li>
 </ul>
-<p>To meet our goals, Android needs to achieve widespread, compatible
-adoption. We believe that the best way to accomplish that is to make sure that
-we ship high-quality, flagship devices with an intense product and end-user
-focus. The "next release" of Android is driven by the product needs for the next
-generation of mobile devices; the resulting excellent product is then released
-to open source and becomes the new current version of the platform.</p>
diff --git a/pdk/docs/community/groups-charter.jd b/pdk/docs/community/groups-charter.jd
index 6d5b501..959917e 100644
--- a/pdk/docs/community/groups-charter.jd
+++ b/pdk/docs/community/groups-charter.jd
@@ -1,26 +1,66 @@
 page.title=Android Discussion Groups Charter
 doc.type=community
+doc.hidenav=true
 @jd:body
-<h2>
-Audience
-</h2>
-<p>These discussion groups are intended for developers working with the Android platform. Everyone is welcome to join in, provided you follow our community's policies described below. Our users help each other, and many experts post to these groups, including members of the Open Handset Alliance.
+<h2>Audience</h2>
+<p>These discussion groups are intended for developers working with the
+Android platform. Everyone is welcome to join in, provided you follow our
+community's policies described below. Our users help each other, and many
+experts post to these groups, including members of the Open Handset Alliance.
 </p>
-<p>No topic is off-limits, provided it relates to Android in some way. However, since these are very busy lists, search the archives before posting your question; you may find your question has already been answered.
+<p>No topic is off-limits, provided it relates to Android in some way.
+However, since these are very busy lists, search the archives before posting
+your question; you may find your question has already been answered.
 </p>
-<h2>
-Mailing list rules
-</h2>
-<p>We love simplicity and hate restrictions, so we keep our policies minimal. The rules below describe what's expected of subscribers to the Android mailing lists.
+<h2>Mailing list rules</h2>
+<p>We love simplicity and hate restrictions, so we keep our policies minimal.
+The rules below describe what's expected of subscribers to the Android mailing
+lists.
 </p>
 <ul><li><b>Please be friendly</b>
-<br>Showing courtesy and respect to others is a vital part of the Android culture, and we expect everyone participating in the Android community to join us in accepting nothing less. Being courteous does not mean we can't constructively disagree with each other, but it does mean that we must be polite when we do so. There's never a reason to be antagonistic or dismissive toward anyone; if you think there is, think again before you post.<br><br>Mobile development is serious business, but it's also a lot of fun. Let's keep it that way. Let's strive to be one of the friendliest communities in all of open source.<br><br></li>
+<br>Showing courtesy and respect to others is a vital part of the Android
+culture, and we expect everyone participating in the Android community to join
+us in accepting nothing less. Being courteous does not mean we can't
+constructively disagree with each other, but it does mean that we must be
+polite when we do so. There's never a reason to be antagonistic or dismissive
+toward anyone; if you think there is, think again before you
+post.<br><br>Mobile development is serious business, but it's also a lot of
+fun. Let's keep it that way. Let's strive to be one of the friendliest
+communities in all of open source.<br><br></li>
+
 <li><b>Allowed discussion topics</b>
-<br>Most topics are technical discussions of Android or users helping each other, but this group is intended for discussions of<i>everything</i>
-in the world of Android. We welcome announcements and discussion of products, libraries, publications, and other interesting Android-related news. We even welcome (polite!) discussion of articles and ideas critical of Android--after all, we can't improve if we don't listen. There are no restrictions on the subject matter, and we don't exclude discussions of commercial products if users are interested in talking about them.<br><br>However, we hate spam almost as passionately as we love courtesy and respect, so we reserve the right to limit discussions that amount to spam. Outright spam will result in the spammer being immediately and permanently banned from the list.
+<br>Most of our groups are for technical discussions of Android or users
+helping each other. Generally we don't put hard restrictions on the topics
+discussed in the group: as long as the topic is relevant to Android in some
+way, it's welcome on our groups.  We welcome announcements and discussion of
+products, libraries, publications, and other interesting Android-related news,
+but <b>please do not cross-post</b>. Post only to the most relevant group for
+your message. We even welcome (polite!) discussion of articles and ideas
+critical of Android--after all, we can't improve if we don't listen.<br><br>
 </li>
+
+<li><b>Working Lists</b>
+<br>Some of our groups are considered "working lists", by which we mean that the
+list is intended to be used in support of the completion of specific tasks. On
+these groups, we don't welcome off-topic conversations, and will generally ask
+you to take general discussions to a different list. Since these are lists
+where people are trying to get work done, we will be pretty aggressive about
+keeping the noise level low. We ask that you respect our contributors' time
+and keep general discussions to appropriate lists.<br><br>
+</li>
+
+<li><b>Spam</b>
+<br>We hate spam almost as passionately as we love courtesy and respect, so we
+reserve the right to limit discussions that amount to spam. Outright spam will
+result in the spammer being immediately and permanently banned from the list.
+<br><br></li>
 </ul>
-<p>The most important rule is friendliness. Remember: disrespect and rudeness are not welcome in our community under any circumstances. We don't have a formal policy on dealing with troublemakers, and we hope we never need one.That said, we do pledge to do our best to be fair, and we will always try to warn someone before banning him or her.
+
+<p>The most important rule is friendliness. Remember: disrespect and rudeness
+are not welcome in our community under any circumstances. We don't have a
+formal policy on dealing with troublemakers, and we hope we never need
+one.That said, we do pledge to do our best to be fair, and we will always try
+to warn someone before banning him or her.
 </p>
 <h2>
 Contacting the moderators
diff --git a/pdk/docs/community/index.jd b/pdk/docs/community/index.jd
index 6e6f59e..46adf37 100644
--- a/pdk/docs/community/index.jd
+++ b/pdk/docs/community/index.jd
@@ -1,7 +1,7 @@
-page.title=Community
+page.title=Android Community
 doc.type=community
+doc.hidenav=true
 @jd:body
-<h1>Android Community</h1>
 <p>Welcome to the Android community!</p>
 <p>The key to any community is, obviously, communication. Like most projects,
 Android communicates via mailing lists. Because Android is an extremely large
@@ -37,6 +37,14 @@
 
 <h2>Open Source Project discussions</h2>
 <ul>
+<li><b>android-platform</b><br/>
+This list is for general discussion about the Android open-source project or
+the platform technologies.<br/><br/>
+Subscribe using Google Groups: <a
+href="http://groups.google.com/group/android-platform">android-platform</a><br/>
+Subscribe via email: <a href="mailto:android-platform+subscribe@googlegroups.com">android-platform+subscribe@googlegroups.com</a>
+</li>
+
 <li><b>android-building</b><br/>
 Subscribe to this list for discussion and help on building the Android source
 code, and on the build system. If you've just checked out the source code and
@@ -58,14 +66,14 @@
 Subscribe via email: <a href="mailto:android-porting+subscribe@googlegroups.com">android-porting+subscribe@googlegroups.com</a>
 </li>
 
-<li><b>android-platform</b><br/>
-This list is for developers who want to contribute code to the Android
-user-space projects, such as the core system libraries, the Android
-services, the public APIs, or the built-in applications. Note: contributors
+<li><b>android-contrib</b><br/>
+This list is for developers who want to contribute code to Android. This is a
+working list, and is not appropriate for general discussion. We ask that
+general discussion go to android-platform.  Note: contributors
 to the Android kernel should go to the android-kernel list, below.<br/><br/>
 Subscribe using Google Groups: <a
-href="http://groups.google.com/group/android-platform">android-platform</a><br/>
-Subscribe via email: <a href="mailto:android-platform+subscribe@googlegroups.com">android-platform+subscribe@googlegroups.com</a>
+href="http://groups.google.com/group/android-contrib">android-contrib</a><br/>
+Subscribe via email: <a href="mailto:android-contrib+subscribe@googlegroups.com">android-contrib+subscribe@googlegroups.com</a>
 </li>
 
 <li><b>android-kernel</b><br/>
@@ -88,27 +96,35 @@
 under "subscribe via email" in the lists above.</p>
 <p>To set up how you receive mailing list postings by email:</p>
 <ol>
-<li>Sign into the group via the Google Groups site. For example, for the android-framework group you would
-visit <a
-href="http://groups.google.com/group/android-framework">http://groups.google.com/group/android-framework</a>.</li>
+<li>Sign into the group via the Google Groups site. For example, for the android-platform group you would
+visit <a href="http://groups.google.com/group/android-platform">http://groups.google.com/group/android-platform</a>.</li>
 <li>Click "Edit my membership" on the right side.</li>
 <li>Under "How do you want to read this group?" select one of the email options.</li>
 </ol>
 
 <h2>Android on IRC</h2>
-<p>We also have a presence on IRC via Freenode. We maintain two official IRC
-channels on irc.freenode.net:</p>
+<p>We also have a presence on IRC via <a href="http://freenode.net/">freenode</a>.
+We maintain two official IRC channels on
+<a href="irc://irc.freenode.net/">irc.freenode.net</a> (access via the web
+at <a href="http://webchat.freenode.net/">freenode webchat</a>):</p>
 <ul>
-<li><b>#android</b> - dedicated to general Android discussion and porting concerns</li>
-<li><b>#android-dev</b> - dedicated to discussion about writing Android applications</li>
+<li><b><a href="irc://irc.freenode.net/android">#android</a></b>
+    &mdash; dedicated to general Android discussion and porting concerns</li>
+<li><b><a href="irc://irc.freenode.net/android-dev">#android-dev</a></b>
+    &mdash; dedicated to discussion about writing Android applications</li>
 </ul>
 <p>The channels above are official. There are a few other channels the
 community is using, but are not official. These aren't official or officially
 moderated/managed, so you use the channels below at your own risk. The Open
 Handset Alliance doesn't endorse these channels, there's no warranty express
-or implied, and so on. There may be more.</p>
+or implied, and so on. There may be more channels than just these listed.</p>
 <ul>
-<li><b>#android-offtopic</b> - for, well, off-topic discussions</li>
-<li><b>#android-root</b> - for discussion related to off-label uses of hardware</li>
-<li><b>#androidfra</b> - pour discuter d'Android en français</li>
+<li><b><a href="irc://irc.freenode.net/android-firehose">#android-firehose</a></b>
+    &mdash; displays in real-time the commits to the Android Open Source Project</li>
+<li><b><a href="irc://irc.freenode.net/android-fr">#android-fr</a></b>
+    &mdash; pour discuter d'Android en français</li>
+<li><b><a href="irc://irc.freenode.net/android-offtopic">#android-offtopic</a></b>
+    &mdash; for, well, off-topic discussions</li>
+<li><b><a href="irc://irc.freenode.net/android-root">#android-root</a></b>
+    &mdash; for discussion related to off-label uses of hardware</li>
 </ul>
diff --git a/pdk/docs/compatibility/2.1/versions.jd b/pdk/docs/compatibility/2.1/versions.jd
new file mode 100644
index 0000000..9687a96
--- /dev/null
+++ b/pdk/docs/compatibility/2.1/versions.jd
@@ -0,0 +1,19 @@
+page.title=Permitted Version Strings for Android 2.1
+doc.type=compatibility
+@jd:body
+<p>As described in Section 3.2.2 of the <a
+href="{@docRoot}compatibility/android-2.1-cdd.pdf">Android 2.1 Compatibility
+Definition</a>, only certain strings are allowable for the system property
+<code>android.os.Build.VERSION.RELEASE</code>. The reason for this is that
+applications and web sites may rely on predictable values for this string, and
+so that end users can easily and reliably identify the version of Android
+running on their devices.</p>
+<p>Because subsequent releases of the Android software may revise this string,
+but not change any API behavior, such releases may not be accompanied by a new
+Compatibility Definition Document. This page lists the versions that are
+allowable by an Android 2.1-based system. The only permitted values for
+<code>android.os.Build.VERSION.RELEASE</code> for Android 2.1 are:</p>
+<ul>
+<li>2.1</li>
+<li>2.1-update1</li>
+</ul>
diff --git a/pdk/docs/compatibility/android-1.6-cdd.pdf b/pdk/docs/compatibility/android-1.6-cdd.pdf
new file mode 100644
index 0000000..ba7b4ad
--- /dev/null
+++ b/pdk/docs/compatibility/android-1.6-cdd.pdf
Binary files differ
diff --git a/pdk/docs/compatibility/android-2.1-cdd.pdf b/pdk/docs/compatibility/android-2.1-cdd.pdf
new file mode 100644
index 0000000..7fe54c6
--- /dev/null
+++ b/pdk/docs/compatibility/android-2.1-cdd.pdf
Binary files differ
diff --git a/pdk/docs/compatibility/android-cts-manual-r4.pdf b/pdk/docs/compatibility/android-cts-manual-r4.pdf
new file mode 100644
index 0000000..f16b6f9
--- /dev/null
+++ b/pdk/docs/compatibility/android-cts-manual-r4.pdf
Binary files differ
diff --git a/pdk/docs/compatibility/compatibility_toc.cs b/pdk/docs/compatibility/compatibility_toc.cs
index 5688d14..536f772 100644
--- a/pdk/docs/compatibility/compatibility_toc.cs
+++ b/pdk/docs/compatibility/compatibility_toc.cs
@@ -7,12 +7,12 @@
 <ul>
   <li><h2>Getting Started</h2><ul>
     <li><a href="<?cs var:toroot ?>compatibility/overview.html">Compatibility Overview</a></li>
-    <li><a href="">Current CDD</a></li>
+    <li><a href="<?cs var:toroot ?>compatibility/android-2.1-cdd.pdf">Current CDD</a></li>
     <li><a href="<?cs var:toroot ?>compatibility/cts-intro.html">CTS Introduction</a></li>
   </ul></li>
 
   <li><h2>More Information</h2><ul>
-    <li><a href="<?cs var:toroot ?>downloads/index.html">Downloads</a></li>
+    <li><a href="<?cs var:toroot ?>compatibility/downloads.html">Downloads</a></li>
     <li><a href="<?cs var:toroot ?>faqs.html#compatibility">FAQs</a></li>
     <li><a href="<?cs var:toroot ?>compatibility/contact-us.html">Contact Us</a></li>
   </ul></li>
diff --git a/pdk/docs/compatibility/contact-us.jd b/pdk/docs/compatibility/contact-us.jd
index ba4e887..2aa2aa1 100644
--- a/pdk/docs/compatibility/contact-us.jd
+++ b/pdk/docs/compatibility/contact-us.jd
@@ -7,13 +7,12 @@
 out of any of these options, please first read "Getting the Most from Our
 Lists" on the <a href="{@docRoot}community/index.html">Community page.</a></p>
 
-<h3>Discussion Group</h3>
+<h3>For General Discussion</h3>
 <p>The preferred way to reach us is via the <a
-href="http://groups.google.com/group/android-compatibility">android-compatibility
-mailing list</a>. Use this list for all your compatibility-related questions.
-Please be aware that this is a public forum.</p>
+href="mailto:compatibility@android.com">compatibility@android.com
+address</a>.</p>
 
-<h3>CTS Technical Questions</h3>
+<h3>For CTS Technical Questions</h3>
 <p>If you have specific issues with the Compatibility Test Suite that require
 you to disclose information you'd prefer not to be public, you can contact an
 email address we've set up specifically this purpose: <a
@@ -23,9 +22,9 @@
 list. Note also that this list is for specific technical questions; general
 inquiries will also be directed back to the android-compatibility list.</p>
 
-<h3>Private Inquiries</h3>
+<h3>For Business Inquiries</h3>
 <p>Finally, business inquiries about the compatibility program, including
 requests to use branding elements and so on, can be sent to the address <a
-href="mailto:compatibility@android.com">compatibility@android.com</a>. Like
+href="mailto:android-partnerships@google.com">android-partnerships@google.com</a>. Like
 the CTS address, this address is for specific, private inquiries; general
 questions will be directed back to the android-compatibility list.</p>
diff --git a/pdk/docs/compatibility/cts-intro.jd b/pdk/docs/compatibility/cts-intro.jd
index f1d2359..f158ca8 100644
--- a/pdk/docs/compatibility/cts-intro.jd
+++ b/pdk/docs/compatibility/cts-intro.jd
@@ -13,10 +13,14 @@
 
 <h3>Workflow</h3>
 <ol>
-<li>Obtain the CTS source code. The CTS is included in the Android source code available from the Android
-Open Source Project. (To get a copy of that source code, <a
-href="{@docRoot}source/download.html">read this page.</a></li>
+<li><a href="{@docRoot}compatibility/downloads.html">Download</a> the CTS.
 <li>Attach at least one device (or emulator) to your machine.</li>
+<li>For CTS 2.1 R2 and beyond, setup your device (or emulator) to run the accessibility tests:
+    <ol>
+        <li>adb install -r android-cts/repository/testcases/CtsDelegatingAccessibilityService.apk</li>
+        <li>On the device, enable Settings > Accessibility > Accessibility > Delegating Accessibility Service</li>
+    </ol>
+</li>
 <li>Launch the CTS. The CTS test harness loads the test plan onto the attached devices. For each test in the test harness:
     <ul>
     <li>The test harness pushes a .apk file to each device, executes the test through instrumentation, and records test results.</li>
diff --git a/pdk/docs/compatibility/downloads.jd b/pdk/docs/compatibility/downloads.jd
new file mode 100644
index 0000000..28a0401
--- /dev/null
+++ b/pdk/docs/compatibility/downloads.jd
@@ -0,0 +1,39 @@
+page.title=Android Compatibility Downloads
+doc.type=compatibility
+@jd:body
+<p>Thanks for your interest in Android Compatibility! The links below allow
+you to access the key documents and information.</p>
+
+<h2>Android 2.1</h2>
+<p>Android 2.1 is the release of the development milestone code-named
+Eclair. Android 2.1 is the current version of Android. Source code for Android
+2.1 is found in the 'eclair' branch in the open-source tree. Note that for
+technical reasons, there is no compatibility program for Android 2.0 or 2.0.1,
+and new devices must use Android 2.1.
+</p>
+<ul>
+  <li><a href="{@docRoot}compatibility/android-2.1-cdd.pdf">Android 2.1 Compatibility Definition Document (CDD)</a></li>
+  <li><a href="http://dl.google.com/dl/android/cts/android-cts-2.1_r3-x86.zip">Android 2.1 R3 Compatibility Test Suite (CTS)</a></li>
+</ul>
+
+<h2>Android 1.6</h2>
+<p>Android 1.6 was the release of the development milestone code-named Donut.
+Android 1.6 was obsoleted by Android 2.1. Source code for Android 1.6 is found
+in the 'donut' branch in the open-source tree.
+<ul>
+  <li><a href="{@docRoot}compatibility/android-1.6-cdd.pdf">Android 1.6 Compatibility Definition Document (CDD)</a></li>
+</ul>
+
+<h2>Compatibility Test Suite Manual</h2>
+<p>The CTS user manual is applicable to any CTS version, but CTS 2.1 R2 and
+beyond require
+<a href="{@docRoot}compatibility/cts-intro.html">additional steps</a>
+to run the accessibility tests.
+<ul>
+  <li><a href="{@docRoot}compatibility/android-cts-manual-r4.pdf">Compatibility Test Suite (CTS) User Manual</a></li>
+</ul>
+
+<h2>Older Android Versions</h2>
+<p>There is no Compatibility Program for older versions of Android, such as Android
+1.5 (known in development as Cupcake). New devices intended to be Android
+compatible must ship with Android 1.6 or later.</p>
diff --git a/pdk/docs/compatibility/index.jd b/pdk/docs/compatibility/index.jd
index 0c39a98..55be052 100644
--- a/pdk/docs/compatibility/index.jd
+++ b/pdk/docs/compatibility/index.jd
@@ -1,17 +1,21 @@
 page.title=Android Compatibility
 doc.type=compatibility
 @jd:body
-<p>Android is an open source product, and anyone can use the source code to build
-devices. The purpose of the Android compatibility program is to help Android
-device implementations remain compatible with all apps.</p>
-<p>A device is considered compatible if existing and new third-party
-applications run correctly on it. Poor device implementations that change APIs
-or alter behaviors will break these apps and so are not compatible. The
-Android compatibility program's aim is to ensure that these APIs are
-consistently implemented across devices.</p>
-<p>The latest version of the Android source code and compatibility program is
-1.6, which roughly corresponded to the Donut branch.  The compatibility
-program for Android 2.x (corresponding to Eclair) is coming soon.</p>
+<p>Android's purpose is to establish an open platform for developers to build
+innovative mobile apps. Three key components work together to realize this
+platform.</p>
+<p>The Android Compatibility Program defines the technical details of Android
+platform and provides tools used by OEMs to ensure that developers’ apps run
+on a variety of devices. The Android SDK provides built-in tools that
+Developers use to clearly state the device features their apps require. And
+Android Market shows apps only to those devices that can properly run
+them.</p>
+<p>These pages describe the Android Compatibility Program and how to get
+access to compatibility information and tools. The latest version of the
+Android source code and compatibility program is 2.1, which roughly
+corresponded to the Eclair branch.</p>
+
+
 <h2>Why build compatible Android devices?</h2>
 <h3>Users want a customizable device.</h3>
 <p>A mobile phone is a highly personal, always-on, always-present gateway to
@@ -20,7 +24,7 @@
 platform for running after-market applications.</p>
 
 <h3>Developers outnumber us all.</h3>
-<p>No device manufacturer can hope to write all the software that anyone could
+<p>No device manufacturer can hope to write all the software that a person could
 conceivably need. We need third-party developers to write the apps users want,
 so the Android Open Source Project aims to make it as easy and open as
 possible for developers to build apps.</p>
@@ -38,30 +42,23 @@
 sure your device is compatible with Android. For more details about the
 Android compatibility program in general, see <a
 href="{@docRoot}compatibility/overview.html">the program overview</a>.</p>
-<p>Building a compatible device is a four-step process:</p>
+<p>Building a compatible device is a three-step process:</p>
 <ol>
-  <li><b>Obtain the Android software stack source code</b><p>This is the
+  <li><b>Obtain the Android software source code</b><p>This is the
   <a href="{@docRoot}source/index.html">source code for the Android
   platform</a>, that you port to your hardware.</p></li>
-  <li><b>Comply with Android Compatibility Definition Document</b><p>
-  This document enumerates the software and the hardware features of
+  <li><b>Comply with Android Compatibility Definition Document (CDD)</b><p>
+  The CDD enumerates the software and hardware requirements of
   a compatible Android device.</p></li>
   <li><b>Pass the Compatibility Test Suite (CTS)</b><p>You can use the CTS
   (included in the Android source code) as an ongoing aid to compatibility
   during the development process.</p></li>
-  <li><b>Submit CTS report</b><p>[Optional] You can also submit your CTS report,
-  so that it can be validated and recorded.</p><p><i>Note:
-  the submission system is currently under construciton, and is not currently
-  available.</i></p></li>
 </ol>
 
-<h2>Benefits of compatibility</h2>
-<p>By submitting a validated CTS report, you receive public recognition of
-your device's compatibility. This also opens up additional options you can
-pursue such as use of the Android branding, access to Android Market, and
-more.</p>
-<p>As a consequence of some legal quirks, we aren't able to offer automatic
-licensing of either the Android Market or branding. To actually obtain access
-to these programs, you will need to <a
-href="{@docRoot}compatibility/contact-us.html">contact us</a> to obtain a
-license.</p>
+<h2>Joining the Ecosystem</h2>
+<p>Once you've built a compatible device, you may wish to include Android
+Market to provide your users access to the third-party app ecosystem.
+Unfortunately, for a variety of legal and business reasons, we aren't able to
+automatically license Android Market to all compatible devices. To inquire
+about access about Android Market, you can <a
+href="{@docRoot}compatibility/contact-us.html">contact us</a></p>
diff --git a/pdk/docs/compatibility/overview.jd b/pdk/docs/compatibility/overview.jd
index 039e2c2..31a4832 100644
--- a/pdk/docs/compatibility/overview.jd
+++ b/pdk/docs/compatibility/overview.jd
@@ -35,11 +35,13 @@
 compatible.</p></li>
 <li><b>Minimize costs and overhead associated with
 compatibility.</b><p>Ensuring compatibility should be easy and inexpensive to
-device manufacturers. The testing tool (CTS) is free and will soon be available
-in open source. CTS is designed to be used for continuous self-testing during
-the device development process to eliminate the cost of changing your workflow
-or sending your device to a third party for testing. Meanwhile, there are no
-required certifications, and thus no corresponding costs and fees.</p></li>
+device manufacturers. The testing tool (CTS) is free, open source, and
+available for <a href="{@docRoot}compatibility/downloads.html">download</a>. 
+CTS is designed to be used for continuous self-testing
+during the device development process to eliminate the cost of changing your
+workflow or sending your device to a third party for testing. Meanwhile, there
+are no required certifications, and thus no corresponding costs and
+fees.</p></li>
 </ul>
 <p>The Android compatibility program consists of three key components:</p>
 <ul>
@@ -76,8 +78,9 @@
 simply examine <a href="">the latest CDD</a>.</p>
 
 <h3>Compatibility Test Suite (CTS)</h3>
-<p>The CTS is a free, commercial-grade test suite, available along with the
-Android source code. The CTS represents the "mechanism" of compatibility.</p>
+<p>The CTS is a free, commercial-grade test suite, available for
+<a href="{@docRoot}compatibility/downloads.html">download</a>.
+The CTS represents the "mechanism" of compatibility.</p>
 <p>The CTS runs on a desktop machine and executes test cases directly on
 attached devices or an emulator. The CTS is a set of unit tests designed to be
 integrated into the daily workflow (such as via a continuous build system) of
diff --git a/pdk/docs/downloads/downloads_toc.cs b/pdk/docs/downloads/downloads_toc.cs
deleted file mode 100644
index 28f43af..0000000
--- a/pdk/docs/downloads/downloads_toc.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-<script type="text/javascript" language="JavaScript">
-<!--
-function nothing() {}
--->
-</script>
-
-<ul>
-  <li><h2>PDK</h2><ul>
-    <li><a href="">PDK 1.6</a></li>
-  </ul></li>
-
-  <li><h2>Compatibility</h2><ul>
-    <li><a href="">Android 1.6</a></li>
-  </ul></li>
-</ul>
-
-<script type="text/javascript">
-<!--
-    buildToggleLists();
-//-->
-</script>
diff --git a/pdk/docs/downloads/index.jd b/pdk/docs/downloads/index.jd
deleted file mode 100644
index b6f5a2f..0000000
--- a/pdk/docs/downloads/index.jd
+++ /dev/null
@@ -1,44 +0,0 @@
-page.title=Downloads
-doc.type=downloads
-doc.hidenav=true
-@jd:body
-<p>This page provides access to various downloads. Note that if you're looking
-for the Android SDK (for application developers), you should visit <a
-href="http://developer.android.com/sdk/index.html">developer.android.com</a>.</p>
-
-<h2>Compatibility</h2>
-<p>The Compatibility Definition Document can be downloaded below. The
-Compatibility Test Suite is available in the open-source tree.</p>
-<table class="download"> 
-  <tr> 
-      <th>Item</th> 
-      <th>File</th> 
-      <th>Size</th> 
-  </tr> 
-  <tr> 
-    <td>Android CDD 2.1</td> 
-    <td><a href="">android-cdd-2.1.pdf</a></td> 
-    <td>23070805 bytes</td> 
-  </tr> 
-  <tr class="alt-color"> 
-    <td>Android CTS 2.1 Manual</td> 
-    <td><a href="">android-cts-manual-2.1.0.pdf</a></td> 
-    <td>23070805 bytes</td> 
-  </tr> 
-  <tr> 
-    <td>Android CDD 1.6</td> 
-    <td><a href="">android-cdd-1.6.pdf</a></td> 
-    <td>23070805 bytes</td> 
-  </tr> 
-  <tr class="alt-color"> 
-    <td>Android CTS 1.6 Manual</td> 
-    <td><a href="">android-cts-manual-1.6.4.pdf</a></td> 
-    <td>23070805 bytes</td> 
-  </tr> 
-</table> 
-<p>For more information on how to build an Android-compatible device, see the
-<a href="{@docRoot}compatibility/index.html">Compatibility</a> page. Note that
-there is no compatibility program for Android 1.5 and earlier. Note also that
-there is no compatibility program for Android 2.0, since it was superceded by
-Android 2.1 after only a few weeks.
-</p>
diff --git a/pdk/docs/faqs.jd b/pdk/docs/faqs.jd
index a55d380..00db026 100644
--- a/pdk/docs/faqs.jd
+++ b/pdk/docs/faqs.jd
@@ -74,8 +74,8 @@
 <p>Finally, Google works on the next version of the Android platform in tandem
   with developing a flagship device. This branch pulls in changes from the
   experimental and stable branches as appropriate.</p>
-<p>You can find more information on this topic at our Branches Releases
-  page.</p>
+<p>You can find more information on this topic at our <a
+href="{@docRoot}source/code-lines.html">Branches and Releases</a> page.</p>
 
 <h3>Why are parts of Android developed in private?</h3>
 <p>It typically takes over a year to bring a device to market, but of course
@@ -86,16 +86,16 @@
 <p>To address this, some parts of the next version of Android including the
   core platform APIs are developed in a private branch. These APIs constitute
   the next version of Android. Our aim is to focus attention on the current
-  stable version of the Android source code, while we refine the next version
-  of the platform using the flagship Android devices. This allows developers
+  stable version of the Android source code, while we create the next version
+  of the platform as driven by flagship Android devices. This allows developers
   and OEMs to focus on a single version without having to track unfinished
-  future work just to keep up.Other parts of the Android system that aren't
+  future work just to keep up. Other parts of the Android system that aren't
   related to application compatibility are developed in the open, however.
   It's our intention to move more of these parts to open development over
   time.</p>
 
 <h3>When are source code releases made?</h3>
-<p>When they are ready. Some parts of Android are developed in the open, and
+<p>When they are ready. Some parts of Android are developed in the open,
   so that source code is always available. Other parts are developed first in
   a private tree, and that source code is released when the next platform
   version is ready.</p>
@@ -143,8 +143,7 @@
   "Android compatible devices" from devices that merely run derivatives of the
   source code. We welcome all uses of the Android source code, but only
   Android compatible devices -- as defined and tested by the Android
-  Compatibility Program -- may call themselves "Android" and participate in
-  the Android ecosystem.</p>
+  Compatibility Program -- may participate in the Android ecosystem.</p>
 
 <h3>How can I contribute to Android?</h3>
 <p>There are a number of ways you can contribute to Android. You can report
@@ -170,8 +169,9 @@
 <p>Once submitted, changes need to be accepted by a designated Approver.
   Approvers are typically Google employees, but the same approvers are
   responsible for all submissions, regardless of origin.</p>
-<p>You can find more information on this topic at the Submitting Patches
-  page.</p>
+<p>You can find more information on this topic at the <a
+   href="{@docRoot}source/submit-patches.html">Submitting Patches</a>
+   page.</p>
 
 <a name="compatibility"></a><h2>Compatibility</h2>
 <h3>What does "compatibility" mean?</h3>
@@ -185,7 +185,7 @@
 <p>In other words, compatibility is a prerequisite to participate in the
   Android apps ecosystem. Anyone is welcome to use the Android source code,
   but if the device isn't compatible, it's not considered part of the Android
-  ecosystem, and irrelevant to developers.</p>
+  ecosystem.</p>
 
 <h3>What is the role of Android Market in compatibility?</h3>
 <p>Devices that are Android compatible may seek to license the Android Market
@@ -200,11 +200,11 @@
   Compatibility Definition Document (CDD) spells out the specific device
   configurations that will be considered compatible.</p>
 <p>For example, though the Android source code could be ported to run on a
-  device that doesn't have a camera, the CDD requires that in order to be
-  compatible, all devices must have a camera. This allows developers to rely
-  on a consistent set of device capabilities when writing their apps.</p>
+  phone that doesn't have a camera, the CDD requires that in order to be
+  compatible, all phones must have a camera. This allows developers to rely
+  on a consistent set of capabilities when writing their apps.</p>
 <p>The CDD will evolve over time to reflect market realities. For instance,
-  the 1.6 CDD only allows cell phones, but the 2.x CDD allows devices to omit
+  the 1.6 CDD only allows cell phones, but the 2.1 CDD allows devices to omit
   telephony hardware, allowing for non-phone devices such as tablet-style
   music players to be compatible. As we make these changes, we will also
   augment Android Market to allow developers to retain control over where
@@ -214,13 +214,10 @@
   devices.</p>
 
 <h3>If my device is compatible, does it automatically have access to Android Market and branding?</h3>
-<p>Android Market is a service operated by Google. For legal and business
-  reasons, Google isn't able to make that service available in all parts of
-  the world. Similarly, Google is unable to license the Android trademark for
-  use in all cases.</p>
-<p>As a result, achieving compatibility does not automatically entitle a
-  device to include Android Market or use the Android name. Device
-  manufacturers should contact Google to obtain access to those tools.</p>
+<p>Android Market is a service operated by Google. Achieving compatibility is
+   a prerequisite for obtaining access to the Android Market software and branding.
+   Device manufacturers should contact Google to obtain access to Android
+   Market.</p>
 
 <h3>If I am not a manufacturer, how can I get Android Market?</h3>
 <p>Android Market is only licensed to handset manufacturers shipping devices.
@@ -229,9 +226,9 @@
 
 <h3>How can I get access to the Google apps for Android, such as Maps?</h3>
 <p>The Google apps for Android, such as YouTube, Google Maps and Navigation,
-  Gmail, and so on are not part of Android, and are licensed separately.
-  Contact android-partnerships@google.com for inquiries related to those
-  apps.</p>
+  Gmail, and so on are Google properties that are not part of Android, and
+  are licensed separately.  Contact android-partnerships@google.com for
+  inquiries related to those apps.</p>
 
 <h3>Is compatibility mandatory?</h3>
 <p>No. The Android Compatibility Program is optional. Since the Android source
@@ -246,7 +243,7 @@
   test a device.</p>
 
 <h3>How long does compatibility take?</h3>
-<p>The process is automatic. The Compatibility Test Suite generates a report
+<p>The process is automated. The Compatibility Test Suite generates a report
   that can be provided to Google to verify compatibility. Eventually we intend
   to provide self-service tools to upload these reports to a public database.</p>
 
@@ -271,12 +268,15 @@
   generally have much effect on third-party apps. As such, device builders are
   free to customize the user interface as much as they like. The Compatibility
   Definition Document does restrict the degree to which OEMs may alter the
-  system user interface for the few areas that do impact third-party apps.</p>
+  system user interface for areas that do impact third-party apps.</p>
 
 <h3>When are compatibility definitions released for new Android versions?</h3>
 <p>Our goal is to release new versions of Android Compatibility Definition
   Documents (CDDs) once the corresponding Android platform version has
-  converged enough to permit it. Since the CDDs</p>
+  converged enough to permit it. While we can't release a final draft of a CDD
+  for an Android software version before the first flagship device ships with
+  that software, final CDDs will always be released after the first device.
+  However, wherever practical we will make draft versions of CDDs available.</p>
 
 <h3>How are device manufacturers' compatibility claims validated?</h3>
 <p>There is no validation process for Android device compatibility. However,
diff --git a/pdk/docs/images/code-lines.png b/pdk/docs/images/code-lines.png
index acfb77b..f86260c 100644
--- a/pdk/docs/images/code-lines.png
+++ b/pdk/docs/images/code-lines.png
Binary files differ
diff --git a/pdk/docs/index.jd b/pdk/docs/index.jd
index 217877d..76f48da 100644
--- a/pdk/docs/index.jd
+++ b/pdk/docs/index.jd
@@ -1,7 +1,19 @@
 page.title=Welcome to Android
 home=true
 @jd:body
-<div style="float: left; width: 45%; font-size: 1.3em;">
+<div style="float: right; width: 35%;">
+<h3 style="padding-top: 0px;">News</h3>
+<p><b>Introducing the Compatibility Program</b><br/>
+We're pleased to introduce the Android Compatibility Program. We've released
+two tools -- the Compatibility Definition Document and the Compatibility Test
+Suite -- to help device manufacturers build compatible devices.</p>
+<p><b>Site redesign</b><br/>
+You're looking at the new and improved source.android.com! We've updated
+the layout and site design, and also added new information. We hope you find
+these improvements helpful.</p>
+</div>
+<img style="float: right; padding-right: 1.5em;" src="{@docRoot}images/home-bugdroid.png" alt="Android Mascot"/>
+<div style="font-size: 1.3em;">
   <p>Here you can find the information and source code you need to build an
   Android-compatible device.</p>
   <p>Android is an open-source software stack for mobile devices, and a
@@ -12,51 +24,40 @@
   created Android, and made its source code open.</p>
   <p><a href="{@docRoot}about/index.html">Learn more &raquo;</a></p>
 </div>
-<div style="float: right; width: 35%;">
-<h3 style="padding-top: 0px;">News</h3>
-<p><b>Site redesign</b><br/>
-You're looking at the new and improved source.android.com! We've updated
-the layout and site design, and also added new information. We hope you find
-these improvements helpful.</p>
-<p><b>Introducing the Compatibility Program</b><br/>
-We're pleased to introduce the Android Compatibility Program. We've released
-two tools -- the Compatibility Definition Document and the Compatibility Test
-Suite -- to help device manufacturers build compatible devices. Full details
-of the Compatibility Program will be available in the first quarter of 2010.</p>
-</div>
-<img style="float: right; padding-right: 1.5em;" src="{@docRoot}images/home-bugdroid.png" alt="Android Mascot"/>
 
 <div style="clear: both;"/>
 
 <table border="0" style="border: 0px; margin: 0px; padding: 0px;"><tr><td align="center" style="border: 0px; margin: 0px; padding: 0px;">
 <div class="rebox lil" style="float: left; width: 30%; margin: 1em;"> 
-  <h2 style="color: white; background-color: #95C0D0; border: 0px;">Get Involved</h2> 
+  <h2 style="color: white; background-color: #95C0D0; border: 0px;">Source</h2> 
   <div class="p"> 
-    <p><img src="images/lil-wrench.png" alt="" style="margin: 1em;"/>
+    <p><img src="images/lil-wrench.png" alt="" style="margin: 1em; margin-bottom: 5em;"/>
     If you're interested in contributing to the Android source code or helping
-    out with the project in some other way, click here.</p> 
-    <p><a href="{@docRoot}source/index.html">More &raquo;</a></p> 
+    out with the open-source project, our Source pages have the information
+    you need.</p> 
+    <p><a href="{@docRoot}source/index.html">Get Involved &raquo;</a></p> 
   </div> 
 </div> 
 
 <div class="rebox lil" style="float: left; width: 30%; margin: 1em;"> 
-  <h2 style="color: white; background-color: #95C0D0; border: 0px;">Build a Device</h2> 
+  <h2 style="color: white; background-color: #95C0D0; border: 0px;">Porting</h2> 
   <div class="p"> 
-    <p><img src="images/lil-wrench.png" alt="" style="margin: 1em;"/>
-    If you're an engineer building a device intended to run the Android
-    software stack, click here to find porting information and tips.</p> 
-    <p><a href="{@docRoot}porting/index.html">More &raquo;</a></p> 
+    <p><img src="images/lil-wrench.png" alt="" style="margin: 1em; margin-bottom: 5em;"/>
+    If you're an engineer building a device
+    intended to run the Android software stack, look at our Porting pages for
+    information and tips.</p> 
+    <p><a href="{@docRoot}porting/index.html">Build a Device &raquo;</a></p> 
   </div> 
 </div> 
 
 <div class="rebox lil" style="float: left; width: 30%; margin: 1em;"> 
   <h2 style="color: white; background-color: #95C0D0; border: 0px;">Compatibility</h2> 
   <div class="p"> 
-    <p><img src="images/lil-wrench.png" alt="" style="margin: 1em;"/>
-    If you're an OEM or other organization building an Android device, click
-    here to find out how to ensure that your device is fully compatible, and
-    how to take advantage of the benefits of compatibility.</p> 
-    <p><a href="{@docRoot}compatibility/index.html">More &raquo;</a></p> 
+    <p><img src="images/lil-wrench.png" alt="" style="margin: 1em; margin-bottom: 5em;"/>
+    If you're an organization building an Android device, you'll want to check out our
+    Compatibility pages to find out how to take advantage of the benefits of
+    compatibility.</p> 
+    <p><a href="{@docRoot}compatibility/index.html">Get Compatible &raquo;</a></p> 
   </div> 
 </div> 
 </td></tr></table>
diff --git a/pdk/docs/porting/bluetooth.jd b/pdk/docs/porting/bluetooth.jd
index ceb8683..c792bd2 100755
--- a/pdk/docs/porting/bluetooth.jd
+++ b/pdk/docs/porting/bluetooth.jd
@@ -17,7 +17,8 @@
 </div>
 </div>
 
-<p>Android's Bluetooth stack uses BlueZ version 3.36 for GAP, SDP, and RFCOMM profiles, and is a SIG-qualified Bluetooth 2.0 + EDR host stack.</p>
+<p>Android's Bluetooth stack uses BlueZ for GAP, SDP, and RFCOMM profiles, and
+is a SIG-qualified Bluetooth stack. </p>
 
 <p>Bluez is GPL licensed, so the Android framework interacts with userspace bluez code through D-BUS IPC to avoid proprietary code.</p>
 
@@ -33,7 +34,7 @@
 
 <a name="androidBluetoothPorting"></a><h3>Porting</h3>
 
-<p>BlueZ is Bluetooth 2.0 compatible and should work with any 2.0 chipset. There are two integration points:</p>
+<p>BlueZ is Bluetooth 2.1 compatible and should work with any 2.1 chipset and is backward compatibile with older Bluetooth versions. There are two integration points:</p>
 <p><ul>
 <li>UART driver</li>
 <li>Bluetooth Power On / Off</li>
@@ -67,7 +68,7 @@
 
 <a name="androidBluetoothTroubleshooting"></a><h3>Troubleshooting</h3>
 <p><strong>Debugging</strong></p>
-<p>To debug your bluetooth implementation, start by reading the logs (<code>adb logcat</code>) and look for ERRROR and WARNING messages regarding Bluetooth. 
+<p>To debug your bluetooth implementation, start by reading the logs (<code>adb logcat</code>) and look for ERRROR and WARNING messages regarding Bluetooth.
   Andoird uses Bluez, which comes with some useful debugging tools. The snippet below provides examples in a suggested order:</p>
 <pre>
 hciconfig -a  			# print BT chipset address and features. Useful to check if you can communicate with your BT chipset.
@@ -158,14 +159,79 @@
   <li>QDID B015261: Host stack (SDP, L2CAP, GAP, RFCOMM, SPP, AVCTP, AVRCP, GAVDP, AVDTP, A2DP)</li>
   <li>QDID B015262: EPL for HTC Sapphire (HSP, HFP)</li>
 </ul>
+<h4>Android 2.0/2.1 release (eclair)</h4>
+<h4>Platform features</h4>
+<ul>
+  <li>Based on Bluez 4.47 with Linux Kernel 2.6.29</li>
+  <li>Bluetooth 2.1+EDR host stack</li>
+  <ul>
+    <li>Support for auto-pairing with '0000' devices</li>
+    <li>Support for Simple Secure Pairing</li>
+  </ul>
+  <li>Headset Profile 1.1 in Audio Gateway role</li>
+  <li>Handsfree Profile 1.5 in Audio Gateway role</li>
+  <ul>
+    <li>Three-way calling    </li>
+    <li>Phonebook over AT commands    </li>
+    <li>Volume synchronization</li>
+    <li>eSCO</li>
+    <li>Extensive bug fixes and compatibility improvements</li>
+  </ul>
+  <li>Stereo Bluetooth (A2DP 1.2) in Source role</li>
+  <ul>
+    <li>AVDTP 1.2 in Acceptor and Initiator roles</li>
+    <li>GAVDTP 1.0 in Acceptor and Initiator roles</li>
+    <li>44.1 khz, stereo, software SBC codec</li>
+  </ul>
+  <li>Remote Control (AVRCP 1.0) in Target role</li>
+  <ul>
+    <li>AVCTP 1.3 in Target role</li>
+    <li>play/pause/stop/prev/next</li>
+  </ul>
+  <li> Object Push Profile version 1.1 </li>
+  <ul>
+     <li>Adds ability to transfer pictures, videos</li>
+     <li>Transfer of contacts using vCard is not supported in this release.</li>
+  </ul>
+  <li>Phone Book Address Profile version 1.0</li>
+  <ul>
+    <li>Phone Book Server Equipment (PSE) role supported</li>
+  </ul>
+  <li>Using Java Bluetooth APIs, an Android application can peform the
+  following:</li>
+  <ul>
+   <li>Scan for other Bluetooth devices </li>
+   <li>Query the local Bluetooth adapter for paired Bluetooth devices </li>
+   <li>Establish RFCOMM channels </li>
+   <li>Connect to other devices through service discovery </li>
+   <li>Transfer data to and from other devices </li>
+   <li>Manage multiple connections </li>
+  </ul>
+  <li>Support for Bluetooth enabled car and desk docks</li>
+  <ul>
+   <li>Framework support for routing Phone Call Audio and A2DP streaming using
+   car and desk docks. </li>
+  </ul>
+</ul>
+
+<h4>Android 2.2  release (Froyo)</h4>
+<h4>Platform features</h4>
+<ul>
+  <li>Based on Bluez 4.47 with Linux Kernel 2.6.32</li>
+  <li>No new profiles added.</li>
+  <li>Added ability to share contacts using vCard</li>
+  <li>Added ability to export all contacts - useful to transfer contacts to car kits </li>
+  <li>Improved compatibility with headsets and car kits. </li>
+</ul>
+
 <h5>&nbsp;</h5>
 <h4>Future releases</h4>
 <p>This section offers a rough guide of which features the team is developing for the next release. This feature list may change without notice. It isn't possible to post scheduling advice to the mailing lists.</p>
 <ul>
-  <li>Java Bluetooth API</li>
-  <li>Bluez 4.x with Linux Kernel 2.6.29</li>
   <li>More profiles...</li>
-  <li>Bluetooth 2.1+EDR</li>
+  <li>Improved compatibility with headsets and car kits</li>
+  <li>Bluetooth emulator support</li>
+  <li>Bluetooth Low Energy </li>
 </ul>
 
 <p><strong>Development Notes</strong></p>
@@ -184,10 +250,4 @@
 While not officially supported, you should be able to run <code>dund</code> or <code>pand</code> daemons and, using <code>pppd</code> or <code>iptables</code>, test tethering support. Next steps include plubming the DBUS APIs to these daemons up into the Android Java framework and adding code to setup the network paths via <code>pppd</code> and / or <code>iptables</code>.<br />
   <br />
   </li>
-  <li><strong>Emulator Support</strong><br />
-  The Android emulator does not support Bluetooth at this time and there currently aren't any plans for its support.<br />
-    <br />
-  </li>
-  <li><strong>Bluetooth 2.1 and Simple Pairing Support</strong><br />
-  In order to support these features, Android needs to move to a Bluez 4.x version. This change is not scheduled at this time.</li>
 </ul>
diff --git a/pdk/docs/porting/instrumentation_testing.jd b/pdk/docs/porting/instrumentation_testing.jd
index c3765f4..045291f 100755
--- a/pdk/docs/porting/instrumentation_testing.jd
+++ b/pdk/docs/porting/instrumentation_testing.jd
@@ -335,7 +335,7 @@
 <pre class="prettify">
 public class FrameworkInstrumentationTestRunner extends InstrumentationTestRunner {
 
-    @Override
+    &#64;Override
     public TestSuite getAllTests() {
         InstrumentationTestSuite suite = new InstrumentationTestSuite(this);
 
@@ -345,7 +345,7 @@
         return suite;
     }
 
-    @Override
+    &#64;Override
     public ClassLoader getLoader() {
         return FrameworkInstrumentationTestRunner.class.getClassLoader();
     }
@@ -373,7 +373,7 @@
         super("com.example", MyActivity.class);
     }
 
-    @Override
+    &#64;Override
     public void setUp() throws Exception {
       super.setUp();
       mLeftButton = (Button) getActivity().findViewById(R.id.leftButton);
diff --git a/pdk/docs/source/building-dream.jd b/pdk/docs/source/building-dream.jd
index 89392fd..d130524 100644
--- a/pdk/docs/source/building-dream.jd
+++ b/pdk/docs/source/building-dream.jd
@@ -3,10 +3,10 @@
 @jd:body
 <p><i>The information on this page is a bit out of date. We'll update this
 page as soon as we can.</i></p>
-<div>The basic manifest for cupcake (and above) defines which projects are
+<div>The basic manifest for 1.6 defines which projects are
 needed to do a generic build for the emulator or for unlocked Dream devices
 (e.g. the Android Dev Phone 1). You need to have an appropriate device running
-a matching official image.<br><br>To build donut or master for dream (your
+a matching official image.<br><br>To build donut for dream (your
 device needs to be an ADP1 running an official 1.6 system):<br><ol><li>Follow
 the <a href="{@docRoot}source/download.html">normal steps</a>
 to setup repo and check out the sources.
@@ -22,17 +22,6 @@
 </li>
 <li>from this point, the fastboot tool (which is put automatically in your path) can be used to flash a device: boot the device into the bootloader by holding the back key while pressing the power key, and run "fastboot -w flashall".<br></li>
 </ol>
-To build cupcake for dream (your device needs to be an ADP1 running an official 1.5 system):<br><ol><li>Follow the <a href="{@docRoot}source/download.html">normal steps</a>
-to setup repo and check out the sources.
-</li>
-<li>At the root of your source tree, run ". build/envsetup.sh" like you normally would for an emulator build.
-</li>
-<li>Run "make adb" if you don't already have adb in your path.<br></li>
-<li>in vendor/htc/dream-open/ there is a script called "extract-files.sh" that must be run (from that directory) to extract some proprietary binaries from your device (*). You only need to do this once.<br></li>
-<li>run "lunch htc_dream-eng" to specifically configure the build system for dream (the default is the equivalent of "lunch generic-eng", which doesn't contain dream-specific files).<br></li>
-<li>run make from the top of the source tree.
-</li>
-<li>from this point, the fastboot tool (which is put automatically in your path) can be used to flash a device: boot the device into the bootloader by holding the back key while pressing the power key, and run "fastboot -w flashall".<br></li>
-</ol>
-* The Dream device software contains some proprietary binaries.For contractual reasons, these cannot be redistributed to be used directly with the Android Open-Source Project, but the provided script may be used to extract these binaries from your development device so that they can be correctly included in your build.These libraries include the openGL|ES library, the Qualcomm camera library, the HTC Radio Interface Library, etc.
+<p>Note: these instructions work for the sapphire (ADP2) build target, as
+well. Simply replace "dream" with "sapphire" above.</p>
 </div>
diff --git a/pdk/docs/source/code-lines.jd b/pdk/docs/source/code-lines.jd
index 61b400d..5e3c91f 100644
--- a/pdk/docs/source/code-lines.jd
+++ b/pdk/docs/source/code-lines.jd
@@ -15,7 +15,7 @@
 <h3>Notes and Explanations</h3>
 <ul>
 <li>A <i>release</i> corresponds to a formal version of the Android platform, such
-as 1.5, 2.0, and so on. Generally speaking, a release of the platform
+as 1.5, 2.1, and so on. Generally speaking, a release of the platform
 corresponds to a version of the <code>SdkVersion</code> field used in
 AndroidManifest.xml files, and defined in <code>frameworks/base/api</code> in
 the source tree.</li>
@@ -25,32 +25,32 @@
 Android projects (such as Dalvik, the Android SDK tools, Bionic, and so on) to
 work as "upstream" projects. These will be developed entirely in the public
 tree, and snapshots will be periodically pulled into releases.</li>
-<li>The diagram refers to "Eclair" and "Flan"; however, they are simply
+<li>The diagram refers to "Eclair" and "FroYo"; however, they are simply
 placeholders, and the diagram actually reflects the overall release and
 branching strategy.</li>
-<li>At all times, the Release code-line (which may actually consist of
+<li>At all times, a release code-line (which may actually consist of
 more than one actual branch in git) is considered the sole canonical source
-code for a given Android platform. OEMs and other groups building devices
-should pull only from a Release branch.</li>
-<li>We will be setting up an "Experimental" code-line to capture changes from
+code for a given Android platform version. OEMs and other groups building devices
+should pull only from a release branch.</li>
+<li>We will set up "experimental" code-lines to capture changes from
 the community, so that they can be iterated on, with an eye toward stability.</li>
-<li>Changes that prove stable will eventually be pulled into a Release
+<li>Changes that prove stable will eventually be pulled into a release
 branch. Note that this will only apply to bug fixes, app improvements, and
 other things that do not affect the APIs of the platform.</li>
-<li>Changes will be pulled into Release branches from upstream projects
-(include the Android "upstream" projects) as necessary.</li>
+<li>Changes will be pulled into release branches from upstream projects
+(including the Android "upstream" projects) as necessary.</li>
 <li>The "n+1"th version (that is, next major version of the framework and
-platform APIs) will be developed by Google internally. (See below for
-details.)</li>
-<li>Changes will be pulled from upstream, Release, and Experimental branches
+platform APIs) will be developed by Google internally. See below for
+details.</li>
+<li>Changes will be pulled from upstream, release, and experimental branches
 into Google's private branch as necessary.</li>
 <li>When the platform APIs for the next version have stabilized and been fully
 tested, Google will cut a release of the next platform version. (This
 specifically refers to a new <code>SdkVersion</code>.) This will also
-correspond to the internal code-line being made a public Release branch, and the
+correspond to the internal code-line being made a public release branch, and the
 new current platform code-line.</li>
-<li>When a new platform version is cut, a corresponding Experimental
-code-line.</li>
+<li>When a new platform version is cut, a corresponding experimental
+code-line will be created at the same time.</li>
 </ul>
 <h3>About Private Code-Lines</h3>
 <p>The source management strategy above includes a code-line that Google will
@@ -62,9 +62,9 @@
 Google retains responsibility for the strategic direction of Android as a
 platform and a product. Our approach is based on focusing on a small number of
 flagship devices to drive features, and secure protections of Android-related
-intellectual property through patents and the like.</p>
+intellectual property.</p>
 <p>As a result, Google frequently has possession of confidential
-information of third parties, and we must refrain from revealing patentable
+information of third parties, and we must refrain from revealing sensitive
 features until we've secured the appropriate protections. Meanwhile, there are
 real risks to the platform arising from having too many platform versions
 extant at once. For these reasons, we have structured the open-source project
diff --git a/pdk/docs/source/code-style.jd b/pdk/docs/source/code-style.jd
index 8b5946e..957ab02 100644
--- a/pdk/docs/source/code-style.jd
+++ b/pdk/docs/source/code-style.jd
@@ -1,7 +1,6 @@
 page.title=Code Style Guidelines for Contributors
 doc.type=source
 @jd:body
-<div>
 <p>The rules below are not guidelines or recommendations, but strict rules.
 Contributions to Android generally <b>will not be accepted if they do not
 adhere to these rules.</b>
@@ -11,425 +10,615 @@
 </h1>
 <p>We follow standard Java coding conventions. We add a few rules:
 </p>
-<ol><li><a href="#exceptionsIgnore">Exceptions</a>
-: Never catch and ignore them without explanation.
-</li>
-<li><a href="#exceptionsAll">Exceptions</a>
-: do not catch generic Exception, except in library code at the root of the stack.
-</li>
-<li><a href="#finalizers">Finalizers</a>
-: generally don't use them.
-</li>
-<li><a href="#imports">Imports</a>
-: Fully qualify imports
-</li>
+<ol><li><a href="#exceptionsIgnore">Exceptions</a>: Never catch and ignore them without explanation.</li>
+<li><a href="#exceptionsAll">Exceptions</a>: do not catch generic Exception, except in library code at the root of the stack.</li>
+<li><a href="#finalizers">Finalizers</a>: generally don't use them.</li>
+<li><a href="#imports">Imports</a>: Fully qualify imports</li>
 </ol>
-<h1><a>Java Library Rules</a>
-</h1>
-<p>There are conventions for using Android's Java libraries and tools. In some cases, the convention has changed in important ways and older code might use a deprecated pattern or library. When working with such code, it's okay to continue the existing style (see <a href="#consistency">Consistency</a>
-). When creating new components never use deprecated libraries.
-</p>
-<h1><a>Java Style Rules</a>
-</h1>
-<p>Programs are much easier to maintain when all files have a consistent style. We follow the standard Java coding style, as defined by Sun in their <a href="http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html">Code Conventions for the Java Programming Language</a>
-, with a few exceptions and additions. This style guide is comprehensive and detailed and is in common usage in the Java community.
-</p>
-<p>In addition, we enforce the following style rules:
-</p>
-<ol><li><a href="#javadoc">Comments/Javadoc</a>
-: write it; use standard style
-</li>
-<li><a href="#shortmethods">Short methods</a>
-: don't write giant methods
-</li>
-<li>Fields: should either be at the top of the file, or immediately before the methods that use them
-</li>
-<li><a href="#localvariables">Local variables</a>
-: limit the scope
-</li>
-<li><a href="#import_style">Imports</a>
-: android; third party alphabetical; java(x)
-</li>
-<li><a href="#indentation">Indentation</a>
-: 4 spaces, no tabs.
-</li>
-<li><a href="#linelen">Line length</a>
-: 100 columns
-</li>
-<li><a href="#field_names">Field names</a>
-: Non-public, non-static fields start with m. Static fields start s.
-</li>
-<li><a href="#braces">Braces</a>
-: Opening braces don't go on their own line.
-</li>
-<li><a href="#annotations">Annotations</a>
-: Use the standard annotations.
-</li>
-<li><a href="#acronyms">Acronyms are words</a>
-: Treat acronyms as words in names, yielding XmlHttpRequest, getUrl(), etc.
-</li>
-<li><a href="#todo">TODO style</a>
-: "TODO: write this description"
-</li>
-<li><a href="#consistency">Consistency</a>
-: Look at what's around you!
-</li>
-<li><a href="#logging">Logging</a>
-: Be careful with logging. It's expensive.
-</li>
+<h1>Java Library Rules</h1>
+<p>There are conventions for using Android's Java libraries and tools. In some
+cases, the convention has changed in important ways and older code might use a
+deprecated pattern or library. When working with such code, it's okay to
+continue the existing style (see <a href="#consistency">Consistency</a>). When
+creating new components never use deprecated libraries.</p>
+<h1>Java Style Rules</h1>
+<p>Programs are much easier to maintain when all files have a consistent
+style. We follow the standard Java coding style, as defined by Sun in their <a
+href="http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html">Code
+Conventions for the Java Programming Language</a>, with a few exceptions and
+additions. This style guide is comprehensive and detailed and is in common
+usage in the Java community.</p>
+<p>In addition, we enforce the following style rules:</p>
+<ol><li><a href="#javadoc">Comments/Javadoc</a>: write it; use standard style</li>
+<li><a href="#shortmethods">Short methods</a>: don't write giant methods</li>
+<li>Fields: should either be at the top of the file, or immediately before the methods that use them</li>
+<li><a href="#localvariables">Local variables</a>: limit the scope</li>
+<li><a href="#import_style">Imports</a>: android; third party alphabetical; java(x)</li>
+<li><a href="#indentation">Indentation</a>: 4 spaces, no tabs.</li>
+<li><a href="#linelen">Line length</a>: 100 columns</li>
+<li><a href="#field_names">Field names</a>: Non-public, non-static fields start with m.</li>
+<li><a href="#braces">Braces</a>: Opening braces don't go on their own line.</li>
+<li><a href="#annotations">Annotations</a>: Use the standard annotations.</li>
+<li><a href="#acronyms">Acronyms are words</a>: Treat acronyms as words in names, yielding XmlHttpRequest, getUrl(), etc.</li>
+<li><a href="#todo">TODO style</a>: "TODO: write this description"</li>
+<li><a href="#consistency">Consistency</a>: Look at what's around you!</li>
+<li><a href="#logging">Logging</a>: Be careful with logging. It's expensive.</li>
 </ol>
-<h1><a>Javatests Style Rules</a>
-</h1>
-<ol><li><a href="#testmethodnames">Naming test methods</a>
-: testMethod_specificCase is ok
-</li>
+<h1>Javatests Style Rules</h1>
+<ol>
+<li><a href="#testmethodnames">Naming test methods</a>: testMethod_specificCase is ok</li>
 </ol>
-<hr><h2>
-Java Language Rules
-</h2>
-<h2><a>Exceptions: do not ignore</a>
-</h2>
-Sometimes it is tempting to write code that completely ignores an exception like this:
-<pre>void setServerPort(String value) {<br>try {<br>serverPort = Integer.parseInt(value);<br>} catch (NumberFormatException e) {<br>}<br>}<br><br></pre>
-<p>You must never do this. While you may think that your code will never encounter this error condition or that it is not important to handle it, ignoring exceptions like above creates mines in your code for someone else to trip over some day. You must handle every Exception in your code in some principled way. The specific handling varies depending on the case.
-</p>
-<blockquote>Anytime somebody has an empty catch clause they should have a creepy feeling. There are definitely times when it is actually the correct thing to do, but at least you have to think about it. In Java you can't escape the creepy feeling.<br><div>-<a href="http://www.artima.com/intv/solid4.html">James Gosling</a>
-</div>
-</blockquote>
-<p>Acceptable alternatives (in order of preference) are:
-</p>
-<ul><li>Throw the exception up to the caller of your method.
-<pre>void setServerPort(String value) throws NumberFormatException {<br>serverPort = Integer.parseInt(value);<br>}<br><br></pre>
-</li>
+<h2>Java Language Rules</h2>
+<h2><a name="exceptionsIgnore"></a>Exceptions: do not ignore</h2>
+<p>Sometimes it is tempting to write code that completely ignores an exception
+like this:</p>
+<pre>void setServerPort(String value) {
+    try {
+        serverPort = Integer.parseInt(value);
+    } catch (NumberFormatException e) { }
+}</pre>
+<p>You must never do this. While you may think that your code will never
+encounter this error condition or that it is not important to handle it,
+ignoring exceptions like above creates mines in your code for someone else to
+trip over some day. You must handle every Exception in your code in some
+principled way. The specific handling varies depending on the case.</p>
+<blockquote>Anytime somebody has an empty catch clause they should have a
+creepy feeling. There are definitely times when it is actually the correct
+thing to do, but at least you have to think about it. In Java you can't escape
+the creepy feeling.
+-<a href="http://www.artima.com/intv/solid4.html">James
+Gosling</a></blockquote>
+<p>Acceptable alternatives (in order of preference) are:</p>
+<ul>
+<li>Throw the exception up to the caller of your method.
+<pre>void setServerPort(String value) throws NumberFormatException {
+    serverPort = Integer.parseInt(value);
+}</pre></li>
 <li>Throw a new exception that's appropriate to your level of abstraction.
-<pre>void setServerPort(String value) throws ConfigurationException {<br>try {<br>serverPort = Integer.parseInt(value);<br>} catch (NumberFormatException e) {<br>throw new ConfigurationException("Port " + value + " is not valid.");<br>}<br><br></pre>
-</li>
-<li>Handle the error gracefully and substitute an appropriate value in the catch {} block.
-<pre>/** Set port. If value is not a valid number, 80 is substituted. */<br>void setServerPort(String value) {<br>try {<br>serverPort = Integer.parseInt(value);<br>} catch (NumberFormatException e) {<br>serverPort = 80;  // default port for server <br>}<br></pre>
-</li>
-<li>Catch the Exception and throw a new RuntimeException. This is dangerous: only do it if you are positive that if this error occurs, the appropriate thing to do is crash.
-<pre>/** Set port. If value is not a valid number, die. */<br>void setServerPort(String value) {<br>try {<br>serverPort = Integer.parseInt(value);<br>} catch (NumberFormatException e) {<br>throw new RuntimeException("port " + value " is invalid, ", e);<br>}<br></pre>
-Note that the original exception is passed to the constructor for RuntimeException. This wrapped exception paradigm is very useful but only works in Java 1.4. If your code must compile under Java 1.3, you will need to omit the exception that is the cause.<br><br></li>
-<li>Last resort: if you are confident that actually ignoring the exception is appropriate then you may ignore it, but you must also comment why with a good reason:
-<pre>/** If value is not a valid number, original port number is used. */<br>void setServerPort(String value) {<br>try {<br>serverPort = Integer.parseInt(value);<br>} catch (NumberFormatException e) {<br>// Method is documented to just ignore invalid user input.<br>// serverPort will just be unchanged.<br>}<br>}<br></pre>
-</li>
+<pre>void setServerPort(String value) throws ConfigurationException {
+    try {
+        serverPort = Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+        throw new ConfigurationException("Port " + value + " is not valid.");
+    }
+}</pre></li>
+<li>Handle the error gracefully and substitute an appropriate value in the
+catch {} block.
+<pre>/** Set port. If value is not a valid number, 80 is substituted. */
+void setServerPort(String value) {
+    try {
+        serverPort = Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+        serverPort = 80;  // default port for server 
+    }
+}</pre></li>
+<li>Catch the Exception and throw a new RuntimeException. This is dangerous:
+only do it if you are positive that if this error occurs, the appropriate
+thing to do is crash.
+<pre>/** Set port. If value is not a valid number, die. */
+void setServerPort(String value) {
+    try {
+        serverPort = Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+        throw new RuntimeException("port " + value " is invalid, ", e);
+    }
+}</pre>
+Note that the original exception is passed to the constructor for
+RuntimeException.  If your code must compile under Java 1.3, you will need to
+omit the exception that is the cause.</li>
+<li>Last resort: if you are confident that actually ignoring the exception is
+appropriate then you may ignore it, but you must also comment why with a good
+reason:
+<pre>/** If value is not a valid number, original port number is used. */
+void setServerPort(String value) {
+    try {
+        serverPort = Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+        // Method is documented to just ignore invalid user input.
+        // serverPort will just be unchanged.
+    }
+}</pre></li>
 </ul>
-<h2><a>Exceptions: do not catch generic Exception</a>
-</h2>
-Sometimes it is tempting to be lazy when catching exceptions and do something like this:
-<pre>try {<br>someComplicatedIOFunction();        // may throw IOException <br>someComplicatedParsingFunction();   // may throw ParsingException <br>someComplicatedSecurityFunction();  // may throw SecurityException <br>// phew, made it all the way <br>} catch (Exception e) {               // I'll just catch all exceptions <br>handleError();                      // with one generic handler!<br>}<br><br></pre>
-You should not do this. In almost all cases it is inappropriate to catch generic Exception or Throwable, preferably not Throwable, because it includes Error exceptions as well. It is very dangerous. It means that Exceptions you never expected (including RuntimeExceptions like ClassCastException) end up getting caught in application-level error handling. It obscures the failure handling properties of your code. It means if someone adds a new type of Exception in the code you're calling, the compiler won't help you realize you need to handle that error differently. And in most cases you shouldn't be handling different types of exception the same way, anyway.
-<p>There are rare exceptions to this rule: certain test code and top-level code where you want to catch all kinds of errors (to prevent them from showing up in a UI, or to keep a batch job running). In that case you may catch generic Exception (or Throwable) and handle the error appropriately. You should think very carefully before doing this, though, and put in comments explaining why it is safe in this place.
-</p>
-<p>Alternatives to catching generic Exception:
-</p>
-<ul><li>Catch each exception separately as separate catch blocks after a single try. This can be awkward but is still preferable to catching all Exceptions. Beware repeating too much code in the catch blocks.
-</li>
-<li>Refactor your code to have more fine-grained error handling, with multiple try blocks. Split up the IO from the parsing, handle errors separately in each case.
-</li>
-<li>Rethrow the exception. Many times you don't need to catch the exception at this level anyway, just let the method throw it.
-</li>
+<h2><a name="exceptionsAll"></a>Exceptions: do not catch generic Exception</h2>
+<p>Sometimes it is tempting to be lazy when catching exceptions and do
+something like this:</p>
+<pre>try {
+    someComplicatedIOFunction();        // may throw IOException 
+    someComplicatedParsingFunction();   // may throw ParsingException 
+    someComplicatedSecurityFunction();  // may throw SecurityException 
+    // phew, made it all the way 
+} catch (Exception e) {               // I'll just catch all exceptions 
+    handleError();                      // with one generic handler!
+}</pre>
+<p>You should not do this. In almost all cases it is inappropriate to catch
+generic Exception or Throwable, preferably not Throwable, because it includes
+Error exceptions as well. It is very dangerous. It means that Exceptions you
+never expected (including RuntimeExceptions like ClassCastException) end up
+getting caught in application-level error handling. It obscures the failure
+handling properties of your code. It means if someone adds a new type of
+Exception in the code you're calling, the compiler won't help you realize you
+need to handle that error differently. And in most cases you shouldn't be
+handling different types of exception the same way, anyway.</p>
+<p>There are rare exceptions to this rule: certain test code and top-level
+code where you want to catch all kinds of errors (to prevent them from showing
+up in a UI, or to keep a batch job running). In that case you may catch
+generic Exception (or Throwable) and handle the error appropriately. You
+should think very carefully before doing this, though, and put in comments
+explaining why it is safe in this place.</p>
+<p>Alternatives to catching generic Exception:</p>
+<ul>
+<li>Catch each exception separately as separate catch blocks after a single
+try. This can be awkward but is still preferable to catching all Exceptions.
+Beware repeating too much code in the catch blocks.</li>
+<li>Refactor your code to have more fine-grained error handling, with multiple
+try blocks. Split up the IO from the parsing, handle errors separately in each
+case.</li>
+<li>Rethrow the exception. Many times you don't need to catch the exception at
+this level anyway, just let the method throw it.</li>
 </ul>
-Remember: exceptions are your friend! When the compiler complains you're not catching an exception, don't scowl. Smile: the compiler just made it easier for you to catch runtime problems in your code.
-<h2><a>Finalizers</a>
-</h2>
-<p><b>What it is</b>
-: Finalizers are a way to have a chunk of code executed when an object is garbage collected.
-</p>
-<p><b>Pros</b>
-: can be handy for doing cleanup, particularly of external resources.
-</p>
-<p><b>Cons</b>
-: there are no guarantees as to when a finalizer will be called, or even that it will be called at all.
-</p>
-<p><b>Decision</b>
-: we don't use finalizers. In most cases, you can do what you need from a finalizer with good exception handling. If you absolutely need it, define a close() method (or the like) and document exactly when that method needs to be called. See InputStream for an example. In this case it is appropriate but not required to print a short log message from the finalizer, as long as it is not expected to flood the logs.
-</p>
-<p>The one exception is it is OK to write a finalizer if all it does is make calls to X.assertTrue().
-</p>
-<h2><a>Imports</a>
-</h2>
-<h3>
-Wildcards in imports
-</h3>
-<p><b>What it is</b>
-: When you want to use class Bar from package foo,there are two possible ways to import it:
-</p>
-<ol><li>import foo.*;
-</li>
-<li>import foo.Bar;
-</li>
+<p>Remember: exceptions are your friend! When the compiler complains you're
+not catching an exception, don't scowl. Smile: the compiler just made it
+easier for you to catch runtime problems in your code.</p>
+<h2><a name="finalizers"></a>Finalizers</h2>
+<p><b>What it is</b>: Finalizers are a way to have a chunk of code executed
+when an object is garbage collected.</p>
+<p><b>Pros</b>: can be handy for doing cleanup, particularly of external
+resources.</p>
+<p><b>Cons</b>: there are no guarantees as to when a finalizer will be called,
+or even that it will be called at all.</p>
+<p><b>Decision</b>: we don't use finalizers. In most cases, you can do what
+you need from a finalizer with good exception handling. If you absolutely need
+it, define a close() method (or the like) and document exactly when that
+method needs to be called. See InputStream for an example. In this case it is
+appropriate but not required to print a short log message from the finalizer,
+as long as it is not expected to flood the logs.</p>
+<h2><a name="imports"></a>Imports</h2>
+<h3>Wildcards in imports</h3>
+<p><b>What it is</b>: When you want to use class Bar from package foo,there
+are two possible ways to import it:</p>
+<ol>
+<li><code>import foo.*;</code></li>
+<li><code>import foo.Bar;</code></li>
 </ol>
-<p><b>Pros of #1</b>
-: Potentially reduces the number of import statements.
+<p><b>Pros of #1</b>: Potentially reduces the number of import statements.
 </p>
-<p><b>Pros of #2</b>
-: Makes it obvious what classes are actually used. Makes code more readable for maintainers.
-</p>
-<p><b>Decision</b>
-:Use style #2 for importing all Android code. An explicit exception is made for java standard libraries (java.util.*, java.io.*, etc.) and unit test code (junit.framework.*).
-</p>
-<h2><a>Comments/Javadoc</a>
-</h2>
-<p>Every file should have a copyright statement at the top. Then a package statement and import statements should follow, each block separated by a blank line. And then there is the class or interface declaration. In the Javadoc comments, describe what the class or interface does.
-</p>
-<pre>/*<br>* Copyright (C) 2007 The Android Open Source Project <br>*<br>* Licensed under the Apache License, Version 2.0 (the "License");<br>* you may not use this file except in compliance with the License.<br>* You may obtain a copy of the License at <br>*<br>*      http://www.apache.org/licenses/LICENSE-2.0<br>*<br>* Unless required by applicable law or agreed to in writing, software <br>* distributed under the License is distributed on an "AS IS" BASIS,<br>* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.<br>* See the License for the specific language governing permissions and <br>* limitations under the License.<br>*/<br><br>package com.android.internal.foo;<br><br>import android.os.Blah;<br>import android.view.Yada;<br><br>import java.sql.ResultSet;<br>import java.sql.SQLException;<br><br>/**<br>* Does X and Y and provides an abstraction for Z.<br>*/<br>public class Foo {<br>...<br>}<br></pre>
-<p>Every class and nontrivial public method you write <b>must</b>
-contain a Javadoc comment with at least one sentence describing what the class or method does. This sentence should start with a 3rd person descriptive verb. Examples:
-</p>
-<pre>/** Returns the correctly rounded positive square root of a double value. */<br>static double sqrt(double a) {<br>}<br><br>/**<br>* Constructs a new String by converting the specified array of <br>* bytes using the platform's default character encoding.<br>*/<br>public String(byte[] bytes) {<br>}<br></pre>
-<p>You do not need to write Javadoc for trivial get and set methods such as setFoo() if all your Javadoc would say is "sets Foo". If the method does something more complex (such as enforcing a constraint or having an important side effect), then you must document it. And if it's not obvious what the property "Foo" means, you should document it.
-</p>
-<p>Every method you write, whether public or otherwise, would benefit from Javadoc. Public methods are part of an API and therefore require Javadoc.
-</p>
-Android does not currently enforce a specific style for writing Javadoc comments, but you <b>should</b>
-follow the <a href="http://java.sun.com/j2se/javadoc/writingdoccomments/">Sun Javadoc conventions</a>
-.
-<h2><a>Short methods</a>
-</h2>
-To the extent that it is feasible, methods should be kept small and focused. It is, however, recognized that long methods are sometimes appropriate, so no hard limit is placed on method length. If a method exceeds 40 lines or so, think about whether it can be broken up without harming the structure of the program.
-<h2><a>Local variables</a>
-</h2>
-The scope of local variables should be kept to a minimum (<i>Effective Java</i>
-Item 29). By doing so, you increase the readability and maintainability of your code and reduce the likelihood of error. Each variable should be declared in the innermost block that encloses all uses of the variable.
-<p>Local variables should be declared at the point they are first used. Nearly every local variable declaration should contain an initializer. If you don't yet have enough information to initialize a variable sensibly, you should postpone the declaration until you do.
-</p>
-<p>One exception to this rule concerns try-catch statements. If a variable is initialized with the return value of a method that throws a checked exception, it must be initialized inside a try block. If the value must be used outside of the try block, then it must be declared before the try block, where it cannot yet be sensibly initialized:
-</p>
-<pre>// Instantiate class cl, which represents some sort of Set <br>Set s = null;<br>try {<br>s = (Set) cl.newInstance();<br>} catch(IllegalAccessException e) {<br>throw new IllegalArgumentException(cl + " not accessible");<br>} catch(InstantiationException e) {<br>throw new IllegalArgumentException(cl + " not instantiable");<br>}<br><br>// Exercise the set <br>s.addAll(Arrays.asList(args));<br></pre>
-<p>But even this case can be avoided by encapsulating the try-catch block in a method:
-</p>
-<pre>Set createSet(Class cl) {<br>// Instantiate class cl, which represents some sort of Set <br>try {<br>return (Set) cl.newInstance();<br>} catch(IllegalAccessException e) {<br>throw new IllegalArgumentException(cl + " not accessible");<br>} catch(InstantiationException e) {<br>throw new IllegalArgumentException(cl + " not instantiable");<br>}<br>}<br>...<br>// Exercise the set <br>Set s = createSet(cl);<br>s.addAll(Arrays.asList(args));<br></pre>
-Loop variables should be declared in the for statement itself unless there is a compelling reason to do otherwise:
-<pre>for (int i = 0; i n; i++) {<br>doSomething(i);<br>}<br><br>for (Iterator i = c.iterator(); i.hasNext(); ) {<br>doSomethingElse(i.next());<br>}<br><br><br></pre>
-<h2><a>Imports</a>
-</h2>
-The ordering of import statements is:Android importsImports from third parties (com, junit, net, org)<br>java and javax
-<p>To exactly match the IDE settings, the imports should be:
-</p>
-Alphabetical within each grouping.<br>Capital letters are considered to come before lower case letter (e.g. Z before a).There should be a blank line between each major grouping (android, com, junit, net, org, java, javax).
-<h4>
-Why?
-</h4>
-<p>Originally there was no style requirement on the ordering. This meant that the IDE's were either always changing the ordering, or IDE developers had to disable the automatic import management features and maintain the imports by hand. This was deemed bad. When java-style was asked, the preferred styles were all over the map. It pretty much came down to our needing to "pick an ordering and be consistent." So we chose a style, updated the javaguide and made the IDE's obey it. We expect that as IDE users work on the code, the imports in all of the packages will end up matching this pattern without any extra engineering effort.
-</p>
-<p>The style chosen such that:
-</p>
-The imports people want to look at first tend to be at the top (android)The imports people want to look at least tend to be at the bottom (java)Humans can easily follow the styleThe IDE's can follow the style
-<h3>
-What about static imports?
-</h3>
-The use and location of static imports have been mildly controversial issues. Some people would prefer static imports to be interspersed with the remaining imports, some would prefer them reside above or below all other imports. Additinally, we have not yet come up with a way to make all IDEs use the same ordering.
-<p>Since most people consider this a low priority issue, just use your judgement and please be consistent.
-</p>
+<p><b>Pros of #2</b>: Makes it obvious what classes are actually used. Makes
+code more readable for maintainers. </p>
+<p><b>Decision</b>: Use style #2 for importing all Android code. An explicit
+exception is made for java standard libraries (java.util.*, java.io.*, etc.)
+and unit test code (junit.framework.*).</p>
+<h2><a name="javadoc"></a>Comments/Javadoc</h2>
+<p>Every file should have a copyright statement at the top. Then a package
+statement and import statements should follow, each block separated by a blank
+line. And then there is the class or interface declaration. In the Javadoc
+comments, describe what the class or interface does.</p>
+<pre>/*
+ * Copyright (C) 2010 The Android Open Source Project 
+ *
+ * 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.
+ */
 
-<h2><a>Indentation</a>
-</h2>
-<p>We use 4 space indents for blocks. We never use tabs. When in doubt, be consistent with code around you.
-</p>
-<p>We use 8 space indents for line wraps, including function calls and assignments. For example, this is correct:
-</p>
-<pre>Instrument i <br>= someLongExpression(that, wouldNotFit, on, one, line);</pre>
-and this is not correct:
-<pre>Instrument i <br>= someLongExpression(that, wouldNotFit, on, one, line);</pre>
-<h2><a>Field Names</a>
-</h2>
-<ul><li>Non-public, non-static field names start with m.
-</li>
-<li>Static field names start with s.
-</li>
-<li>Other fields start with a lower case letter.
-</li>
-<li>Public static final fields (constants) are ALL_CAPS_WITH_UNDERSCORES.
-</li>
+package com.android.internal.foo;
+
+import android.os.Blah;
+import android.view.Yada;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Does X and Y and provides an abstraction for Z.
+ */
+public class Foo {
+    ...
+}</pre>
+<p>Every class and nontrivial public method you write <b>must</b> contain a
+Javadoc comment with at least one sentence describing what the class or method
+does. This sentence should start with a 3rd person descriptive verb.
+Examples:</p>
+<pre>/** Returns the correctly rounded positive square root of a double value. */
+static double sqrt(double a) {
+}
+
+/**
+ * Constructs a new String by converting the specified array of 
+ * bytes using the platform's default character encoding.
+ */
+public String(byte[] bytes) {
+}</pre>
+<p>You do not need to write Javadoc for trivial get and set methods such as
+setFoo() if all your Javadoc would say is "sets Foo". If the method does
+something more complex (such as enforcing a constraint or having an important
+side effect), then you must document it. And if it's not obvious what the
+property "Foo" means, you should document it.</p>
+<p>Every method you write, whether public or otherwise, would benefit from
+Javadoc. Public methods are part of an API and therefore require Javadoc.</p>
+<p>Android does not currently enforce a specific style for writing Javadoc
+comments, but you <b>should</b> follow the <a
+href="http://java.sun.com/j2se/javadoc/writingdoccomments/">Sun Javadoc
+conventions</a>.</p>
+<h2><a name="shortmethods"></a>Short methods</h2>
+<p>To the extent that it is feasible, methods should be kept small and
+focused. It is, however, recognized that long methods are sometimes
+appropriate, so no hard limit is placed on method length. If a method exceeds
+40 lines or so, think about whether it can be broken up without harming the
+structure of the program.</p>
+<h2><a name="localvariables"></a>Local variables</h2>
+<p>The scope of local variables should be kept to a minimum (<i>Effective
+Java</i> Item 29). By doing so, you increase the readability and
+maintainability of your code and reduce the likelihood of error. Each variable
+should be declared in the innermost block that encloses all uses of the
+variable.</p>
+<p>Local variables should be declared at the point they are first used. Nearly
+every local variable declaration should contain an initializer. If you don't
+yet have enough information to initialize a variable sensibly, you should
+postpone the declaration until you do.</p>
+<p>One exception to this rule concerns try-catch statements. If a variable is
+initialized with the return value of a method that throws a checked exception,
+it must be initialized inside a try block. If the value must be used outside
+of the try block, then it must be declared before the try block, where it
+cannot yet be sensibly initialized:</p>
+<pre>// Instantiate class cl, which represents some sort of Set 
+Set s = null;
+try {
+    s = (Set) cl.newInstance();
+} catch(IllegalAccessException e) {
+    throw new IllegalArgumentException(cl + " not accessible");
+} catch(InstantiationException e) {
+    throw new IllegalArgumentException(cl + " not instantiable");
+}
+
+// Exercise the set 
+s.addAll(Arrays.asList(args));</pre>
+<p>But even this case can be avoided by encapsulating the try-catch block in a method:</p>
+<pre>Set createSet(Class cl) {
+    // Instantiate class cl, which represents some sort of Set 
+    try {
+        return (Set) cl.newInstance();
+    } catch(IllegalAccessException e) {
+        throw new IllegalArgumentException(cl + " not accessible");
+    } catch(InstantiationException e) {
+        throw new IllegalArgumentException(cl + " not instantiable");
+    }
+}
+
+...
+
+// Exercise the set 
+Set s = createSet(cl);
+s.addAll(Arrays.asList(args));</pre>
+<p>Loop variables should be declared in the for statement itself unless there
+is a compelling reason to do otherwise:</p>
+<pre>for (int i = 0; i n; i++) {
+    doSomething(i);
+}
+
+for (Iterator i = c.iterator(); i.hasNext(); ) {
+    doSomethingElse(i.next());
+}</pre>
+<h2><a name="import_style"></a>Imports</h2>
+<p>The ordering of import statements is:</p>
+<ol>
+<li>Android imports</li>
+<li>Imports from third parties (com, junit, net, org)</li>
+<li>java and javax</li>
+</ol>
+<p>To exactly match the IDE settings, the imports should be:</p>
+<ul>
+<li>Alphabetical within each grouping.</li>
+<li>Capital letters are considered to come before lower case letter (e.g. Z before a).</li>
+<li>There should be a blank line between each major grouping (android, com, junit, net, org, java, javax).</li>
 </ul>
-<p>For example:
-</p>
-<pre>public class MyClass {<br>public static final int SOME_CONSTANT = 42;<br>public int publicField;<br>private static MyClass sSingleton;<br>int mPackagePrivate;<br>private int mPrivate;<br>protected int mProtected;<br>}</pre>
-<h2><a>Braces</a>
-</h2>
-<p>Braces do not go on their own line; they go on the same line as the code before them. So:
-</p>
-<pre>class MyClass {<br>int func() {<br>if (something) {<br>// ...<br>} else if (somethingElse) {<br>// ...<br>} else {<br>// ...<br>}<br>}<br>}<br></pre>
-<p>We require braces around the statements for a conditional. Except, if the entire conditional (the condition and the body) fit on one line, you may (but are not obligated to) put it all on one line. That is, this is legal:
-</p>
-<pre>if (condition) {<br>body; // ok <br>}<br>if (condition) body; // ok</pre>
-<p>but this is still illegal:
-</p>
-<pre>if (condition)<br>body; // bad <br></pre>
-<h2><a>Line length</a>
-</h2>
-<p>Each line of text in your code should be at most 100 characters long.
-</p>
-<p>There has been lots of discussion about this rule and the decision remains that 100 characters is the maximum.
-</p>
-<p>Exception: if a comment line contains an example command or a literal URL longer than 100 characters, that line may be longer than 100 characters for ease of cut and paste.
-</p>
-<p>Exception: import lines can go over the limit because humans rarely see them. This also simplifies tool writing.
-</p>
-<h2>
-Java 1.5 Annotations
-</h2>
-<p>Annotations should precede other modifiers for the same language element. Simple marker annotations (e.g. @Override) can be listed on the same line with the language element. If there are multiple annotations, or parameterized annotations, they should each be listed one-per-line in alphabetical order.
-</p>
-<p>Android -standard practices for the three predefined annotations in Java 1.5's are:
-</p>
-@DeprecatedThe @Deprecated annotation must be used whenever the use of the annotated element is discouraged. If you use the @Deprecated annotation, you must also have a @deprecated Javadoc tag and it should name an alternate implementation. In addition, remember that a @Deprecated method is <b>still</b>
-supposed to work.
-<p>If you see old code that has a @deprecated Javadoc tag, please add the @Deprecated annotation.
-</p>
-@OverrideThe @Override annotation must be used whenever a method overrides the declaration or implementation from a super-class.
-<p>For example, if you use the {@inheritdocs} Javadoc tag, and derive from a class (not an interface), you must also annotate that the method @Overrides the parent class's method.
-</p>
-@SuppressWarningsThe @SuppressWarnings annotation should only be used under circumstances where it is impossible to eliminate a warning. If a warning passes this "impossible to eliminate" test, the@SuppressWarnings annotation <b>must</b>
-be used, so as to ensure that all warnings reflect actual problems in the code.
-<p>When a @SuppressWarnings annotation is necessary, it must be prefixed with a TODO comment that explains the "impossible to eliminate" condition. This will normally identify an offending class that has an awkward interface. For example:
-</p>
-<pre>// TODO: The third-party class com.third.useful.Utility.rotate() needs generics <br>@SuppressWarnings({"generic-cast"})<br>ListStringblix = Utility.rotate(blax);<br></pre>
-When a @SuppressWarnings annotation is required, the code should be refactored to isolate the software elements where the annotation applies.
-<h2><a>Acronyms in names</a>
-</h2>
-<p>Treat acronyms and abbreviations as words. The names are much more readable:
-</p>
+<h4>Why?</h4>
+<p>Originally there was no style requirement on the ordering. This meant that
+the IDE's were either always changing the ordering, or IDE developers had to
+disable the automatic import management features and maintain the imports by
+hand. This was deemed bad. When java-style was asked, the preferred styles
+were all over the map. It pretty much came down to our needing to "pick an
+ordering and be consistent." So we chose a style, updated the style guide, and
+made the IDEs obey it. We expect that as IDE users work on the code, the
+imports in all of the packages will end up matching this pattern without any
+extra engineering effort.</p>
+<p>The style chosen such that:</p>
+<ul>
+<li>The imports people want to look at first tend to be at the top (android)</li>
+<li>The imports people want to look at least tend to be at the bottom (java)</li>
+<li>Humans can easily follow the style</li>
+<li>The IDE's can follow the style</li>
+</ul>
+<h3>What about static imports?</h3>
+<p>The use and location of static imports have been mildly controversial
+issues. Some people would prefer static imports to be interspersed with the
+remaining imports, some would prefer them reside above or below all other
+imports. Additinally, we have not yet come up with a way to make all IDEs use
+the same ordering.</p>
+<p>Since most people consider this a low priority issue, just use your
+judgement and please be consistent.</p>
 
-<table><tbody><tr><td>Good
-</td>
-<td>Bad
-</td>
-</tr>
-<tr><td>XmlHttpRequest</td>
-<td>XMLHTTPRequest
-</td>
-</tr>
-<tr><td>getCustomerId</td>
-<td>getCustomerID
-</td>
-</tr>
-</tbody>
-</table>
-
-<p>This style rule also applies when an acronym or abbreviation is the entire name:
-</p>
-
-<table><tbody><tr><td>Good
-</td>
-<td>Bad
-</td>
-</tr>
-<tr><td>class Html</td>
-<td>class HTML
-</td>
-</tr>
-<tr><td>String url;</td>
-<td>String URL;
-</td>
-</tr>
-<tr><td>long id;</td>
-<td>long ID;
-</td>
-</tr>
-</tbody>
-</table>
-
-<p>Both the JDK and the Android code bases are very inconsistent with regards to acronyms, therefore, it is virtually impossible to be consistent with the code around you. Bite the bullet, and treat acronyms as words.
-</p>
+<h2><a name="indentation"></a>Indentation</h2>
+<p>We use 4 space indents for blocks. We never use tabs. When in doubt, be
+consistent with code around you.</p>
+<p>We use 8 space indents for line wraps, including function calls and
+assignments. For example, this is correct:</p>
+<pre>Instrument i =
+        someLongExpression(that, wouldNotFit, on, one, line);</pre>
+<p>and this is not correct:</p>
+<pre>Instrument i =
+    someLongExpression(that, wouldNotFit, on, one, line);</pre>
+<h2><a name="field_names"></a>Field Names</h2>
+<ul>
+<li>Non-public, non-static field names start with m.</li>
+<li>Static field names start with s.</li>
+<li>Other fields start with a lower case letter.</li>
+<li>Public static final fields (constants) are ALL_CAPS_WITH_UNDERSCORES.</li>
+</ul>
+<p>For example:</p>
+<pre>public class MyClass {
+    public static final int SOME_CONSTANT = 42;
+    public int publicField;
+    private static MyClass sSingleton;
+    int mPackagePrivate;
+    private int mPrivate;
+    protected int mProtected;
+}</pre>
+<h2><a name="braces"></a>Braces</h2>
+<p>Braces do not go on their own line; they go on the same line as the code
+before them. So:</p>
+<pre>class MyClass {
+    int func() {
+        if (something) {
+            // ...
+        } else if (somethingElse) {
+            // ...
+        } else {
+            // ...
+        }
+    }
+}</pre>
+<p>We require braces around the statements for a conditional. Except, if the
+entire conditional (the condition and the body) fit on one line, you may (but
+are not obligated to) put it all on one line. That is, this is legal:</p>
+<pre>if (condition) {
+    body(); // ok 
+}
+if (condition) body(); // ok</pre>
+<p>but this is still illegal:</p>
+<pre>if (condition)
+    body(); // bad</pre>
+<h2><a name="linelen"></a>Line length</h2>
+<p>Each line of text in your code should be at most 100 characters long.</p>
+<p>There has been lots of discussion about this rule and the decision remains
+that 100 characters is the maximum.</p>
+<p>Exception: if a comment line contains an example command or a literal URL
+longer than 100 characters, that line may be longer than 100 characters for
+ease of cut and paste.</p>
+<p>Exception: import lines can go over the limit because humans rarely see
+them. This also simplifies tool writing.</p>
+<h2><a name="annotations"></a>Java 1.5 Annotations</h2>
+<p>Annotations should precede other modifiers for the same language element.
+Simple marker annotations (e.g. &#64;Override) can be listed on the same line with
+the language element. If there are multiple annotations, or parameterized
+annotations, they should each be listed one-per-line in alphabetical
+order.</p>
+<p>Android -standard practices for the three predefined annotations in Java
+1.5's are:</p>
+<h3>&#64;Deprecated</h3>
+<p>The &#64;Deprecated annotation must be used whenever the use of the annotated
+element is discouraged. If you use the &#64;Deprecated annotation, you must also
+have a &#64;deprecated Javadoc tag and it should name an alternate implementation.
+In addition, remember that a &#64;Deprecated method is <b>still</b> supposed to
+work.</p>
+<p>If you see old code that has a &#64;deprecated Javadoc tag, please add the &#64;Deprecated annotation.</p>
+<h3>&#64;Override</h3>
+<p>The &#64;Override annotation must be used whenever a method overrides the
+declaration or implementation from a super-class.</p>
+<p>For example, if you use the &#64;inheritdocs Javadoc tag, and derive from a
+class (not an interface), you must also annotate that the method &#64;Overrides
+the parent class's method.</p>
+<h3>&#64;SuppressWarnings</h3>
+<p>The &#64;SuppressWarnings annotation should only be used under circumstances
+where it is impossible to eliminate a warning. If a warning passes this
+"impossible to eliminate" test, the &#64;SuppressWarnings annotation <b>must</b> be
+used, so as to ensure that all warnings reflect actual problems in the
+code.</p>
+<p>When a &#64;SuppressWarnings annotation is necessary, it must be prefixed with
+a TODO comment that explains the "impossible to eliminate" condition. This
+will normally identify an offending class that has an awkward interface. For
+example:</p>
+<pre>// TODO: The third-party class com.third.useful.Utility.rotate() needs generics 
+&#64;SuppressWarnings("generic-cast")
+List&lt;String&gt; blix = Utility.rotate(blax);</pre>
+<p>When a &#64;SuppressWarnings annotation is required, the code should be
+refactored to isolate the software elements where the annotation applies.</p>
+<h2><a name="acronyms"></a>Acronyms in names</h2>
+<p>Treat acronyms and abbreviations as words. The names are much more readable:</p>
+<table><tbody>
+<tr><td>Good</td> <td>Bad</td></tr>
+<tr><td>XmlHttpRequest</td> <td>XMLHTTPRequest</td></tr>
+<tr><td>getCustomerId</td> <td>getCustomerID</td></tr>
+</tbody></table>
+<p>This style rule also applies when an acronym or abbreviation is the entire
+name:</p>
+<table><tbody>
+<tr><td>Good</td> <td>Bad</td></tr>
+<tr><td>class Html</td> <td>class HTML</td></tr>
+<tr><td>String url;</td> <td>String URL;</td></tr>
+<tr><td>long id;</td> <td>long ID;</td></tr>
+</tbody></table>
+<p>Both the JDK and the Android code bases are very inconsistent with regards
+to acronyms, therefore, it is virtually impossible to be consistent with the
+code around you. Bite the bullet, and treat acronyms as words.</p>
 <p>For further justifications of this style rule, see <i>Effective Java</i>
-Item 38 and <i>Java Puzzlers</i>
-Number 68.
-</p>
-<h2><a>TODO style</a>
-</h2>
-<p>Use TODO comments for code that is temporary, a short-term solution, or good-enough but not perfect.
-</p>
-<p>TODOs should include the string TODO in all caps, followed by a colon:
-</p>
-<pre>// TODO: Remove this code after the UrlTable2 has been checked in.<br><br>// TODO: Change this to use a flag instead of a constant.</pre>
-<p>If your TODO is of the form "At a future date do something" make sure that you either include a very specific date ("Fix by November 2005") or a very specific event ("Remove this code after all production mixers understand protocol V7.").
-</p>
-<h2>
-Consistency
-</h2>
-<p>Our parting thought: BE CONSISTENT. If you're editing code, take a few minutes to look at the code around you and determine its style. If they use spaces around their if clauses, you should too. If their comments have little boxes of stars around them, make your comments have little boxes of stars around them too.
-</p>
-<p>The point of having style guidelines is to have a common vocabulary of coding, so people can concentrate on what you're saying, rather than on how you're saying it. We present global style rules here so people know the vocabulary. But local style is also important. If code you add to a a file looks drastically different from the existing code around it, it throws readers out of their rhythm when they go to read it. Try to avoid this.
-</p>
-<h2><a>Logging</a>
-</h2>
-<p>While logging is necessary it has a significantly negative impact on performance and quickly loses its usefulness if it's not kept reasonably terse. The logging facilities provides five different levels of logging. Below are the different levels and when and how they should be used.
-</p>
+Item 38 and <i>Java Puzzlers</i> Number 68.</p>
 
-<ul><li><b>ERROR:</b>
-This level of logging should be used when something fatal has happened, i.e. something that will have user-visible consequences and won't be recoverable without explicitly deleting some data, uninstalling applications, wiping the data partitions or reflashing the entire phone (or worse). This level is always logged. Issues that justify some logging at the ERROR level are typically good candidates to be reported to a statistics-gathering server.
-</li>
+<h2><a name="todo"></a>TODO style</h2>
+<p>Use TODO comments for code that is temporary, a short-term solution, or
+good-enough but not perfect.</p>
+<p>TODOs should include the string TODO in all caps, followed by a colon:</p>
+<pre>// TODO: Remove this code after the UrlTable2 has been checked in.
+
+// TODO: Change this to use a flag instead of a constant.</pre>
+<p>If your TODO is of the form "At a future date do something" make sure that
+you either include a very specific date ("Fix by November 2005") or a very
+specific event ("Remove this code after all production mixers understand
+protocol V7.").</p>
+
+<h2><a name="consistency"></a>Consistency</h2>
+<p>Our parting thought: BE CONSISTENT. If you're editing code, take a few
+minutes to look at the code around you and determine its style. If they use
+spaces around their if clauses, you should too. If their comments have little
+boxes of stars around them, make your comments have little boxes of stars
+around them too.</p>
+<p>The point of having style guidelines is to have a common vocabulary of
+coding, so people can concentrate on what you're saying, rather than on how
+you're saying it. We present global style rules here so people know the
+vocabulary. But local style is also important. If code you add to a a file
+looks drastically different from the existing code around it, it throws
+readers out of their rhythm when they go to read it. Try to avoid this.</p>
+
+<h2><a name="logging"></a>Logging</h2>
+<p>While logging is necessary it has a significantly negative impact on
+performance and quickly loses its usefulness if it's not kept reasonably
+terse. The logging facilities provides five different levels of logging. Below
+are the different levels and when and how they should be used.</p>
+
+<ul>
+<li><b>ERROR:</b>
+This level of logging should be used when something fatal has happened,
+i.e. something that will have user-visible consequences and won't be
+recoverable without explicitly deleting some data, uninstalling applications,
+wiping the data partitions or reflashing the entire phone (or worse). This
+level is always logged. Issues that justify some logging at the ERROR level
+are typically good candidates to be reported to a statistics-gathering
+server.</li>
 <li><b>WARNING:</b>
-This level of logging should used when something serious and unexpected happened, i.e. something that will have user-visible consequences but is likely to be recoverable without data loss by performing some explicit action, ranging from waiting or restarting an app all the way to re-downloading a new version of an application or rebooting the device. This level is always logged. Issues that justify some logging at the WARNING level might also be considered for reporting to a statistics-gathering server.
-</li>
+This level of logging should used when something serious and unexpected
+happened, i.e. something that will have user-visible consequences but is
+likely to be recoverable without data loss by performing some explicit action,
+ranging from waiting or restarting an app all the way to re-downloading a new
+version of an application or rebooting the device. This level is always
+logged. Issues that justify some logging at the WARNING level might also be
+considered for reporting to a statistics-gathering server.</li>
 <li><b>INFORMATIVE:</b>
-This level of logging should used be to note that something interesting to most people happened, i.e. when a situation is detected that is likely to have widespread impact, though isn't necessarily an error. Such a condition should only be logged by a module that reasonably believes that it is the most authoritative in that domain (to avoid duplicate logging by non-authoritative components). This level is always logged.
-</li>
+This level of logging should used be to note that something interesting to
+most people happened, i.e. when a situation is detected that is likely to have
+widespread impact, though isn't necessarily an error. Such a condition should
+only be logged by a module that reasonably believes that it is the most
+authoritative in that domain (to avoid duplicate logging by non-authoritative
+components). This level is always logged.</li>
 <li><b>DEBUG:</b>
-This level of logging should be used to further note what is happening on the device that could be relevant to investigate and debug unexpected behaviors. You should log only what is needed to gather enough information about what is going on about your component. If your debug logs are dominating the log then you probably should be using verbose logging. This level will be logged, even on release builds, and is required to be surrounded by an if (LOCAL_LOG) or if (LOCAL_LOGD) block, where LOCAL_LOG[D] is defined in your class or subcomponent, so that there can exist a possibility to disable all such logging. There must therefore be no active logic in an if (LOCAL_LOG) block. All the string building for the log also needs to be placed inside the if (LOCAL_LOG) block. The logging call should not be re-factored out into a method call if it is going to cause the string building to take place outside of the if (LOCAL_LOG) block. There is some code that still says if (localLOGV). This is considered acceptable as well, although the name is nonstandard.
-</li>
+This level of logging should be used to further note what is happening on the
+device that could be relevant to investigate and debug unexpected behaviors.
+You should log only what is needed to gather enough information about what is
+going on about your component. If your debug logs are dominating the log then
+you probably should be using verbose logging. This level will be logged, even
+on release builds, and is required to be surrounded by an if (LOCAL_LOG) or if
+(LOCAL_LOGD) block, where LOCAL_LOG[D] is defined in your class or
+subcomponent, so that there can exist a possibility to disable all such
+logging. There must therefore be no active logic in an if (LOCAL_LOG) block.
+All the string building for the log also needs to be placed inside the if
+(LOCAL_LOG) block. The logging call should not be re-factored out into a
+method call if it is going to cause the string building to take place outside
+of the if (LOCAL_LOG) block. There is some code that still says if
+(localLOGV). This is considered acceptable as well, although the name is
+nonstandard.</li>
 <li><b>VERBOSE:</b>
-This level of logging should be used for everything else. This level will only be logged on debug builds and should be surrounded by if (LOCAL_LOGV) block (or equivalent) so that it can be compiled out by default. Any string building will be stripped out of release builds and needs to appear inside the if (LOCAL_LOGV) block.
-</li>
+This level of logging should be used for everything else. This level will only
+be logged on debug builds and should be surrounded by if (LOCAL_LOGV) block
+(or equivalent) so that it can be compiled out by default. Any string building
+will be stripped out of release builds and needs to appear inside the if
+(LOCAL_LOGV) block.</li>
 </ul>
-<p><i>Note:</i>
-Within a given module, other than at the VERBOSE level, an error should only be reported once if possible: within a single chain of function calls within a module, only the innermost function should return the error, and callers in the same module should only add some logging if that significantly helps to isolate the issue.
-</p>
-<p><i>Note:</i>
-In a chain of modules, other than at the VERBOSE level, when a lower-level module detects invalid data coming from a higher-level module, the lower-level module should only log this situation to the DEBUG log, and only if logging provides information that is not otherwise available to the caller. Specifically, there is no need to log situations where an exception is thrown (the exception should contain all the relevant information), or where the only information being logged is contained in an error code. This is especially important in the interaction between the framework and applications, and conditions caused by third-party applications that are properly handled by the framework should not trigger logging higher than the DEBUG level. The only situations that should trigger logging at the INFORMATIVE level or higher is when a module or application detects an error at its own level or coming from a lower level.
-</p>
-<p><i>Note:</i>
-When a condition that would normally justify some logging is likely to occur many times, it can be a good idea to implement some rate-limiting mechanism to prevent overflowing the logs with many duplicate copies of the same (or very similar) information.
-</p>
-<p><i>Note:</i>
-Losses of network connectivity are considered common and fully expected and should not be logged gratuitously. A loss of network connectivity that has consequences within an app should be logged at the DEBUG or VERBOSE level (depending on whether the consequences are serious enough and unexpected enough to be logged in a release build).
-</p>
-<p><i>Note:</i>
-A full filesystem on a filesystem that is acceessible to or on behalf of third-party applications should not be logged at a level higher than INFORMATIVE.
-</p>
-<p><i>Note:</i>
-Invalid data coming from any untrusted source (including any file on shared storage, or data coming through just about any network connections) is considered expected and should not trigger any logging at a level higher then DEBUG when it's detected to be invalid (and even then logging should be as limited as possible).
-</p>
-<p><i>Note:</i>
-Keep in mind that the '+' operator, when used on Strings, implicitly creates a StringBuilder with the default buffer size (16 characters) and potentially quite a few other temporary String objects, i.e. that explicitly creating StringBuilders isn't more expensive than relying on the default '+' operator (and can be a lot more efficient in fact). Also keep in mind that code that calls Log.v() is compiled and executed on release builds, including building the strings, even if the logs aren't being read.
-</p>
-<p><i>Note:</i>
-Any logging that is meant to be read by other people and to be available in release builds should be terse without being cryptic, and should be reasonably understandable. This includes all logging up to the DEBUG level.
-</p>
-<p><i>Note:</i>
-When possible, logging should be kept on a single line if it makes sense. Line lengths up to 80 or 100 characters are perfectly acceptable, while lengths longer than about 130 or 160 characters (including the length of the tag) should be avoided if possible.
-</p>
-<p><i>Note:</i>
-Logging that reports successes should never be used at levels higher than VERBOSE.
-</p>
-<p><i>Note:</i>
-Temporary logging that is used to diagnose an issue that's hard to reproduce should be kept at the DEBUG or VERBOSE level, and should be enclosed by if blocks that allow to disable it entirely at compile-time.
-</p>
-<p><i>Note:</i>
-Be careful about security leaks through the log. Private information should be avoided. Information about protected content must definitely be avoided. This is especially important when writing framework code as it's not easy to know in advance what will and will not be private information or protected content.
-</p>
-<p><i>Note:</i>
-System.out.println() (or printf() for native code) should never be used. System.out and System.err get redirected to /dev/null, so your print statements will have no visible effects. However, all the string building that happens for these calls still gets executed.
-</p>
-<p><i>Note:</i>
-<b>The golden rule of logging is that your logs may not unnecessarily push other logs out of the buffer, just as others may not push out yours.</b>
-</p>
-<h2>
-Javatests Style Rules
-</h2>
+<p><i>Note:</i> Within a given module, other than at the VERBOSE level, an
+error should only be reported once if possible: within a single chain of
+function calls within a module, only the innermost function should return the
+error, and callers in the same module should only add some logging if that
+significantly helps to isolate the issue.</p>
+<p><i>Note:</i> In a chain of modules, other than at the VERBOSE level, when a
+lower-level module detects invalid data coming from a higher-level module, the
+lower-level module should only log this situation to the DEBUG log, and only
+if logging provides information that is not otherwise available to the caller.
+Specifically, there is no need to log situations where an exception is thrown
+(the exception should contain all the relevant information), or where the only
+information being logged is contained in an error code. This is especially
+important in the interaction between the framework and applications, and
+conditions caused by third-party applications that are properly handled by the
+framework should not trigger logging higher than the DEBUG level. The only
+situations that should trigger logging at the INFORMATIVE level or higher is
+when a module or application detects an error at its own level or coming from
+a lower level.</p>
+<p><i>Note:</i> When a condition that would normally justify some logging is
+likely to occur many times, it can be a good idea to implement some
+rate-limiting mechanism to prevent overflowing the logs with many duplicate
+copies of the same (or very similar) information.</p>
+<p><i>Note:</i> Losses of network connectivity are considered common and fully
+expected and should not be logged gratuitously. A loss of network connectivity
+that has consequences within an app should be logged at the DEBUG or VERBOSE
+level (depending on whether the consequences are serious enough and unexpected
+enough to be logged in a release build).</p>
+<p><i>Note:</i> A full filesystem on a filesystem that is acceessible to or on
+behalf of third-party applications should not be logged at a level higher than
+INFORMATIVE.</p>
+<p><i>Note:</i> Invalid data coming from any untrusted source (including any
+file on shared storage, or data coming through just about any network
+connections) is considered expected and should not trigger any logging at a
+level higher then DEBUG when it's detected to be invalid (and even then
+logging should be as limited as possible).</p>
+<p><i>Note:</i> Keep in mind that the '+' operator, when used on Strings,
+implicitly creates a StringBuilder with the default buffer size (16
+characters) and potentially quite a few other temporary String objects, i.e.
+that explicitly creating StringBuilders isn't more expensive than relying on
+the default '+' operator (and can be a lot more efficient in fact). Also keep
+in mind that code that calls Log.v() is compiled and executed on release
+builds, including building the strings, even if the logs aren't being
+read.</p>
+<p><i>Note:</i> Any logging that is meant to be read by other people and to be
+available in release builds should be terse without being cryptic, and should
+be reasonably understandable. This includes all logging up to the DEBUG
+level.</p>
+<p><i>Note:</i> When possible, logging should be kept on a single line if it
+makes sense. Line lengths up to 80 or 100 characters are perfectly acceptable,
+while lengths longer than about 130 or 160 characters (including the length of
+the tag) should be avoided if possible.</p>
+<p><i>Note:</i> Logging that reports successes should never be used at levels
+higher than VERBOSE.</p>
+<p><i>Note:</i> Temporary logging that is used to diagnose an issue that's
+hard to reproduce should be kept at the DEBUG or VERBOSE level, and should be
+enclosed by if blocks that allow to disable it entirely at compile-time.</p>
+<p><i>Note:</i> Be careful about security leaks through the log. Private
+information should be avoided. Information about protected content must
+definitely be avoided. This is especially important when writing framework
+code as it's not easy to know in advance what will and will not be private
+information or protected content.</p>
+<p><i>Note:</i> System.out.println() (or printf() for native code) should
+never be used. System.out and System.err get redirected to /dev/null, so your
+print statements will have no visible effects. However, all the string
+building that happens for these calls still gets executed.</p>
+<p><i>Note:</i> <b>The golden rule of logging is that your logs may not
+unnecessarily push other logs out of the buffer, just as others may not push
+out yours.</b></p>
 
-<h2><a>Naming test methods</a>
-</h2>
-<a>When naming test methods, you can use an underscore to seperate what is being tested from the specific case being tested. This style makes it easier to see exactly what cases are being tested.</a>
-<p><a>Example:</a>
-</p>
-<pre><a>testMethod_specificCase1 <br>testMethod_specificCase2</a>
-</pre>
+<h2>Javatests Style Rules</h2>
+<h2><a name="testmethodnames"></a>Naming test methods</h2>
+<p>When naming test methods, you can use an underscore to seperate what is
+being tested from the specific case being tested. This style makes it easier
+to see exactly what cases are being tested.</p>
+<p><a>For example:</a></p>
+<pre>testMethod_specificCase1 testMethod_specificCase2</pre>
 
-<pre><a>void testIsDistinguishable_protanopia() {<br>ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA)<br>assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK))<br>assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y))<br>}</a>
+<pre>void testIsDistinguishable_protanopia() {
+    ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA)
+    assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK))
+    assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y))
+}
 </pre>
-</div>
-</div>
-</div>
diff --git a/pdk/docs/source/download.jd b/pdk/docs/source/download.jd
index 329deff..98e0478 100644
--- a/pdk/docs/source/download.jd
+++ b/pdk/docs/source/download.jd
@@ -94,7 +94,7 @@
 sun-java5-jdk <span><span>zlib1g-dev</span>
 </span>
 gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev ia32-libs x11proto-core-dev libx11-dev lib32readline5-dev lib32z-dev</blockquote>
-<ul><li>Set the system to use the right version of java by default:<br><br>$sudo update-java-alternatives -sjava-1.5.0-sun <br></li>
+<ul><li>Set the system to use the right version of java by default:<br><br>$ sudo update-java-alternatives -s java-1.5.0-sun <br></li>
 </ul>
 <ul><li>X11: Ubuntu doesn't have packages for the X11 libraries, but that can be worked around with the following command:<br><br>$ sudo ln -s /usr/lib32/libX11.so.6 /usr/lib32/libX11.so <br><br></li>
 </ul>
@@ -135,38 +135,32 @@
 <li>volume format: case sensitive, journaled</li>
 </ul>
 </li>
-<li>This will create a .dmg file which, once mounted, acts as a drive with the required formatting for Android development. For a disk image named "android.dmg" stored in your home directory, you can add the following to your ~/.bash_profile to mount the image when you execute "mountAndroid":<br><br><div># command to mount the android file image <br>function mountAndroid{ hdiutil attach ~/android.dmg-mountpoint /Volumes/android; }<br></div>
+<li>This will create a .dmg file which, once mounted, acts as a drive with the required formatting for Android development. For a disk image named "android.dmg" stored in your home directory, you can add the following to your ~/.bash_profile to mount the image when you execute "mountAndroid":<br><br><div># command to mount the android file image <br>function mountAndroid { hdiutil attach ~/android.dmg -mountpoint /Volumes/android; }<br></div>
 <br>Once mounted, you'll do all your work in the "android" volume. You can eject it (unmount it) just like you would with an external drive.</li>
 </ul>
 </ul>
 </ul>
 <ul></ul>
-To set up your Mac OS development environment, follow these steps:<br><ol><li>Install the XCode version 2.4 or later from <a href="http://developer.apple.com/">http://developer.apple.com</a>. We recommend version 3.0 or newer.<br></li>
+To set up your Mac OS development environment, follow these steps:<br>
+<ol><li>Install the XCode version 2.4 or later from <a href="http://developer.apple.com/">http://developer.apple.com</a>. We recommend version 3.0 or newer.<br></li>
 <li>Install MacPorts. To do this:</li>
-<ol><li>Download the tar file from <a href="http://www.macports.org/">http://www.macports.org/</a> and untar the files.</li>
-<li>Run the following:<span><br>$ ./configure <br></span>
-<span>$</span>
-<span>make <br></span>
-<span>$</span>
-<span>sudo make install</span>
-</li>
-<li>Make sure that /opt/local/bin is in your path before /usr/bin. by running <br>$ echo $PATH <br>If you don't see /opt/local/bin, edit $HOME/.bash_profile and add the line <br>export PATH=/opt/local/bin:$PATH <br>(or the equivalent for other shells) after any other PATH-related lines.<span>To verify that your path is now correct, o</span>
-pen a new terminal and <span>run</span>
-echo $PATH <span>again</span>
-.</li>
-<li>Ask MacPorts to update itself:<br><span></span>
-<span>$</span>
-<span>sudo port selfupdate</span>
-<br></li>
+<ol>
+    <li>Download and run the installer from <a href="http://www.macports.org/install.php">http://www.macports.org/install.php</a></li>
+    <li>Make sure that <code>/opt/local/bin</code> is in your path before <code>/usr/bin</code> by running
+        <div><code>$ echo $PATH</code></div>
+        <div> If you don't see /opt/local/bin, edit <code>$HOME/.bash_profile</code> and add the line <code>export PATH=/opt/local/bin:$PATH</code>
+        after any other PATH-related lines. To verify that your path is now correct, open a new terminal and run <code>echo $PATH</code> again.</div>
+    </li>
 </ol>
-<li>Get the following packages from port:<br>$<span>POSIXLY_CORRECT=1</span>
-sudo port install gmake libsdl git-core gnupg <br><span>If using Mac OS 10.4, also install:<br>$<span>POSIXLY_CORRECT=1</span>
+<li>Get the following packages from port:<br>$ <span>POSIXLY_CORRECT=1</span>
+sudo port install gmake libsdl git-core gnupg <br><span>If using Mac OS 10.4, also install:<br>$ <span>POSIXLY_CORRECT=1</span>
 sudo port install bison <br></span>
 </li>
-<li>Upgrade GNU make to 3.81 or later by running.Mac OS doesn't come with a recent enough version.<br>$ sudo ln -s gmake /opt/local/bin/make <br></li>
+<li>Upgrade GNU make to 3.81 or later by running the following command. (Mac OS doesn't come with a recent enough version.)<br>$ sudo ln -s gmake /opt/local/bin/make <br></li>
 <li>Set an appropriate per-process file descriptor limit. To do this, add the following lines to your <i><span>.bash_profile</span>
 </i>
-file:<br># set the number of open files to be 1024<br>ulimit -S -n 1024</li>
+file:<br># set the number of open files to be 1024<br>$ <span>ulimit -S -n 1024</span><br>
+Note that this may not be necessary; on some systems, the output of "ulimit -S" will show "unlimited". In this case, there is no need to set the limit to 1024.</li>
 </ol>
 </div>
 </div>
@@ -180,11 +174,11 @@
 .<br></p>
 To install, initialize, and configure Repo, follow these steps:<br><br></div>
 <ol><li><span>Make sure you have a~/bindirectory in your home directory, and check to be sure that this bin directory is in your path:</span>
-<br>$ cd~<br><span><span>$ mkdir bin <br>$ echo $PATH <br></span>
+<br>$ cd ~<br><span><span>$ mkdir bin <br>$ echo $PATH <br></span>
 </span>
 </li>
 <li><span>Download thereposcript and make sure it is executable:</span>
-<br>$ curl http://android.git.kernel.org/repo~/bin/repo <div>$ chmod a+x ~/bin/repo</div>
+<br>$ curl http://android.git.kernel.org/repo &gt;~/bin/repo <div>$ chmod a+x ~/bin/repo</div>
 </li>
 </ol>
 </div>
@@ -197,7 +191,7 @@
 <span>$ mkdir mydroid</span>
 <br><span>$ cd mydroid</span>
 <br></li>
-<li><span>Runrepo initto bring down the latest version of Repo with all its most recent bug fixes. You must specify a URL for the manifest:</span>
+<li><span>Run "repo init" to bring down the latest version of Repo with all its most recent bug fixes. You must specify a URL for the manifest:</span>
 <br><span>$ repo init</span>
 -u git://android.git.kernel.org/platform/manifest.git</li>
 <ul><li><span>If you would like to check out a branch other than "master", specify it with -b, like:</span>
@@ -238,7 +232,7 @@
 Getting the files</h2>
 <div><span><span>To pull down files to your working directory from the repositories as specified in the default manifest, run</span>
 <br><br>$ repo sync <i><br></i>
-<br><span>For more aboutrepo syncand other Repo commands, see</span>
+<br><span>For more about "repo sync" and other Repo commands, see</span>
 <a href="{@docRoot}source/git-repo.html">Using Repo and Git</a>
 <span>.</span>
 <br><br><span>The Android source files will be located in your working
diff --git a/pdk/docs/source/index.jd b/pdk/docs/source/index.jd
index 230a0b3..a9acbf4 100644
--- a/pdk/docs/source/index.jd
+++ b/pdk/docs/source/index.jd
@@ -3,7 +3,7 @@
 @jd:body
 <div>
 <p>Thanks for your interest in Android! Here are some ways you can get involved
-and help Google improve Android. For background on the Android project and our
+and help us improve Android. For background on the Android project and our
 goals, check out the <a href="{@docRoot}about/philosophy.html">Project
 Philosophy page</a>.</p>
 
@@ -11,7 +11,7 @@
 <p>One of the easiest and most effective ways you can help improve Android is
 to file bugs. For more information, visit the <a
 href="{@docRoot}source/report-bugs.html">Reporting Bugs</a> page.</p>
-<p>Please note that we can't guarantee that any particular bug can be fixed in
+<p>Please note that we can't guarantee that any particular bug will be fixed in
 any particular release. To see what happens to your bug once you report it,
 read <a href="{@docRoot}source/life-of-a-bug.html">Life of a Bug</a>.</p>
 
@@ -26,8 +26,10 @@
 
 <h2>Contribute to the Code</h2>
 <p>Code is King. We'd love to review any changes you submit, so please check
-out the source, pick a bug or feature, and get coding.</p>
-<p>You can get started with  by learning about the <a
+out the source, pick a bug or feature, and get coding. Note that the smaller
+and more targetted your patch submissions, the easier it will be for us to
+review them.</p>
+<p>You can get started with Android by learning about the <a
 href="{@docRoot}source/life-of-a-patch.html">Life of a Patch</a>, and by
 learning about <code>git</code>, <code>repo</code>, and other tools using the
 links to the left. If you need help along the way, you can join our <a
diff --git a/pdk/docs/source/licenses.jd b/pdk/docs/source/licenses.jd
index 846a92a..17cebeb 100644
--- a/pdk/docs/source/licenses.jd
+++ b/pdk/docs/source/licenses.jd
@@ -4,18 +4,15 @@
 <div>
 <p>The Android Open Source Project uses a few <a
 href="http://www.opensource.org/">open source initiative</a> approved open
-source licenses to enable availability of source code and to accept
-contributions from individuals and corporations.</p>
+source licenses for our software.</p>
 <h2>Android Open Source Project license</h2>
-<p>The preferred license for the Android Open Source Project is <a
-href="http://www.apache.org/licenses/LICENSE-2.0">Apache 2.0</a>. Apache 2.0
-is a commercial and open source friendly open source license. The majority of
-the Android platform is licensed under the <a
-href="http://www.apache.org/licenses/">Apache 2.0 license</a>. While the
-project will strive to adhere to the preferred license, there may be
-exceptions which will be handled on a case-by-case basis. For example, the
-Linux kernel patches are under the GPLv2 license with system exceptions, which
-can be found on <a
+<p>The preferred license for the Android Open Source Project is the <a
+href="http://www.apache.org/licenses/LICENSE-2.0">Apache Software License,
+2.0</a> ("Apache 2.0"), and the majority of the Android software is licensed
+with Apache 2.0. While the project will strive to adhere to the preferred
+license, there may be exceptions which will be handled on a case-by-case
+basis. For example, the Linux kernel patches are under the GPLv2 license with
+system exceptions, which can be found on <a
 href="http://www.kernel.org/pub/linux/kernel/COPYING">kernel.org</a>.
 </p>
 <h2>Contributor License Grants</h2>
@@ -25,15 +22,15 @@
 href="{@docRoot}source/cla-individual.html">Individual
 Contributor License Grant</a>. The grant can be executed online through the <a
 href="https://review.source.android.com/#settings,agreements">code review
-tool</a>. The agreement clearly defines the terms under which intellectual
-property has been contributed to the Android Open Source Project.This license
+tool</a>. The grant clearly defines the terms under which intellectual
+property has been contributed to the Android Open Source Project. This license
 is for your protection as a contributor as well as the protection of the
 project; it does not change your rights to use your own contributions for any
 other purpose.</p>
 <p>For a <b>corporation</b> (or other entity) that has assigned employees to
 work on the Android Open Source Project, a <a
 href="{@docRoot}source/cla-corporate.html">Corporate
-Contributor License Grant</a> is available. This version of the Grant allows a
+Contributor License Grant</a> is available. This version of the grant allows a
 corporation to authorize contributions submitted by its designated employees
 and to grant copyright and patent licenses. Note that a Corporate Contributor
 License Grant does not remove the need for any developer to sign their own
diff --git a/pdk/docs/source/life-of-a-bug.jd b/pdk/docs/source/life-of-a-bug.jd
index 1d58ae1..5d77f7a 100644
--- a/pdk/docs/source/life-of-a-bug.jd
+++ b/pdk/docs/source/life-of-a-bug.jd
@@ -57,7 +57,7 @@
 which is considered the "master" copy. (For instance, Google maintains one
 such private issue tracker, intended primarily for bugs which contain
 sensitive information which can't be revealed publicly.)</p></li>
-<li><b>Assigned</b><li>Like <code>Unassigned</code>, but the bug has been
+<li><b>Assigned</b><p>Like <code>Unassigned</code>, but the bug has been
 actually assigned to a specific contributor to fix.</p></li>
 </ul>
 <p>Typically, a given bug will start in <code>Unassigned</code>, where it
@@ -77,8 +77,8 @@
 <li><b>Spam</b><p>A kind soul sent us some delicious pork products, that we,
 regrettably, do not want.</p></li>
 <li><b>Question</b><p>Someone mistook the issue tracker for a help forum.
-(This is not as uncommon as one might assume: many users whose native language
-isn't English can make this mistake.)</p></li>
+(This is not as uncommon as you might think: many users whose native language
+isn't English misunderstand the site and make this mistake.)</p></li>
 <li><b>Unreproducible</b><p>An AOSP contributor attempted to reproduce the
 behavior described, and was unable to do so. This sometimes means that the bug
 is legitimate but simply rare or difficult to reproduce, and sometimes means
diff --git a/pdk/docs/source/overview-1.0.jd b/pdk/docs/source/overview-1.0.jd
deleted file mode 100644
index e1b5cd6..0000000
--- a/pdk/docs/source/overview-1.0.jd
+++ /dev/null
@@ -1,157 +0,0 @@
-page.title=Android 1.0 Features
-doc.type=source
-@jd:body
-<div><div><div><div>This page provides a high-level overview of Android 1.0
-features. To see the code itself, you can either use the <a href="http://android.git.kernel.org/">GitWeb</a>
-interface to view snapshots of the files, or you can <a
-href="{@docRoot}source/download.html">download</a>
-the source code onto your local machine.<br><br><b>Applications</b>
-<br><br>The Android platform comes with a variety of applications written using the Java programming language:<br><ul><li><i>Home</i>
-displays applications, widgets, and shortcuts. It also supports customizable wall paper.
-</li>
-<li><i>Phone</i>
-supports regular telephony functions as well as call controls, conference calls, supplementary services, and easy integration with <i>Contacts</i>
-.<br></li>
-<li><i>Web Browser</i>
-is a fully featured WebKit-based browser that supports HTML and XHTML.
-</li>
-<li><i>Email</i>
-provides access to email servers commonly found on the Internet and supports POP3, IMAP4, and SMTP.
-</li>
-<li><i>Media Player</i>
-enables managing, importing, and playing back content that has been encoded in various forms.<br></li>
-<li><i>Alarm Clock, Calculator, Calendar, Camera, Contacts, IM, MMS, Settings,</i>
-<i>Voice Dialer</i>
-, and many other applications are also included in this release.<br></li>
-</ul>
-<b>Application framework</b>
-<br><br></div>
-<div>The Android Application Framework has been designed to provide a rich set of APIs for third-party application developers. For more information, visit the <a href="http://developer.android.com/guide/index.html">Android SDK developer guide</a>
-.<b><br></b>
-</div>
-<div></div>
-<div><b>Android runtime</b>
-<b><br><br></b>
-Android applications run on Dalvik, a custom virtual machine (VM) designed for embedded use. The Dalvik VM runs dex executable files, which are typically compiled from source code written in Java.<br><br></div>
-<div>The dex executable format is designed to have these characteristics:<br><ul><li>Efficient on-device storage.
-</li>
-<li>Efficient runtime memory usage.
-</li>
-<li>Ease of interpretation.<br></li>
-</ul>
-Dalvik has the following runtime characteristics:
-<ul><li>Efficient support for multiple concurrent VM processes, including amortized initialization and heavily shared memory.
-</li>
-<li>Optimized interpreter.
-</li>
-<li>Efficient linkage to low-level native code.
-</li>
-<li>A familiar and rich set of core library functionality. For a complete list of supported libraries, see <a href="http://developer.android.com/reference/packages.html">http://developer.android.com/reference/packages.html</a>
-.
-</li>
-<li>Enhanced JDWP support, enabling easier debugging of multiple processes simultaneously.
-</li>
-<li>JNI support.
-</li>
-</ul>
-<b>Native libraries <br></b>
-<br>The Android platform makes use of many native libraries, including:<br><ul><li><i>Bionic</i>
-, a custom libc implementation optimized for embedded systems.
-</li>
-<li>Graphics libraries for 2D and 3D (OpenGL ES 1.0) graphics support.<br></li>
-<li>openCore to provide the bulk of Android's multimedia capability. It includes support for network streaming (HTTP and RTSP), as well as most of the codecs and media file parsers used by the system.<br></li>
-<li>sqlite to support having a lightweight transactional data store.
-</li>
-<li>WebKit library to power Android's WebKit based full web browser.<br></li>
-</ul>
-<br><b>System software</b>
-<b><br></b>
-<br></div>
-<div>About Android's operating system:
-<ul><li>Based on Linux 2.6.25 for ARM.<br></li>
-<li>Platform currently expects ARM V5T or better architecture. Support for earlier architectures could be added, but CPUs without an MMU would be difficult to support.
-</li>
-<li>A set of kernel enhancements are provided to support Android. The patches include alarm, ashmem, binder, power management, low memory killer, kernel degugger, and logger <b>.<br></b>
-</li>
-<li>While the platform is designed to be chipset agnostic, and will run on virtually any ARM-based Linux kernel environment, version 1.0 of the platform has been tested and verified on the MSM 7K chipsets <b>.</b>
-Over time we expect to see support for other major chipsets.
-Kernel patches for MSM based chipsets are also available.
-</li>
-<li>FAT32 file system is supported.
-</li>
-<li>Support for TCP/IP (TCP, UDP, etc).
-</li>
-</ul>
-</div>
-<div>A minimal reference bootloader for the supported chipset is provided. It is capable of booting Linux from RAM, debugger, and NAND Flash.<br></div>
-<div><br>About Android's support for debugging:<br><ul><li>Debugging native code is supported via GDB (GNU Project Debugger) over USB.
-</li>
-<li>Debugging managed code is supported via any JDWP-compatible debugger over USB.
-</li>
-<li>Logging and crash logs supported for debugging.
-</li>
-</ul>
-<b>Supported hardware <br></b>
-<ul><li>The platform will run on almost on any ARM based Linux kernel environment.
-</li>
-<li>The platform requires a minimum of 128 MB of RAM and 256 MB ofFlash memory. AnOEM may want to support more Flash memory to make it possible to download more third-party applications to user devices.<br></li>
-<li>The platform will interface with a baseband radio stack provided externally via a Radio Interface Layer (RIL).
-</li>
-<li>802.11 b/g Wi-Fi
-</li>
-<li>Standard USB interface, including USB 2.0
-</li>
-<li>Bluetooth 2.0 EDR (Enhanced Data Rate)
-</li>
-<li>Camera for still and video capture
-</li>
-<li>Removable storage
-</li>
-</ul>
-<b>Supported display</b>
-<br><ul><li>HVGA resolution <br></li>
-<li>16 bit color depth <br></li>
-<li>Landscape and portrait orientation, including dynamic runtime switching
-</li>
-<li>Finger-based touchscreen navigation
-</li>
-</ul>
-<b>Supported keypads and buttons</b>
-<br><ul><li>QWERTY
-</li>
-<li>5-way navigation
-</li>
-<li>Hardware buttons: Send, End, Home, Back, Menu</li>
-<li>Power button
-</li>
-<li>Volume keys (up and down)
-</li>
-<li>Camera trigger button, including detection for both partial press (to focus) and full press (to actually take a picture)
-</li>
-</ul>
-<b>Supported audio outputs</b>
-<br><ul><li>Audio output via the headphone jack (mono and stereo)
-</li>
-<li>64 kbps Bluetooth audio supported</li>
-</ul>
-<b>Supported notifications</b>
-<br><ul><li>LEDs
-</li>
-<li>Vibration
-</li>
-</ul>
-<b>Supported radio and telephony features <br></b>
-<ul><li>GPRS, EDGE, UMTS, HSDPA
-</li>
-<li>International roaming, SMS, MMS <br></li>
-<li>Emergency call support <br></li>
-<li>Supplementary Services for Telephony, for example call waiting and conference calling <br></li>
-<li>Unstructured Supplementary Service Data (USSD)
-</li>
-<li>Reference Radio Interface Layer (RIL)
-</li>
-</ul>
-</div>
-</div>
-</div>
-</div>
diff --git a/pdk/docs/source/overview-1.5.jd b/pdk/docs/source/overview-1.5.jd
deleted file mode 100644
index dd74874..0000000
--- a/pdk/docs/source/overview-1.5.jd
+++ /dev/null
@@ -1,198 +0,0 @@
-page.title=Android 1.5 Features
-doc.type=source
-@jd:body
-<h3><b>Release features - Android 1.5</b>
-</h3>
-<div><div><div><div><b>Previous release highlights</b>
-:<a href="{@docRoot}source/overview-1.0.html">Android 1.0</a>
-<br><br>This page provides a high-level overview of the new features added to
-Android 1.5. To see the code itself, you can either use the<a href="http://android.git.kernel.org/">GitWeb</a>
-interface to view snapshots of the files, or you can<a
-href="{@docRoot}source/download.html">download</a>
-the source code onto your local machine. You can use<i>repo init -u</i>
-git://android.git.kernel.org/platform/manifest.git<i>-b android-1.5</i>
-to download the source code for Android 1.5.<br><br><b>User interface refinements:</b>
-<br><ul><li>System-wide:
-<ul><li>Refinement of all core UI elements
-</li>
-<li>Animated window transitions (off by default)
-</li>
-<li>Accelerometer-based application rotations
-</li>
-</ul>
-</li>
-<li>UI polish for:
-<ul><li>In-call experience
-</li>
-<li>Contacts, Call log, and Favorites
-</li>
-<li>SMS MMS
-</li>
-<li>Browser
-</li>
-<li>Calendar
-</li>
-<li>Email
-</li>
-<li>Camera Gallery
-</li>
-<li>Application management
-</li>
-</ul>
-</li>
-</ul>
-<div><b><br>Performance improvements:</b>
-<br></div>
-<ul><li>Faster Camera start-up and image capture
-</li>
-<li>Much faster acquisition of GPS location (powered by SUPL AGPS)
-</li>
-<li>Smoother page scrolling in Browser
-</li>
-</ul>
-<br><b>Applications</b>
-<br><ul><li>Camera Gallery
-</li>
-<ul><li>Video recording
-</li>
-<li>Video playback (MPEG-4 3GP formats)
-</li>
-</ul>
-<li>Browser
-</li>
-<ul><li>Updated with latest Webkit browser Squirrelfish Javascript engines
-</li>
-<li>Copy 'n paste in browser
-</li>
-<li>Search within a page
-</li>
-<li>User-selectable text-encoding
-</li>
-<li>UI changes include:
-</li>
-<ul><li>Unified Go and Search box
-</li>
-<li>Tabbed bookmarks/history/most-visited screen
-</li>
-</ul>
-</ul>
-<li>Contacts
-</li>
-<ul><li>Shows user picture for Favorites
-</li>
-<li>Specific date/time stamp for events in call log
-</li>
-<li>One-touch access to a contact card from call log event
-</li>
-</ul>
-</ul>
-<b><br>Application framework</b>
-<br><br></div>
-<div><ul><li>On-screen soft keyboard
-</li>
-<ul><li>Works in both portrait and landscape orientation
-</li>
-<li>Support for user installation of 3rd party keyboards
-</li>
-<li>User dictionary for custom words
-</li>
-</ul>
-<li>Home screen
-</li>
-<ul><li>Widgets
-</li>
-<ul><li>Bundled home screen widgets include: analog clock, calendar, music player, picture frame, and search
-</li>
-</ul>
-<li>Live folders
-</li>
-</ul>
-<li>UI framework
-</li>
-<ul><li>Framework for easier background/UI thread interaction
-</li>
-<li>New SlidingDrawer widget
-</li>
-<li>Horizontal ScrollView widget
-</li>
-</ul>
-<li>Home Screen framework
-</li>
-<ul><li>APIs for creating secure home screen widgets
-</li>
-<li>APIs for populating live folders with custom content
-</li>
-</ul>
-<li>Media framework
-</li>
-<ul><li>Raw audio recording and playback APIs
-</li>
-<li>Interactive MIDI playback engine
-</li>
-<li>Video recording APIs for developers (3GP format)
-</li>
-<li>Video and photo sharing Intents
-</li>
-<li>Media search Intent
-</li>
-</ul>
-<li>Input Method framework
-</li>
-<ul><li>Text prediction engine
-</li>
-<li>Ability to provide downloadable IMEs to users
-</li>
-</ul>
-<li>Speech recognition framework
-</li>
-<ul><li>Support for using speech recognition libraries via Intent
-</li>
-</ul>
-<li>Misc API additions
-</li>
-<ul><li>LocationManager - Applications can get location change updates via Intent
-</li>
-<li>WebView - Touch start/end/move/cancel DOM event support
-</li>
-<li>SensorManager - redesigned sensor APIs
-</li>
-<li>GLSurfaceView - convenience framework for creating OpenGL applications
-</li>
-<li>Broadcast Intent for app update install succeeded - for smoother app upgrade experience
-</li>
-</ul>
-</ul>
-</div>
-<div></div>
-<div><br><b>System software</b>
-<br><br></div>
-<ul><li>New Linux kernel (version 2.6.27)
-</li>
-<li>SD card filesystem auto-checking and repair
-</li>
-<li>SIM Application Toolkit 1.0
-</li>
-</ul>
-<div><b><br>Supported hardware<br></b>
-<ul><li>Bluetooth</li>
-<ul><li>Stereo Bluetooth support (A2DP and AVCRP profiles)
-</li>
-<li>Auto-pairing
-</li>
-<li>Improved handsfree experience
-</li>
-</ul>
-</ul>
-<br><b>Developer tools</b>
-<br></div>
-<div><ul><li>Support for multiple versions of Android in a single SDK installation
-</li>
-<li>Improved JUnit support in ADT
-</li>
-<li>Easier application performance profiling
-</li>
-</ul>
-</div>
-</div>
-</div>
-</div>
diff --git a/pdk/docs/source/overview.jd b/pdk/docs/source/overview.jd
index 2763c52..7cb593c 100644
--- a/pdk/docs/source/overview.jd
+++ b/pdk/docs/source/overview.jd
@@ -1,93 +1,36 @@
-page.title=Android 1.6 Platform Overview
+page.title=Android 2.1 Platform
 doc.type=source
 @jd:body
-<p>This page provides a high-level overview of the new features added to
-Android 1.6. To see the code itself, you can either use the <a
-href="http://android.git.kernel.org/">GitWeb</a>
-interface to view snapshots of the files, or you can <a
-href="{@docRoot}source/download.html">download</a>
-the source code onto your local machine. You can use <code>repo init -u
-git://android.git.kernel.org/platform/manifest.git -b android-1.6</code>
-to download the source code for Android 1.6.</p>
-<p><i>Note: platform overview information for Android 2.x has not yet been
-published, since the <a
-href="{@docRoot}compatibility/index.html">Compatibility Program</a> for
-Android 2.x has not yet launched. When the Compatibilty Definition Document
-for 2.x is released, this page will be updated to match.</i></p>
-<p>Information about older Android releases is also available:<ul>
-<li><a href="{@docRoot}source/overview-1.5.html">Android 1.5 Platform Overview</a></li>
-<li><a href="{@docRoot}source/overview-1.0.html">Android 1.0 Platform Overview</a></li>
-</ul></p>
-<h2>User interface refinements</h2>
-<h3>Quick Search Box for Android </h3>
-<p>Android 1.6 includes a redesigned search framework that provides a quick, effective, and consistent way for users to search across multiple sources—such as browser bookmarks history, contacts, and the web—directly from the home screen.
-</p>
-<p><br></p>
-<p>The system constantly learns which search results are more relevant based on what is clicked. So popular contacts or apps that have previously been picked will bubble up to the top when a user types the first few letters of a relevant query.
-</p>
-<p><br></p>
-<p>The search framework also provides developers a way to easily expose relevant content from their applications in Quick Search Box.
-</p>
-<h3>Camera, Camcorder, and Gallery </h3>
-<p>An updated user interface provides an integrated camera, camcorder, and gallery experience. Users can quickly toggle between still and video capture modes. Additionally, the gallery enables users to select multiple photos for deletion.<br></p>
-<p><br></p>
-<p>Android 1.6 also provides a much faster camera experience. Compared to the previous release, launching the camera is now 39% faster, and there is a 28% improvement in the time from completing one shot to the next.
-</p>
-<h3>VPN, 802.1x </h3>
-<p>A new Virtual Private Network (VPN) control panel in Settings allows users to configure and connect to the following types of VPNs:
-</p>
-<ul><li>L2TP/IPSEC pre-shared key based VPN
-  </li>
-<li>L2TP/IPsec certificate based VPN
-  </li>
-<li>L2TP only VPN
-  </li>
-<li>PPTP only VPN
-  </li>
-</ul>
-<h3>Battery usage indicator </h3>
-<p>A new battery usage screen lets users see which apps and services are consuming battery power. If the user determines that a particular service or application is using too much power, they can take action to save the battery by adjusting settings, stopping the application, or uninstalling the application.
-</p>
-<h3>Accessibility </h3>
-<p>Users will be able to download new accessibility services built on the new accessibility framework and enable them in Settings.
-</p>
-<h2>New Platform Technologies </h2>
-<h3>Expanded Search Framework </h3>
-<p>The Android search framework has been redesigned and expanded to provide third-party applications the opportunity to surface content from their applications in Quick Search Box, the global search tool. To do this, developers will need to make their app "searchable" and provide suggestions in response to user queries. To enable application search suggestions, users simply select each application from which they'd like to receive suggestions, under Searchable items in the Search settings.
-</p>
-<h3>Text-to-speech engine</h3>
-<p>Android 1.6 features a multi-lingual speech synthesis engine called Pico. It allows any Android application to "speak" a string of text with an accent that matches the language. The engine supports the following languages: English (American and British accents), French, Italian, German and Spanish. If you're using a T-Mobile G1 or Dream device, you'll need to download the SpeechSynthesis Data Installer from Android Market, which includes the "voices" needed by the text-to-speech engine.
-</p>
-<h3>Gestures </h3>
-<p>A new gestures framework provides application developers with a framework for creating, storing, loading, and recognizing gestures and associating them with specific actions.
-</p>
-<p>Developers can use the new GestureBuilder tool included in the Android 1.6 SDK to generate libraries of gestures to include with their application.
-</p>
-<h3>Accessibility </h3>
-<p>Android 1.6 provides a new accessibility framework. With this framework, developers can create accessibility plugins that respond to user input, such as making a sound when a new window is shown, vibrating when navigating to the top of a list, and providing spoken feedback.
-</p>
-<h3>Expanded support for screen densities and resolutions </h3>
-<p>Android 1.6 adds screen support that enables applications to be rendered properly on different display resolutions and densities. Developers can also specify the types of screens supported by their application.
-</p>
-<h3>Telephony support for CDMA </h3>
-<p>Android 1.6 includes support for CDMA in the telephony stack.
-</p>
-<h3>New version of OpenCore </h3>
-<p>Android 1.6 includes the updated OpenCore 2 media engine, which has:
-</p>
-<ul><li>Support for OpenMAX encoders
-  </li>
-<li>Support for additional audio codecs in AuthorEngine
-  </li>
-<li>Improved buffering model supports shared buffers allocated in the decoder
-  </li>
-</ul>
-<h3>2.6.29 Linux kernel </h3>
-<p>Android 1.6 upgrades the Linux kernel from 2.6.27 to 2.6.29.
-</p>
-<h2>New Framework APIs</h2>
-<p>For a detailed overview of new APIs, see the <a
-href="http://developer.android.com/sdk/android-1.6.html#api-changes">Version
-Notes</a>. For a complete report of all API changes, see the <a
-href="http://developer.android.com/sdk/api_diff/4/changes.html">API
-Differences Report</a>.</p>
+<p>Our sister site, <a
+href="http://developer.android.com/">http://developer.android.com/</a>, 
+includes feature overviews of the various Android platform versions.
+The links below will take you to developer.android.com where you can view this
+information.</p>
+<p>The links below will navigate you away from this site.</p>
+<p><i>Note: this page will be updated to cover Android 2.2 when the
+formal compatibility program for Android 2.2 is launched, which will occur in
+the near future.</i></p>
+<h3><a href="http://developer.android.com/sdk/android-2.0-highlights.html">Android
+2.1</a></h3>
+<p>Android 2.1 corresponded to the "Eclair" milestone branch, and has an API level of
+7.</p>
+<p>The Eclair branch was also used for 2.0 and 2.0.1; however, both of those
+releases were quickly obsoleted by the version 2.1 Eclair release. As Android
+2.1 includes key bug fixes and improvements not present in 2.0/2.0.1, only
+Android 2.1 should be used for new devices. As there is no compatibility
+program for 2.0 or 2.0.1, the officially compatible Eclair-based release is Android
+2.1. (The linked document refers to Android 2.0, because there were
+no new platform features added in 2.1.)</p>
+<h3><a href="http://developer.android.com/sdk/android-1.6-highlights.html">Android 1.6</a></h3>
+<p>Android 1.6 corresponded to the "Donut" milestone branch, and has an API level of
+4.</p>
+<h3><a href="http://developer.android.com/sdk/android-1.5-highlights.html">Android 1.5</a></h3>
+<p>Android 1.5 corresponded to the "Cupcake" milestone branch, and has an API
+level of 3.</p>
+<h3><a href="http://developer.android.com/sdk/android-1.1.html">Android 1.1</a></h3>
+<p>Android 1.1 has an API level of 2. Android 1.1 was known as
+"Petit Four" internally, though this name was not used officially.</p>
+<h3>Android 1.0</h3>
+<p>was the first release of Android, and has an API
+level of 1. Since it was the first released version of Android, no platform
+highlights were prepared for this release.</p>
diff --git a/pdk/docs/source/report-bugs.jd b/pdk/docs/source/report-bugs.jd
index 138080d..a6e56c6 100644
--- a/pdk/docs/source/report-bugs.jd
+++ b/pdk/docs/source/report-bugs.jd
@@ -77,7 +77,7 @@
 
 public class TestObjectNull extends Activity {
     /** Called when the activity is first created. */
-    @Override
+    &#64;Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         setContentView(R.layout.main);
diff --git a/pdk/docs/source/roles.jd b/pdk/docs/source/roles.jd
index 451c821..f4fb891 100644
--- a/pdk/docs/source/roles.jd
+++ b/pdk/docs/source/roles.jd
@@ -15,22 +15,22 @@
 
 <h2>Contributor</h2>
 <p>A "Contributor" is anyone making contributions to the AOSP source code,
-including both employees of Google or other companies, as well as
-external developers who are contributing to Android on their own behalf.
-There is no distinction between Contributors who are employed by 
-Google, and those who are not: all engineers use the same git/gerrit tools, 
-follow the same code review process, and are subject to the same requirements
-on code style and so on.</p>
+including both employees of Google or other companies, as well as external
+developers who are contributing to Android on their own behalf.  There is no
+distinction between Contributors who are employed by Google, and those who are
+not: all engineers use the same tools (<code>git</code>, <code>repo</code>,
+and <code>gerrit</code>), follow the same code review process, and are subject
+to the same requirements on code style and so on.</p>
 <p/>
 
 <h2>Developer</h2>
 <p>A "Developer" is an engineer writing applications that run on Android
 devices. There is, of course, no difference in skillset between a "Developer"
-and a "Contributor"; AOSP simply uses "Developer" to help identify our audience.
-Since the key purpose of Android is to cultivate an open development platform,
-"Developers" are one of the key customers of the Android platform. As such, we
-talk about them a lot, though this isn't technically a separate role in the
-AOSP <i>per se.</i></p>
+and a "Contributor", but AOSP uses "Developer" to distinguish between
+engineers using the platform and those contributing to it. Developers are
+(along with end users) the "customers" of the platform that the Contributors
+create. As such, we talk about Developers a lot, though this isn't technically
+a separate role in the AOSP <i>per se.</i></p>
 <p/>
 
 <h2>Verifier</h2>
@@ -62,12 +62,12 @@
   releases.</li>
   <li>Designate Verifiers and Approvers for submitted patches.</li>
   <li>Be fair and unbiased while reviewing changes. Accept or reject patches
-  based on technical merit and alignment with the Android platform.</li>
+  based on technical merit and alignment with the Android strategy.</li>
   <li>Review changes in a timely manner and make best efforts to communicate
   when changes are not accepted.</li>
   <li>Optionally maintain a web site for the project for information and
   documents specific to the project.</li>
   <li>Act as a facilitator in resolving technical conflicts.</li>
-  <li>Be the public face for the project and the go-to person for questions
+  <li>Be a public face for the project and the go-to person for questions
   related to the project.</li>
 </ul>
diff --git a/pdk/docs/source/using-eclipse.jd b/pdk/docs/source/using-eclipse.jd
index 56df713..660de11 100644
--- a/pdk/docs/source/using-eclipse.jd
+++ b/pdk/docs/source/using-eclipse.jd
@@ -95,58 +95,6 @@
 <p>When you're done, the "source folder" path in the list should look like android/packages/apps/<i>YourAppName</i>
 /src. Depending on which app(s) you include, you may also need to include othersrc/main/java directories under android/dalvik/libcore. Do this if you find you cannot build with the default set.
 </p>
-<h4>
-Eclipse setup to work on developer tools
-</h4>
-<p>To work on Java developer tools, the principle is the same, except you specify /path/to/tool when using the option "Create project from existing source."
-</p>
-<p>Once the project is created, you need to set up the Java Build Path:
-</p>
-<ol><li>Select the project you just created.
-</li>
-<li>Project Properties
-</li>
-<li>Select "Java Build Path" from the left-hand menu.
-</li>
-<li>Choose the "Source" tab.
-</li>
-<li>Expand the single <i>toolname</i>
-/src entry.
-</li>
-<li>Double click the "Excluded: (none)" item.
-</li>
-<li>Add to the excluded (bottom) list: "MakeFile" and "resources/".
-</li>
-<li>Close the dialog.
-</li>
-<li>Back in the "Source" tab, click "Add Folder...", and add <i>toolname</i>
-/src/resources.
-</li>
-<li>Click OK.
-</li>
-</ol>
-<h4>
-Eclipse setup to work on DDMS <br></h4>
-<p>For DDMS, you will need to make a project for
-</p>
-<ol><li>development/tools/ddms/libs/ddmlib
-</li>
-<li>development/tools/ddms/libs/ddmuilib
-</li>
-<li>development/tools/ddms/app
-</li>
-</ol>
-<p>Each project will need to reference the ones before it ("ddmuilib" references "ddmlib", and "app" references both of those). To do this:
-</p>
-<ol><li>Make sure you have all 3 projects defined.
-</li>
-<li>Right click on a project, "Build Path" "Configure Build Path..."
-</li>
-<li>Choose the "Project" tab.
-</li>
-<li>Click "Add..." and check the required projects.
-</li>
-</ol>
 <h2><a>Eclipse formatting</a>
 </h2>
 <p>You can import files in development/ide/eclipse to make Eclipse
@@ -189,15 +137,6 @@
 
 <pre>Ctrl-Shift-o = Organize imports <br>Ctrl-Shift-t = load class by name <br>Ctrl-Shift-r = load non-class resource by name <br>Ctrl-1 = quick fix <br>Ctrl-e = Recently viewed files <br>Ctrl-space = auto complete <br>Shift-Alt-r = refactor:rename <br>Shift-Alt-v = refactor:move <br></pre>
 
-<h2><a>Useful Plugins</a>
-</h2>
-<p>Eclipse has a plugin architecture that enables third parties to extend the IDE. Here are some plugins that make Eclipse easier to use for writing Android software:
-</p>
-
-<ul><li><a href="http://andrei.gmxhome.de/anyedit/">AnyEdit</a>
-- automatically fix whitespace issues when saving files. Can convert tabs to spaces, strip blanks at end-of-line, and ensure the last line of the file has an end-of-line character.
-</li>
-</ul>
 <h2><a>"Eclipse is not working correctly, what should I do?"</a>
 </h2>
 <p>Make sure:
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index e3eca81..f0c9598 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -34,9 +34,11 @@
 
     <!-- We will request access to the camera, saying we require a camera
          of some sort but not one with autofocus capability. -->
+    <!--
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-feature android:name="android.hardware.camera" />
     <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
+    -->
 
     <application android:name="ApiDemosApplication"
             android:label="@string/activity_sample_code"
@@ -207,6 +209,74 @@
             </intent-filter>
         </activity>
 
+        <!-- Fragment Samples -->
+
+        <activity android:name=".app.FragmentAnim" android:label="@string/fragment_anim">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentContextMenu"
+                android:label="@string/fragment_context_menu">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentLayout" android:label="@string/fragment_layout">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentLayout$DialogActivity" />
+
+        <activity android:name=".app.FragmentListCursorLoader" android:label="@string/fragment_list_cursor_loader">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentListArray" android:label="@string/fragment_list_array">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentMenu" android:label="@string/fragment_menu">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentRetainInstance" android:label="@string/fragment_retain_instance">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentReceiveResult" android:label="@string/fragment_receive_result">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentStack" android:label="@string/fragment_stack">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <!-- Intent Samples -->
 
         <activity android:name=".app.Intents" android:label="@string/activity_intents">
diff --git a/samples/ApiDemos/res/layout-land/fragment_layout.xml b/samples/ApiDemos/res/layout-land/fragment_layout.xml
new file mode 100644
index 0000000..24629e9
--- /dev/null
+++ b/samples/ApiDemos/res/layout-land/fragment_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<!-- Top-level content view for the layout fragment sample.  This version is
+     for display when in landscape: we can fit both titles and dialog. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+
+    <fragment android:name="com.example.android.apis.app.FragmentLayout$TitlesFragment"
+            android:id="@+id/titles" android:layout_weight="1"
+            android:layout_width="0px" android:layout_height="match_parent" />
+
+    <fragment android:name="com.example.android.apis.app.FragmentLayout$DialogFragment"
+            android:id="@+id/dialog" android:layout_weight="1"
+            android:layout_width="0px" android:layout_height="match_parent" />
+    
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/device_admin_sample.xml b/samples/ApiDemos/res/layout/device_admin_sample.xml
index 2827c01..f7273be 100644
--- a/samples/ApiDemos/res/layout/device_admin_sample.xml
+++ b/samples/ApiDemos/res/layout/device_admin_sample.xml
@@ -15,126 +15,199 @@
 -->
 
 <!-- Demonstrates implementation of a DeviceAdmin. -->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical" android:padding="4dip"
-    android:gravity="center_horizontal"
-    android:layout_width="match_parent" android:layout_height="match_parent">
+    <LinearLayout
+        android:orientation="vertical" android:padding="4dip"
+        android:gravity="center_horizontal"
+        android:layout_width="match_parent" android:layout_height="match_parent">
 
-    <TextView
-        android:layout_width="match_parent" android:layout_height="wrap_content"
-        android:layout_weight="0"
-        android:paddingBottom="4dip"
-        android:text="@string/sample_device_admin_summary"/>
-
-    <LinearLayout android:orientation="horizontal" android:gravity="center"
-        android:layout_width="match_parent" android:layout_height="wrap_content">
-
-        <Button android:id="@+id/enable"
-            android:layout_width="wrap_content" android:layout_height="wrap_content"
-            android:text="@string/enable_admin">
-            <requestFocus />
-        </Button>
-
-        <Button android:id="@+id/disable"
-            android:layout_width="wrap_content" android:layout_height="wrap_content"
-            android:text="@string/disable_admin">
-        </Button>
-
-    </LinearLayout>
-
-    <LinearLayout android:orientation="horizontal" android:gravity="center"
-        android:layout_width="match_parent" android:layout_height="wrap_content">
-
-        <Spinner android:id="@+id/password_quality"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:drawSelectorOnTop="true"
-            android:prompt="@string/password_quality">
-        </Spinner>
-
-        <EditText android:id="@+id/password_length"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:hint="@string/password_length_hint"
-            android:inputType="number">
-        </EditText>
-
-    </LinearLayout>
-
-    <Button android:id="@+id/set_password"
-        android:layout_width="wrap_content" android:layout_height="wrap_content"
-        android_layout_gravity="east|center_vertical"
-        android:text="@string/set_password">
-    </Button>
-
-    <LinearLayout android:orientation="horizontal" android:gravity="center"
-        android:layout_width="match_parent" android:layout_height="wrap_content">
-
-        <EditText android:id="@+id/password"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:hint="@string/password_hint"
-            android:freezesText="true">
-        </EditText>
-
-        <Button android:id="@+id/reset_password"
-            android:layout_width="wrap_content" android:layout_height="wrap_content"
+        <TextView
+            android:layout_width="match_parent" android:layout_height="wrap_content"
             android:layout_weight="0"
-            android:text="@string/reset_password">
-        </Button>
+            android:paddingBottom="4dip"
+            android:text="@string/sample_device_admin_summary"/>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <Button android:id="@+id/enable"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android:text="@string/enable_admin">
+                <requestFocus />
+            </Button>
+
+            <Button android:id="@+id/disable"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android:text="@string/disable_admin">
+            </Button>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <Spinner android:id="@+id/password_quality"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawSelectorOnTop="true"
+                android:prompt="@string/password_quality">
+            </Spinner>
+
+            <EditText android:id="@+id/password_length"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_length_hint"
+                android:inputType="number">
+            </EditText>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <EditText android:id="@+id/password_minimum_letters"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_minimum_letters_hint"
+                android:inputType="number">
+            </EditText>
+
+            <EditText android:id="@+id/password_minimum_numeric"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_minimum_numeric_hint"
+                android:inputType="number">
+            </EditText>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <EditText android:id="@+id/password_minimum_lowercase"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_minimum_lowercase_hint"
+                android:inputType="number">
+            </EditText>
+
+            <EditText android:id="@+id/password_minimum_uppercase"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_minimum_uppercase_hint"
+                android:inputType="number">
+            </EditText>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <EditText android:id="@+id/password_minimum_symbols"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_minimum_symbols_hint"
+                android:inputType="number">
+            </EditText>
+
+            <EditText android:id="@+id/password_minimum_nonletter"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_minimum_nonletter_hint"
+                android:inputType="number">
+            </EditText>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <EditText android:id="@+id/password_history_length"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_history_length_hint"
+                android:inputType="number">
+            </EditText>
+
+            <Button android:id="@+id/set_password"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android_layout_gravity="east|center_vertical"
+                android:text="@string/set_password">
+            </Button>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <EditText android:id="@+id/password"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:hint="@string/password_hint"
+                android:freezesText="true">
+            </EditText>
+
+            <Button android:id="@+id/reset_password"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:text="@string/reset_password">
+            </Button>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <EditText android:id="@+id/max_failed_pw"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/max_failed_pw_hint"
+                android:inputType="number">
+            </EditText>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <Button android:id="@+id/force_lock"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:text="@string/force_lock">
+            </Button>
+
+            <Button android:id="@+id/wipe_data"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:text="@string/wipe_data">
+            </Button>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
+            <EditText android:id="@+id/timeout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:hint="@string/timeout_hint"
+                android:inputType="number"
+                android:freezesText="true">
+            </EditText>
+
+            <Button android:id="@+id/set_timeout"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:text="@string/set_timeout_label">
+            </Button>
+
+        </LinearLayout>
 
     </LinearLayout>
 
-    <LinearLayout android:orientation="horizontal" android:gravity="center"
-        android:layout_width="match_parent" android:layout_height="wrap_content">
-
-        <EditText android:id="@+id/max_failed_pw"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:hint="@string/max_failed_pw_hint"
-            android:inputType="number">
-        </EditText>
-
-    </LinearLayout>
-
-    <LinearLayout android:orientation="horizontal" android:gravity="center"
-        android:layout_width="match_parent" android:layout_height="wrap_content">
-
-        <Button android:id="@+id/force_lock"
-            android:layout_width="wrap_content" android:layout_height="wrap_content"
-            android:layout_weight="0"
-            android:text="@string/force_lock">
-        </Button>
-
-        <Button android:id="@+id/wipe_data"
-            android:layout_width="wrap_content" android:layout_height="wrap_content"
-            android:layout_weight="0"
-            android:text="@string/wipe_data">
-        </Button>
-
-    </LinearLayout>
-
-    <LinearLayout android:orientation="horizontal" android:gravity="center"
-        android:layout_width="match_parent" android:layout_height="wrap_content">
-
-        <EditText android:id="@+id/timeout"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:hint="@string/timeout_hint"
-            android:inputType="number"
-            android:freezesText="true">
-        </EditText>
-
-        <Button android:id="@+id/set_timeout"
-            android:layout_width="wrap_content" android:layout_height="wrap_content"
-            android:layout_weight="0"
-            android:text="@string/set_timeout_label">
-        </Button>
-
-    </LinearLayout>
-
-</LinearLayout>
-
+</ScrollView>
diff --git a/samples/ApiDemos/res/layout/fragment_anim.xml b/samples/ApiDemos/res/layout/fragment_anim.xml
new file mode 100644
index 0000000..8bec894
--- /dev/null
+++ b/samples/ApiDemos/res/layout/fragment_anim.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<!-- Top-level content view for the layout fragment sample. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:gravity="center_horizontal"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+
+    <LinearLayout android:orientation="horizontal" android:padding="4dip"
+        android:gravity="center_vertical" android:layout_weight="1"
+        android:layout_width="wrap_content" android:layout_height="wrap_content">
+
+        <fragment android:name="com.example.android.apis.app.FragmentAnim$FirstFragment"
+                android:id="@+id/fragment1" android:layout_weight="1"
+                android:layout_width="0px" android:layout_height="wrap_content" />
+
+        <Button android:id="@+id/frag1hide"
+            android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="Hide">
+        </Button>
+    
+    </LinearLayout>
+    
+    <LinearLayout android:orientation="horizontal" android:padding="4dip"
+        android:gravity="center_vertical" android:layout_weight="1"
+        android:layout_width="wrap_content" android:layout_height="wrap_content">
+
+        <fragment android:name="com.example.android.apis.app.FragmentAnim$SecondFragment"
+                android:id="@+id/fragment2" android:layout_weight="1"
+                android:layout_width="0px" android:layout_height="wrap_content" />
+
+        <Button android:id="@+id/frag2hide"
+            android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="Hide">
+        </Button>
+    
+    </LinearLayout>
+    
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_context_menu.xml b/samples/ApiDemos/res/layout/fragment_context_menu.xml
new file mode 100644
index 0000000..8f2c044
--- /dev/null
+++ b/samples/ApiDemos/res/layout/fragment_context_menu.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/fragment_context_menu_msg" />
+
+    <Button android:id="@+id/long_press"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/long_press">
+        <requestFocus />
+    </Button>
+    
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_layout.xml b/samples/ApiDemos/res/layout/fragment_layout.xml
new file mode 100644
index 0000000..e7704ff
--- /dev/null
+++ b/samples/ApiDemos/res/layout/fragment_layout.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<!-- Top-level content view for the layout fragment sample.  This version is
+     for display when not in landscape: we can only fit the list of titles. -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
+            android:name="com.example.android.apis.app.FragmentLayout$TitlesFragment"
+            android:id="@+id/titles"
+            android:layout_width="match_parent" android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_menu.xml b/samples/ApiDemos/res/layout/fragment_menu.xml
new file mode 100644
index 0000000..b2d3c52
--- /dev/null
+++ b/samples/ApiDemos/res/layout/fragment_menu.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/fragment_menu_msg" />
+
+    <CheckBox android:id="@+id/menu1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:checked="true"
+        android:text="@string/fragment1menu">
+    </CheckBox>
+    
+    <CheckBox android:id="@+id/menu2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:checked="true"
+        android:text="@string/fragment2menu">
+    </CheckBox>
+    
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_retain_instance.xml b/samples/ApiDemos/res/layout/fragment_retain_instance.xml
new file mode 100644
index 0000000..e9a9a43
--- /dev/null
+++ b/samples/ApiDemos/res/layout/fragment_retain_instance.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/fragment_retain_instance_msg" />
+
+    <ProgressBar android:id="@+id/progress_horizontal"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="200dip"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:padding="6dp"
+        android:max="500" />
+
+    <Button android:id="@+id/restart"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/restart">
+        <requestFocus />
+    </Button>
+    
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_stack.xml b/samples/ApiDemos/res/layout/fragment_stack.xml
new file mode 100644
index 0000000..6e3a49f
--- /dev/null
+++ b/samples/ApiDemos/res/layout/fragment_stack.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<!-- Top-level content view for the simple fragment sample. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:padding="4dip"
+    android:gravity="center_horizontal"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+
+    <FrameLayout
+            android:id="@+id/simple_fragment"
+            android:layout_width="match_parent"
+            android:layout_height="0px"
+            android:layout_weight="1" />
+            
+    <Button android:id="@+id/next"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:layout_weight="0" 
+        android:text="@string/next">
+        <requestFocus />
+    </Button>
+
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/gallery_1.xml b/samples/ApiDemos/res/layout/gallery_1.xml
index 9b9c4bb..8239ef0 100644
--- a/samples/ApiDemos/res/layout/gallery_1.xml
+++ b/samples/ApiDemos/res/layout/gallery_1.xml
@@ -14,8 +14,19 @@
      limitations under the License.
 -->
 
-<Gallery xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gallery"
-	android:layout_width="match_parent"
-	android:layout_height="wrap_content"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout2"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+<Gallery  android:id="@+id/gallery"
+android:layout_width="fill_parent"
+android:layout_height="wrap_content"
 />
-       
+<EditText
+ android:text="@+id/EditText01"
+ android:id="@+id/EditText01"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"></EditText>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/ApiDemos/res/layout/labeled_text_edit.xml b/samples/ApiDemos/res/layout/labeled_text_edit.xml
new file mode 100644
index 0000000..27568af
--- /dev/null
+++ b/samples/ApiDemos/res/layout/labeled_text_edit.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<!-- Content for a fragment with a text editor. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:padding="4dip"
+    android:layout_width="match_parent" android:layout_height="wrap_content">
+
+    <TextView android:id="@+id/msg"
+        android:layout_width="match_parent" android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:paddingBottom="4dip" />
+
+    <EditText android:id="@+id/saved"
+        android:layout_width="match_parent" android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:background="@drawable/green"
+        android:text="@string/initial_text"
+        android:freezesText="true">
+        <requestFocus />
+    </EditText>
+
+</LinearLayout>
+
diff --git a/samples/ApiDemos/res/values/arrays.xml b/samples/ApiDemos/res/values/arrays.xml
index df918cd..f5ccbe1 100644
--- a/samples/ApiDemos/res/values/arrays.xml
+++ b/samples/ApiDemos/res/values/arrays.xml
@@ -92,6 +92,7 @@
         <item>Numeric</item>
         <item>Alphabetic</item>
         <item>Alphanumeric</item>
+        <item>Complex</item>
     </string-array>
 
 </resources>
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index 802fc80..a8f8af2 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -83,6 +83,35 @@
     <string name="redirect_getter">Enter the text that will be used by the main activity.  Press back to cancel.</string>
     <string name="apply">Apply</string>
 
+    <string name="fragment_anim">App/Fragment/Anim</string>
+    
+    <string name="fragment_context_menu">App/Fragment/Context Menu</string>
+    <string name="fragment_context_menu_msg">Fragment populating a context
+            menu; long press the button to see.</string>
+    <string name="long_press">Long press me</string>
+    
+    <string name="fragment_layout">App/Fragment/Layout</string>
+    
+    <string name="fragment_list_array">App/Fragment/List Array</string>
+    
+    <string name="fragment_list_cursor_loader">App/Fragment/List Cursor Loader</string>
+    
+    <string name="fragment_menu">App/Fragment/Menu</string>
+    <string name="fragment_menu_msg">Build menus from two fragments, allowing
+        you to hide them to remove them..</string>
+    <string name="fragment1menu">Show fragment 1 menu</string>
+    <string name="fragment2menu">Show fragment 2 menu</string>
+    
+    <string name="fragment_retain_instance">App/Fragment/Retain Instance</string>
+    <string name="fragment_retain_instance_msg">Current progress of retained fragment;
+    restarts if fragment is re-created.</string>
+    <string name="restart">Restart</string>
+
+    <string name="fragment_receive_result">App/Fragment/Receive Result</string>
+    
+    <string name="fragment_stack">App/Fragment/Stack</string>
+    <string name="next">Next</string>
+
     <string name="activity_menu">App/Activity/Menu</string>
     <string name="open_menu">Open menu</string>
     <string name="close_menu">Close menu</string>
@@ -458,6 +487,13 @@
     <string name="disable_admin">Disable Admin</string>
     <string name="password_quality">Password Quality</string>
     <string name="password_length_hint">Minimum Length</string>
+    <string name="password_minimum_letters_hint">Minimum Letters</string>
+    <string name="password_minimum_uppercase_hint">Minimum Uppercase</string>
+    <string name="password_minimum_lowercase_hint">Minimum Lowercase</string>
+    <string name="password_minimum_symbols_hint">Minimum Symbols</string>
+    <string name="password_minimum_numeric_hint">Minimum Numeric</string>
+    <string name="password_minimum_nonletter_hint">Minimum Non-Letter</string>
+    <string name="password_history_length_hint">Password History Length</string>
     <string name="set_password">Set Password</string>
     <string name="password_hint">Password</string>
     <string name="reset_password">Reset Password</string>
diff --git a/samples/ApiDemos/src/com/example/android/apis/Shakespeare.java b/samples/ApiDemos/src/com/example/android/apis/Shakespeare.java
new file mode 100644
index 0000000..481df4b
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/Shakespeare.java
@@ -0,0 +1,223 @@
+package com.example.android.apis;
+
+public final class Shakespeare {
+    /**
+     * Our data, part 1.
+     */
+    public static final String[] TITLES = 
+    {
+            "Henry IV (1)",   
+            "Henry V",
+            "Henry VIII",       
+            "Richard II",
+            "Richard III",
+            "Merchant of Venice",  
+            "Othello",
+            "King Lear"
+    };
+    
+    /**
+     * Our data, part 2.
+     */
+    public static final String[] DIALOGUE = 
+    {
+            "So shaken as we are, so wan with care," +
+            "Find we a time for frighted peace to pant," +
+            "And breathe short-winded accents of new broils" +
+            "To be commenced in strands afar remote." +
+            "No more the thirsty entrance of this soil" +
+            "Shall daub her lips with her own children's blood;" +
+            "Nor more shall trenching war channel her fields," +
+            "Nor bruise her flowerets with the armed hoofs" +
+            "Of hostile paces: those opposed eyes," +
+            "Which, like the meteors of a troubled heaven," +
+            "All of one nature, of one substance bred," +
+            "Did lately meet in the intestine shock" +
+            "And furious close of civil butchery" +
+            "Shall now, in mutual well-beseeming ranks," +
+            "March all one way and be no more opposed" +
+            "Against acquaintance, kindred and allies:" +
+            "The edge of war, like an ill-sheathed knife," +
+            "No more shall cut his master. Therefore, friends," +
+            "As far as to the sepulchre of Christ," +
+            "Whose soldier now, under whose blessed cross" +
+            "We are impressed and engaged to fight," +
+            "Forthwith a power of English shall we levy;" +
+            "Whose arms were moulded in their mothers' womb" +
+            "To chase these pagans in those holy fields" +
+            "Over whose acres walk'd those blessed feet" +
+            "Which fourteen hundred years ago were nail'd" +
+            "For our advantage on the bitter cross." +
+            "But this our purpose now is twelve month old," +
+            "And bootless 'tis to tell you we will go:" +
+            "Therefore we meet not now. Then let me hear" +
+            "Of you, my gentle cousin Westmoreland," +
+            "What yesternight our council did decree" +
+            "In forwarding this dear expedience.",
+            
+            "Hear him but reason in divinity," + 
+            "And all-admiring with an inward wish" + 
+            "You would desire the king were made a prelate:" + 
+            "Hear him debate of commonwealth affairs," + 
+            "You would say it hath been all in all his study:" + 
+            "List his discourse of war, and you shall hear" + 
+            "A fearful battle render'd you in music:" + 
+            "Turn him to any cause of policy," + 
+            "The Gordian knot of it he will unloose," + 
+            "Familiar as his garter: that, when he speaks," + 
+            "The air, a charter'd libertine, is still," + 
+            "And the mute wonder lurketh in men's ears," + 
+            "To steal his sweet and honey'd sentences;" + 
+            "So that the art and practic part of life" + 
+            "Must be the mistress to this theoric:" + 
+            "Which is a wonder how his grace should glean it," + 
+            "Since his addiction was to courses vain," + 
+            "His companies unletter'd, rude and shallow," + 
+            "His hours fill'd up with riots, banquets, sports," + 
+            "And never noted in him any study," + 
+            "Any retirement, any sequestration" + 
+            "From open haunts and popularity.",
+
+            "I come no more to make you laugh: things now," +
+            "That bear a weighty and a serious brow," +
+            "Sad, high, and working, full of state and woe," +
+            "Such noble scenes as draw the eye to flow," +
+            "We now present. Those that can pity, here" +
+            "May, if they think it well, let fall a tear;" +
+            "The subject will deserve it. Such as give" +
+            "Their money out of hope they may believe," +
+            "May here find truth too. Those that come to see" +
+            "Only a show or two, and so agree" +
+            "The play may pass, if they be still and willing," +
+            "I'll undertake may see away their shilling" +
+            "Richly in two short hours. Only they" +
+            "That come to hear a merry bawdy play," +
+            "A noise of targets, or to see a fellow" +
+            "In a long motley coat guarded with yellow," +
+            "Will be deceived; for, gentle hearers, know," +
+            "To rank our chosen truth with such a show" +
+            "As fool and fight is, beside forfeiting" +
+            "Our own brains, and the opinion that we bring," +
+            "To make that only true we now intend," +
+            "Will leave us never an understanding friend." +
+            "Therefore, for goodness' sake, and as you are known" +
+            "The first and happiest hearers of the town," +
+            "Be sad, as we would make ye: think ye see" +
+            "The very persons of our noble story" +
+            "As they were living; think you see them great," +
+            "And follow'd with the general throng and sweat" +
+            "Of thousand friends; then in a moment, see" +
+            "How soon this mightiness meets misery:" +
+            "And, if you can be merry then, I'll say" +
+            "A man may weep upon his wedding-day.",
+            
+            "First, heaven be the record to my speech!" + 
+            "In the devotion of a subject's love," + 
+            "Tendering the precious safety of my prince," + 
+            "And free from other misbegotten hate," + 
+            "Come I appellant to this princely presence." + 
+            "Now, Thomas Mowbray, do I turn to thee," + 
+            "And mark my greeting well; for what I speak" + 
+            "My body shall make good upon this earth," + 
+            "Or my divine soul answer it in heaven." + 
+            "Thou art a traitor and a miscreant," + 
+            "Too good to be so and too bad to live," + 
+            "Since the more fair and crystal is the sky," + 
+            "The uglier seem the clouds that in it fly." + 
+            "Once more, the more to aggravate the note," + 
+            "With a foul traitor's name stuff I thy throat;" + 
+            "And wish, so please my sovereign, ere I move," + 
+            "What my tongue speaks my right drawn sword may prove.",
+            
+            "Now is the winter of our discontent" + 
+            "Made glorious summer by this sun of York;" + 
+            "And all the clouds that lour'd upon our house" + 
+            "In the deep bosom of the ocean buried." + 
+            "Now are our brows bound with victorious wreaths;" + 
+            "Our bruised arms hung up for monuments;" + 
+            "Our stern alarums changed to merry meetings," + 
+            "Our dreadful marches to delightful measures." + 
+            "Grim-visaged war hath smooth'd his wrinkled front;" + 
+            "And now, instead of mounting barded steeds" + 
+            "To fright the souls of fearful adversaries," + 
+            "He capers nimbly in a lady's chamber" + 
+            "To the lascivious pleasing of a lute." + 
+            "But I, that am not shaped for sportive tricks," + 
+            "Nor made to court an amorous looking-glass;" + 
+            "I, that am rudely stamp'd, and want love's majesty" + 
+            "To strut before a wanton ambling nymph;" + 
+            "I, that am curtail'd of this fair proportion," + 
+            "Cheated of feature by dissembling nature," + 
+            "Deformed, unfinish'd, sent before my time" + 
+            "Into this breathing world, scarce half made up," + 
+            "And that so lamely and unfashionable" + 
+            "That dogs bark at me as I halt by them;" + 
+            "Why, I, in this weak piping time of peace," + 
+            "Have no delight to pass away the time," + 
+            "Unless to spy my shadow in the sun" + 
+            "And descant on mine own deformity:" + 
+            "And therefore, since I cannot prove a lover," + 
+            "To entertain these fair well-spoken days," + 
+            "I am determined to prove a villain" + 
+            "And hate the idle pleasures of these days." + 
+            "Plots have I laid, inductions dangerous," + 
+            "By drunken prophecies, libels and dreams," + 
+            "To set my brother Clarence and the king" + 
+            "In deadly hate the one against the other:" + 
+            "And if King Edward be as true and just" + 
+            "As I am subtle, false and treacherous," + 
+            "This day should Clarence closely be mew'd up," + 
+            "About a prophecy, which says that 'G'" + 
+            "Of Edward's heirs the murderer shall be." + 
+            "Dive, thoughts, down to my soul: here" + 
+            "Clarence comes.",
+            
+            "To bait fish withal: if it will feed nothing else," + 
+            "it will feed my revenge. He hath disgraced me, and" + 
+            "hindered me half a million; laughed at my losses," + 
+            "mocked at my gains, scorned my nation, thwarted my" + 
+            "bargains, cooled my friends, heated mine" + 
+            "enemies; and what's his reason? I am a Jew. Hath" + 
+            "not a Jew eyes? hath not a Jew hands, organs," + 
+            "dimensions, senses, affections, passions? fed with" + 
+            "the same food, hurt with the same weapons, subject" + 
+            "to the same diseases, healed by the same means," + 
+            "warmed and cooled by the same winter and summer, as" + 
+            "a Christian is? If you prick us, do we not bleed?" + 
+            "if you tickle us, do we not laugh? if you poison" + 
+            "us, do we not die? and if you wrong us, shall we not" + 
+            "revenge? If we are like you in the rest, we will" + 
+            "resemble you in that. If a Jew wrong a Christian," + 
+            "what is his humility? Revenge. If a Christian" + 
+            "wrong a Jew, what should his sufferance be by" + 
+            "Christian example? Why, revenge. The villany you" + 
+            "teach me, I will execute, and it shall go hard but I" + 
+            "will better the instruction.",
+            
+            "Virtue! a fig! 'tis in ourselves that we are thus" + 
+            "or thus. Our bodies are our gardens, to the which" + 
+            "our wills are gardeners: so that if we will plant" + 
+            "nettles, or sow lettuce, set hyssop and weed up" + 
+            "thyme, supply it with one gender of herbs, or" + 
+            "distract it with many, either to have it sterile" + 
+            "with idleness, or manured with industry, why, the" + 
+            "power and corrigible authority of this lies in our" + 
+            "wills. If the balance of our lives had not one" + 
+            "scale of reason to poise another of sensuality, the" + 
+            "blood and baseness of our natures would conduct us" + 
+            "to most preposterous conclusions: but we have" + 
+            "reason to cool our raging motions, our carnal" + 
+            "stings, our unbitted lusts, whereof I take this that" + 
+            "you call love to be a sect or scion.",
+
+            "Blow, winds, and crack your cheeks! rage! blow!" + 
+            "You cataracts and hurricanoes, spout" + 
+            "Till you have drench'd our steeples, drown'd the cocks!" + 
+            "You sulphurous and thought-executing fires," + 
+            "Vaunt-couriers to oak-cleaving thunderbolts," + 
+            "Singe my white head! And thou, all-shaking thunder," + 
+            "Smite flat the thick rotundity o' the world!" + 
+            "Crack nature's moulds, an germens spill at once," + 
+            "That make ingrateful man!"
+    };
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
index f7ee2d7..a282172 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
@@ -54,6 +54,13 @@
 
     static String PREF_PASSWORD_QUALITY = "password_quality";
     static String PREF_PASSWORD_LENGTH = "password_length";
+    static String PREF_PASSWORD_MINIMUM_LETTERS = "password_minimum_letters";
+    static String PREF_PASSWORD_MINIMUM_UPPERCASE = "password_minimum_uppercase";
+    static String PREF_PASSWORD_MINIMUM_LOWERCASE = "password_minimum_lowercase";
+    static String PREF_PASSWORD_MINIMUM_NUMERIC = "password_minimum_numeric";
+    static String PREF_PASSWORD_MINIMUM_SYMBOLS = "password_minimum_symbols";
+    static String PREF_PASSWORD_MINIMUM_NONLETTER = "password_minimum_nonletter";
+    static String PREF_PASSWORD_HISTORY_LENGTH = "password_history_length";
     static String PREF_MAX_FAILED_PW = "max_failed_pw";
 
     void showToast(Context context, CharSequence msg) {
@@ -115,10 +122,18 @@
             DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
             DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
             DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
-            DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+            DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
+            DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
         };
         Spinner mPasswordQuality;
         EditText mPasswordLength;
+        EditText mPasswordMinimumLetters;
+        EditText mPasswordMinimumUppercase;
+        EditText mPasswordMinimumLowercase;
+        EditText mPasswordMinimumNumeric;
+        EditText mPasswordMinimumSymbols;
+        EditText mPasswordMinimumNonLetter;
+        EditText mPasswordHistoryLength;
         Button mSetPasswordButton;
 
         EditText mPassword;
@@ -178,6 +193,97 @@
                     }
                 }
             });
+            mPasswordMinimumLetters = (EditText)findViewById(R.id.password_minimum_letters);
+            mPasswordMinimumLetters.addTextChangedListener(new TextWatcher() {
+                public void afterTextChanged(Editable s) {
+                }
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    try {
+                        setPasswordMinimumLetters(Integer.parseInt(s.toString()));
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            });
+            mPasswordMinimumUppercase = (EditText)findViewById(R.id.password_minimum_uppercase);
+            mPasswordMinimumUppercase.addTextChangedListener(new TextWatcher() {
+                public void afterTextChanged(Editable s) {
+                }
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    try {
+                        setPasswordMinimumUppercase(Integer.parseInt(s.toString()));
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            });
+            mPasswordMinimumLowercase = (EditText)findViewById(R.id.password_minimum_lowercase);
+            mPasswordMinimumLowercase.addTextChangedListener(new TextWatcher() {
+                public void afterTextChanged(Editable s) {
+                }
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    try {
+                        setPasswordMinimumLowercase(Integer.parseInt(s.toString()));
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            });
+            mPasswordMinimumNumeric = (EditText)findViewById(R.id.password_minimum_numeric);
+            mPasswordMinimumNumeric.addTextChangedListener(new TextWatcher() {
+                public void afterTextChanged(Editable s) {
+                }
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    try {
+                        setPasswordMinimumNumeric(Integer.parseInt(s.toString()));
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            });
+            mPasswordMinimumSymbols = (EditText)findViewById(R.id.password_minimum_symbols);
+            mPasswordMinimumSymbols.addTextChangedListener(new TextWatcher() {
+                public void afterTextChanged(Editable s) {
+                }
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    try {
+                        setPasswordMinimumSymbols(Integer.parseInt(s.toString()));
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            });
+            mPasswordMinimumNonLetter = (EditText)findViewById(R.id.password_minimum_nonletter);
+            mPasswordMinimumNonLetter.addTextChangedListener(new TextWatcher() {
+                public void afterTextChanged(Editable s) {
+                }
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    try {
+                        setPasswordMinimumNonLetter(Integer.parseInt(s.toString()));
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            });
+            mPasswordHistoryLength = (EditText)findViewById(R.id.password_history_length);
+            mPasswordHistoryLength.addTextChangedListener(new TextWatcher() {
+                public void afterTextChanged(Editable s) {
+                }
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
+                    try {
+                        setPasswordHistoryLength(Integer.parseInt(s.toString()));
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            });
             mSetPasswordButton = (Button)findViewById(R.id.set_password);
             mSetPasswordButton.setOnClickListener(mSetPasswordListener);
 
@@ -221,6 +327,13 @@
                 mDisableButton.setEnabled(true);
                 mPasswordQuality.setEnabled(true);
                 mPasswordLength.setEnabled(true);
+                mPasswordMinimumLetters.setEnabled(true);
+                mPasswordMinimumUppercase.setEnabled(true);
+                mPasswordMinimumLowercase.setEnabled(true);
+                mPasswordMinimumSymbols.setEnabled(true);
+                mPasswordMinimumNumeric.setEnabled(true);
+                mPasswordMinimumNonLetter.setEnabled(true);
+                mPasswordHistoryLength.setEnabled(true);
                 mSetPasswordButton.setEnabled(true);
                 mPassword.setEnabled(true);
                 mResetPasswordButton.setEnabled(true);
@@ -231,6 +344,13 @@
                 mDisableButton.setEnabled(false);
                 mPasswordQuality.setEnabled(false);
                 mPasswordLength.setEnabled(false);
+                mPasswordMinimumLetters.setEnabled(false);
+                mPasswordMinimumUppercase.setEnabled(false);
+                mPasswordMinimumLowercase.setEnabled(false);
+                mPasswordMinimumSymbols.setEnabled(false);
+                mPasswordMinimumNumeric.setEnabled(false);
+                mPasswordMinimumNonLetter.setEnabled(false);
+                mPasswordHistoryLength.setEnabled(false);
                 mSetPasswordButton.setEnabled(false);
                 mPassword.setEnabled(false);
                 mResetPasswordButton.setEnabled(false);
@@ -244,6 +364,13 @@
             final int pwQuality = prefs.getInt(PREF_PASSWORD_QUALITY,
                     DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
             final int pwLength = prefs.getInt(PREF_PASSWORD_LENGTH, 0);
+            final int pwMinLetters = prefs.getInt(PREF_PASSWORD_MINIMUM_LETTERS, 0);
+            final int pwMinUppercase = prefs.getInt(PREF_PASSWORD_MINIMUM_UPPERCASE, 0);
+            final int pwMinLowercase = prefs.getInt(PREF_PASSWORD_MINIMUM_LOWERCASE, 0);
+            final int pwMinNumeric = prefs.getInt(PREF_PASSWORD_MINIMUM_NUMERIC, 0);
+            final int pwMinSymbols = prefs.getInt(PREF_PASSWORD_MINIMUM_SYMBOLS, 0);
+            final int pwMinNonLetter = prefs.getInt(PREF_PASSWORD_MINIMUM_NONLETTER, 0);
+            final int pwHistoryLength = prefs.getInt(PREF_PASSWORD_HISTORY_LENGTH, 0);
             final int maxFailedPw = prefs.getInt(PREF_MAX_FAILED_PW, 0);
 
             for (int i=0; i<mPasswordQualityValues.length; i++) {
@@ -252,6 +379,13 @@
                 }
             }
             mPasswordLength.setText(Integer.toString(pwLength));
+            mPasswordMinimumLetters.setText(Integer.toString(pwMinLetters));
+            mPasswordMinimumUppercase.setText(Integer.toString(pwMinUppercase));
+            mPasswordMinimumLowercase.setText(Integer.toString(pwMinLowercase));
+            mPasswordMinimumSymbols.setText(Integer.toString(pwMinSymbols));
+            mPasswordMinimumNumeric.setText(Integer.toString(pwMinNumeric));
+            mPasswordMinimumNonLetter.setText(Integer.toString(pwMinNonLetter));
+            mPasswordHistoryLength.setText(Integer.toString(pwHistoryLength));
             mMaxFailedPw.setText(Integer.toString(maxFailedPw));
         }
 
@@ -260,12 +394,26 @@
             final int pwQuality = prefs.getInt(PREF_PASSWORD_QUALITY,
                     DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
             final int pwLength = prefs.getInt(PREF_PASSWORD_LENGTH, 0);
+            final int pwMinLetters = prefs.getInt(PREF_PASSWORD_MINIMUM_LETTERS, 0);
+            final int pwMinUppercase = prefs.getInt(PREF_PASSWORD_MINIMUM_UPPERCASE, 0);
+            final int pwMinLowercase = prefs.getInt(PREF_PASSWORD_MINIMUM_LOWERCASE, 0);
+            final int pwMinNumeric = prefs.getInt(PREF_PASSWORD_MINIMUM_NUMERIC, 0);
+            final int pwMinSymbols = prefs.getInt(PREF_PASSWORD_MINIMUM_SYMBOLS, 0);
+            final int pwMinNonLetter = prefs.getInt(PREF_PASSWORD_MINIMUM_NONLETTER, 0);
+            final int pwHistoryLength = prefs.getInt(PREF_PASSWORD_HISTORY_LENGTH, 0);
             final int maxFailedPw = prefs.getInt(PREF_MAX_FAILED_PW, 0);
 
             boolean active = mDPM.isAdminActive(mDeviceAdminSample);
             if (active) {
                 mDPM.setPasswordQuality(mDeviceAdminSample, pwQuality);
                 mDPM.setPasswordMinimumLength(mDeviceAdminSample, pwLength);
+                mDPM.setPasswordMinimumLetters(mDeviceAdminSample, pwMinLetters);
+                mDPM.setPasswordMinimumUpperCase(mDeviceAdminSample, pwMinUppercase);
+                mDPM.setPasswordMinimumLowerCase(mDeviceAdminSample, pwMinLowercase);
+                mDPM.setPasswordMinimumNumeric(mDeviceAdminSample, pwMinNumeric);
+                mDPM.setPasswordMinimumSymbols(mDeviceAdminSample, pwMinSymbols);
+                mDPM.setPasswordMinimumNonLetter(mDeviceAdminSample, pwMinNonLetter);
+                mDPM.setPasswordHistoryLength(mDeviceAdminSample, pwHistoryLength);
                 mDPM.setMaximumFailedPasswordsForWipe(mDeviceAdminSample, maxFailedPw);
             }
         }
@@ -282,6 +430,48 @@
             updatePolicies();
         }
 
+        void setPasswordMinimumLetters(int length) {
+            SharedPreferences prefs = getSamplePreferences(this);
+            prefs.edit().putInt(PREF_PASSWORD_MINIMUM_LETTERS, length).commit();
+            updatePolicies();
+        }
+
+        void setPasswordMinimumUppercase(int length) {
+            SharedPreferences prefs = getSamplePreferences(this);
+            prefs.edit().putInt(PREF_PASSWORD_MINIMUM_UPPERCASE, length).commit();
+            updatePolicies();
+        }
+
+        void setPasswordMinimumLowercase(int length) {
+            SharedPreferences prefs = getSamplePreferences(this);
+            prefs.edit().putInt(PREF_PASSWORD_MINIMUM_LOWERCASE, length).commit();
+            updatePolicies();
+        }
+
+        void setPasswordMinimumNumeric(int length) {
+            SharedPreferences prefs = getSamplePreferences(this);
+            prefs.edit().putInt(PREF_PASSWORD_MINIMUM_NUMERIC, length).commit();
+            updatePolicies();
+        }
+
+        void setPasswordMinimumSymbols(int length) {
+            SharedPreferences prefs = getSamplePreferences(this);
+            prefs.edit().putInt(PREF_PASSWORD_MINIMUM_SYMBOLS, length).commit();
+            updatePolicies();
+        }
+
+        void setPasswordMinimumNonLetter(int length) {
+            SharedPreferences prefs = getSamplePreferences(this);
+            prefs.edit().putInt(PREF_PASSWORD_MINIMUM_NONLETTER, length).commit();
+            updatePolicies();
+        }
+
+        void setPasswordHistoryLength(int length) {
+            SharedPreferences prefs = getSamplePreferences(this);
+            prefs.edit().putInt(PREF_PASSWORD_HISTORY_LENGTH, length).commit();
+            updatePolicies();
+        }
+
         void setMaxFailedPw(int length) {
             SharedPreferences prefs = getSamplePreferences(this);
             prefs.edit().putInt(PREF_MAX_FAILED_PW, length).commit();
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentAnim.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentAnim.java
new file mode 100644
index 0000000..227a2fb
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentAnim.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Demonstration of animations when changing fragment states.
+ */
+public class FragmentAnim extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fragment_anim);
+        
+        addShowHideListener(R.id.frag1hide, findFragmentById(R.id.fragment1));
+        addShowHideListener(R.id.frag2hide, findFragmentById(R.id.fragment2));
+    }
+    
+    void addShowHideListener(int buttonId, final Fragment fragment) {
+        final Button button = (Button)findViewById(buttonId);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                FragmentTransaction ft = openFragmentTransaction();
+                ft.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
+                if (fragment.isHidden()) {
+                    button.setAnimation(AnimationUtils.loadAnimation(
+                            FragmentAnim.this, android.R.anim.slide_in_left));
+                    ft.show(fragment);
+                } else {
+                    button.setAnimation(AnimationUtils.loadAnimation(
+                            FragmentAnim.this, android.R.anim.slide_out_right));
+                    ft.hide(fragment);
+                }
+                ft.commit();
+            }
+        });
+    }
+    
+    public static class FirstFragment extends Fragment {
+        TextView mTextView;
+        
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            View v = inflater.inflate(R.layout.labeled_text_edit, container, false);
+            View tv = v.findViewById(R.id.msg);
+            ((TextView)tv).setText("The fragment saves and restores this text.");
+            
+            // Retrieve the text editor, and restore the last saved state if needed.
+            mTextView = (TextView)v.findViewById(R.id.saved);
+            if (savedInstanceState != null) {
+                mTextView.setText(savedInstanceState.getCharSequence("text"));
+            }
+            return v;
+        }
+
+        @Override
+        public void onSaveInstanceState(Bundle outState) {
+            super.onSaveInstanceState(outState);
+            
+            // Remember the current text, to restore if we later restart.
+            outState.putCharSequence("text", mTextView.getText());
+        }
+    }
+    
+    public static class SecondFragment extends Fragment {
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            View v = inflater.inflate(R.layout.labeled_text_edit, container, false);
+            View tv = v.findViewById(R.id.msg);
+            ((TextView)tv).setText("The TextView saves and restores this text.");
+            
+            // Retrieve the text editor and tell it to save and restore its state.
+            // Note that you will often set this in the layout XML, but since
+            // we are sharing our layout with the other fragment we will customize
+            // it here.
+            ((TextView)v.findViewById(R.id.saved)).setSaveEnabled(true);
+            return v;
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java
new file mode 100644
index 0000000..3587dba
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+
+/**
+ * Demonstration of displaying a context menu from a fragment.
+ */
+public class FragmentContextMenu extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        // Create the list fragment and add it as our sole content.
+        ContextMenuFragment content = new ContextMenuFragment();
+        openFragmentTransaction().add(android.R.id.content, content).commit();
+    }
+    
+    public static class ContextMenuFragment extends Fragment {
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            View root = inflater.inflate(R.layout.fragment_context_menu, container, false);
+            registerForContextMenu(root.findViewById(R.id.long_press));
+            return root;
+        }
+
+        @Override
+        public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+            super.onCreateContextMenu(menu, v, menuInfo);
+            menu.add(Menu.NONE, R.id.a_item, Menu.NONE, "Menu A");
+            menu.add(Menu.NONE, R.id.b_item, Menu.NONE, "Menu B");
+        }
+        
+        @Override
+        public boolean onContextItemSelected(MenuItem item) {
+            switch (item.getItemId()) {
+                case R.id.a_item:
+                    Log.i("ContextMenu", "Item 1a was chosen");
+                    return true;
+                case R.id.b_item:
+                    Log.i("ContextMenu", "Item 1b was chosen");
+                    return true;
+            }
+            return super.onContextItemSelected(item);
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
new file mode 100644
index 0000000..c802310
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+import com.example.android.apis.Shakespeare;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.view.animation.AnimationUtils;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+/**
+ * Demonstration of using fragments to implement different activity layouts.
+ * This sample provides a different layout (and activity flow) when run in
+ * landscape.
+ */
+public class FragmentLayout extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        // This layout varies depending on the screen size.
+        setContentView(R.layout.fragment_layout);
+    }
+    
+    /**
+     * This is a secondary activity, to show what the user has selected
+     * when the screen is not large enough to show it all in one activity.
+     */
+    public static class DialogActivity extends Activity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            
+            if (getResources().getConfiguration().orientation
+                    == Configuration.ORIENTATION_LANDSCAPE) {
+                // If the screen is now in landscape mode, we can show the
+                // dialog in-line with the list so we don't need this activity.
+                finish();
+                return;
+            }
+            
+            DialogFragment dialog = new DialogFragment();
+            openFragmentTransaction().add(android.R.id.content, dialog).commit();
+            dialog.setText(getIntent().getIntExtra("text", -1));
+        }
+    }
+    
+    public static class TitlesFragment extends Fragment
+            implements AdapterView.OnItemClickListener {
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            ListView list = new ListView(getActivity());
+            list.setDrawSelectorOnTop(false);
+            list.setAdapter(new ArrayAdapter<String>(getActivity(),
+                    android.R.layout.simple_list_item_1, Shakespeare.TITLES));
+            list.setOnItemClickListener(this);
+            list.setId(android.R.id.list);  // set id to allow state save/restore.
+            return list;
+        }
+
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            DialogFragment frag = (DialogFragment)getActivity().findFragmentById(R.id.dialog);
+            if (frag != null && frag.isVisible()) {
+                frag.setText((int)id);
+            } else {
+                Intent intent = new Intent();
+                intent.setClass(getActivity(), DialogActivity.class);
+                intent.putExtra("text", (int)id);
+                startActivity(intent);
+            }
+        }
+    }
+    
+    public static class DialogFragment extends Fragment {
+        int mDisplayedText = -1;
+        TextView mText;
+        
+        public void setText(int id) {
+            mDisplayedText = id;
+            if (mText != null && id >= 0) {
+                mText.setText(Shakespeare.DIALOGUE[id]);
+            }
+        }
+        
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState != null) {
+                mDisplayedText = savedInstanceState.getInt("text", -1);
+            }
+        }
+
+        @Override
+        public void onSaveInstanceState(Bundle outState) {
+            super.onSaveInstanceState(outState);
+            outState.putInt("text", mDisplayedText);
+        }
+
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            ScrollView scroller = new ScrollView(getActivity());
+            mText = new TextView(getActivity());
+            scroller.addView(mText);
+            setText(mDisplayedText);
+            return scroller;
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentListArray.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentListArray.java
new file mode 100644
index 0000000..9dad030
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentListArray.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.Shakespeare;
+
+import android.app.Activity;
+import android.app.ListFragment;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+/**
+ * Demonstration of using ListFragment to show a list of items
+ * from a canned array.
+ */
+public class FragmentListArray extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        // Create the list fragment and add it as our sole content.
+        if (findFragmentById(android.R.id.content) == null) {
+            ArrayListFragment list = new ArrayListFragment();
+            openFragmentTransaction().add(android.R.id.content, list).commit();
+        }
+    }
+    
+    public static class ArrayListFragment extends ListFragment {
+        @Override
+        public void onActivityCreated(Bundle savedInstanceState) {
+            super.onActivityCreated(savedInstanceState);
+            setListAdapter(new ArrayAdapter<String>(getActivity(),
+                    android.R.layout.simple_list_item_1, Shakespeare.TITLES));
+        }
+        
+        @Override
+        public void onListItemClick(ListView l, View v, int position, long id) {
+            Log.i("FragmentList", "Item clicked: " + id);
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java
new file mode 100644
index 0000000..bd49676
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.apis.app;
+
+import android.app.Activity;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.Contacts.Phones;
+import android.provider.ContactsContract.Contacts;
+import android.util.Log;
+import android.view.View;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+/**
+ * Demonstration of more complex use if a ListFragment, including showing
+ * an empty view and loading progress.
+ */
+public class FragmentListCursorLoader extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        // Create the list fragment and add it as our sole content.
+        if (findFragmentById(android.R.id.content) == null) {
+            CursorLoaderListFragment list = new CursorLoaderListFragment();
+            openFragmentTransaction().add(android.R.id.content, list).commit();
+        }
+    }
+    
+    public static class CursorLoaderListFragment extends ListFragment
+            implements LoaderManager.LoaderCallbacks<Cursor> {
+        @Override
+        public void onActivityCreated(Bundle savedInstanceState) {
+            super.onActivityCreated(savedInstanceState);
+            
+            // Give some text to display if there is no data.  In a real
+            // application this would come from a resource.
+            setEmptyText("No phone numbers");
+            
+            // Prepare the loader.  Either re-connect with an existing one,
+            // or start a new one.
+            getLoaderManager().initLoader(0, null, this);
+        }
+        
+        @Override
+        public void onListItemClick(ListView l, View v, int position, long id) {
+            Log.i("FragmentComplexList", "Item clicked: " + id);
+        }
+        
+        static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+            Contacts._ID,
+            Contacts.DISPLAY_NAME,
+            Contacts.CONTACT_STATUS,
+            Contacts.CONTACT_PRESENCE,
+            Contacts.PHOTO_ID,
+            Contacts.LOOKUP_KEY,
+        };
+    
+        @Override
+        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+            String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                    + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                    + Contacts.DISPLAY_NAME + " != '' ))";
+            return new CursorLoader(getActivity(), Contacts.CONTENT_URI,
+                    CONTACTS_SUMMARY_PROJECTION, select, null,
+                    Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+            ListAdapter adapter = new SimpleCursorAdapter(getActivity(),
+                    android.R.layout.simple_list_item_2, data, 
+                            new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, 
+                            new int[] { android.R.id.text1, android.R.id.text2 });
+            setListAdapter(adapter);
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenu.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenu.java
new file mode 100644
index 0000000..6116953
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenu.java
@@ -0,0 +1,115 @@
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+/**
+ * Demonstrates how fragments can participate in the options menu.
+ */
+public class FragmentMenu extends Activity {
+    Fragment mFragment1;
+    Fragment mFragment2;
+    CheckBox mCheckBox1;
+    CheckBox mCheckBox2;
+    
+    // Update fragment visibility when check boxes are changed.
+    final OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            updateFragmentVisibility();
+        }
+    };
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fragment_menu);
+        
+        // Make sure the two menu fragments are created.
+        FragmentTransaction ft = openFragmentTransaction();
+        mFragment1 = findFragmentByTag("f1");
+        if (mFragment1 == null) {
+            mFragment1 = new MenuFragment();
+            ft.add(mFragment1, "f1");
+        }
+        mFragment2 = findFragmentByTag("f2");
+        if (mFragment2 == null) {
+            mFragment2 = new Menu2Fragment();
+            ft.add(mFragment2, "f2");
+        }
+        ft.commit();
+        
+        // Watch check box clicks.
+        mCheckBox1 = (CheckBox)findViewById(R.id.menu1);
+        mCheckBox1.setOnClickListener(mClickListener);
+        mCheckBox2 = (CheckBox)findViewById(R.id.menu2);
+        mCheckBox2.setOnClickListener(mClickListener);
+        
+        // Make sure fragments start out with correct visibility.
+        updateFragmentVisibility();
+    }
+    
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        // Make sure fragments are updated after check box view state is restored.
+        updateFragmentVisibility();
+    }
+
+    // Update fragment visibility based on current check box state.
+    void updateFragmentVisibility() {
+        FragmentTransaction ft = openFragmentTransaction();
+        if (mCheckBox1.isChecked()) ft.show(mFragment1);
+        else ft.hide(mFragment1);
+        if (mCheckBox2.isChecked()) ft.show(mFragment2);
+        else ft.hide(mFragment2);
+        ft.commit();
+    }
+    
+    /**
+     * A fragment that displays a menu.  This fragment happens to not
+     * have a UI (it does not implement onCreateView), but it could also
+     * have one if it wanted.
+     */
+    public static class MenuFragment extends Fragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setHasOptionsMenu(true);
+        }
+
+        @Override
+        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+            menu.add("Menu 1a");
+            menu.add("Menu 1b");
+        }
+    }
+
+    /**
+     * Second fragment with a menu.
+     */
+    public static class Menu2Fragment extends Fragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setHasOptionsMenu(true);
+        }
+
+        @Override
+        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+            menu.add("Menu 2");
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentReceiveResult.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentReceiveResult.java
new file mode 100644
index 0000000..a3699b1
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentReceiveResult.java
@@ -0,0 +1,117 @@
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+import com.example.android.apis.app.FragmentStack.CountingFragment;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.Editable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+public class FragmentReceiveResult extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+        FrameLayout frame = new FrameLayout(this);
+        frame.setId(R.id.simple_fragment);
+        setContentView(frame, lp);
+        
+        if (savedInstanceState == null) {
+            // Do first time initialization -- add fragment. 
+            Fragment newFragment = new ReceiveResultFragment();
+            FragmentTransaction ft = openFragmentTransaction();
+            ft.add(R.id.simple_fragment, newFragment).commit();
+        }
+    }
+    
+    public static class ReceiveResultFragment extends Fragment {
+        // Definition of the one requestCode we use for receiving resuls.
+        static final private int GET_CODE = 0;
+        
+        private TextView mResults;
+        
+        private OnClickListener mGetListener = new OnClickListener() {
+            public void onClick(View v) {
+                // Start the activity whose result we want to retrieve.  The
+                // result will come back with request code GET_CODE.
+                Intent intent = new Intent(getActivity(), SendResult.class);
+                startActivityForResult(intent, GET_CODE);
+            }
+        };
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+        }
+
+        @Override
+        public void onSaveInstanceState(Bundle outState) {
+            super.onSaveInstanceState(outState);
+        }
+
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            View v = inflater.inflate(R.layout.receive_result, container, false);
+            
+            // Retrieve the TextView widget that will display results.
+            mResults = (TextView)v.findViewById(R.id.results);
+
+            // This allows us to later extend the text buffer.
+            mResults.setText(mResults.getText(), TextView.BufferType.EDITABLE);
+
+            // Watch for button clicks.
+            Button getButton = (Button)v.findViewById(R.id.get);
+            getButton.setOnClickListener(mGetListener);
+            
+            return v;
+        }
+        
+        /**
+         * This method is called when the sending activity has finished, with the
+         * result it supplied.
+         */
+        @Override
+        public void onActivityResult(int requestCode, int resultCode, Intent data) {
+            // You can use the requestCode to select between multiple child
+            // activities you may have started.  Here there is only one thing
+            // we launch.
+            if (requestCode == GET_CODE) {
+
+                // We will be adding to our text.
+                Editable text = (Editable)mResults.getText();
+
+                // This is a standard resultCode that is sent back if the
+                // activity doesn't supply an explicit result.  It will also
+                // be returned if the activity failed to launch.
+                if (resultCode == RESULT_CANCELED) {
+                    text.append("(cancelled)");
+
+                // Our protocol with the sending activity is that it will send
+                // text in 'data' as its result.
+                } else {
+                    text.append("(okay ");
+                    text.append(Integer.toString(resultCode));
+                    text.append(") ");
+                    if (data != null) {
+                        text.append(data.getAction());
+                    }
+                }
+
+                text.append("\n");
+            }
+        }
+
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentRetainInstance.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentRetainInstance.java
new file mode 100644
index 0000000..070eb47
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentRetainInstance.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * This example shows how you can use a Fragment to easily propagate state
+ * (such as threads) across activity instances when an activity needs to be
+ * restarted due to, for example, a configuration change.  This is a lot
+ * easier than using the raw Activity.onRetainNonConfiguratinInstance() API.
+ */
+public class FragmentRetainInstance extends Activity {
+    RetainedFragment mRetainedFragment;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fragment_retain_instance);
+        
+        // Watch for button clicks.
+        Button button = (Button)findViewById(R.id.restart);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                mRetainedFragment.restart();
+            }
+        });
+        
+        // Check to see if we retained the fragment.
+        mRetainedFragment = (RetainedFragment)findFragmentByTag("retained");
+        
+        // If not retained (or first time running), we need to re-create it.
+        if (mRetainedFragment == null) {
+            mRetainedFragment = new RetainedFragment();
+            openFragmentTransaction().add(mRetainedFragment, "retained").commit();
+        }
+    }
+    
+    /**
+     * This is the Fragment implementation that will be retained across
+     * activity instances.  It represents some ongoing work, here a thread
+     * we have that sits around incrementing a progress indicator.
+     */
+    public static class RetainedFragment extends Fragment {
+        ProgressBar mProgressBar;
+        int mPosition;
+        boolean mReady = false;
+        boolean mQuiting = false;
+        
+        /**
+         * This is the thread that will do our work.  It sits in a loop running
+         * the progress up until it has reached the top, then stops and waits.
+         */
+        final Thread mThread = new Thread() {
+            @Override
+            public void run() {
+                // We'll figure the real value out later.
+                int max = 10000;
+                
+                // This thread runs almost forever.
+                while (true) {
+                    
+                    // Update our shared state with the UI.
+                    synchronized (this) {
+                        // Our thread is stopped if the UI is not ready
+                        // or it has completed its work.
+                        while (!mReady || mPosition >= max) {
+                            if (mQuiting) {
+                                return;
+                            }
+                            try {
+                                wait();
+                            } catch (InterruptedException e) {
+                            }
+                        }
+                        
+                        // Now update the progress.  Note it is important that
+                        // we touch the progress bar with the lock held, so it
+                        // doesn't disappear on us.
+                        mPosition++;
+                        max = mProgressBar.getMax();
+                        mProgressBar.setProgress(mPosition);
+                    }
+                    
+                    // Normally we would be doing some work, but put a kludge
+                    // here to pretend like we are.
+                    synchronized (this) {
+                        try {
+                            wait(50);
+                        } catch (InterruptedException e) {
+                        }
+                    }
+                }
+            }
+        };
+        
+        /**
+         * Fragment initialization.  We way we want to be retained and
+         * start our thread.
+         */
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            
+            // Tell the framework to try to keep this fragment around
+            // during a configuration change.
+            setRetainInstance(true);
+            
+            // Start up the worker thread.
+            mThread.start();
+        }
+
+        /**
+         * This is called when the Fragment's Activity is ready to go, after
+         * its content view has been installed; it is called both after
+         * the initial fragment creation and after the fragment is re-attached
+         * to a new activity.
+         */
+        @Override
+        public void onActivityCreated(Bundle savedInstanceState) {
+            super.onActivityCreated(savedInstanceState);
+            
+            // Retrieve the progress bar from the current activity.
+            mProgressBar = (ProgressBar)getActivity().findViewById(
+                    R.id.progress_horizontal);
+            
+            // We are ready for our thread to go.
+            synchronized (mThread) {
+                mReady = true;
+                mThread.notify();
+            }
+        }
+
+        /**
+         * This is called when the fragment is going away.  It is NOT called
+         * when the fragment is being propagated between activity instances.
+         */
+        @Override
+        public void onDestroy() {
+            // Make the thread go away.
+            synchronized (mThread) {
+                mReady = false;
+                mQuiting = true;
+                mThread.notify();
+            }
+            
+            super.onDestroy();
+        }
+
+        /**
+         * This is called right before the fragment is detached from its
+         * current activity instance.
+         */
+        @Override
+        public void onDetach() {
+            // This fragment is being detached from its activity.  We need
+            // to make sure its thread is not going to touch any activity
+            // state after returning from this function.
+            synchronized (mThread) {
+                mProgressBar = null;
+                mReady = false;
+                mThread.notify();
+            }
+            
+            super.onDetach();
+        }
+        
+        /**
+         * API for our UI to restart the progress thread.
+         */
+        public void restart() {
+            synchronized (mThread) {
+                mPosition = 0;
+                mThread.notify();
+            }
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java
new file mode 100644
index 0000000..c7754b6
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class FragmentStack extends Activity {
+    int mStackLevel = 1;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fragment_stack);
+        
+        // Watch for button clicks.
+        Button button = (Button)findViewById(R.id.next);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                addFragmentToStack();
+            }
+        });
+        
+        if (savedInstanceState == null) {
+            // Do first time initialization -- add initial fragment. 
+            Fragment newFragment = new CountingFragment(mStackLevel);
+            FragmentTransaction ft = openFragmentTransaction();
+            ft.add(R.id.simple_fragment, newFragment).commit();
+        } else {
+            mStackLevel = savedInstanceState.getInt("level");
+        }
+    }
+    
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("level", mStackLevel);
+    }
+
+    void addFragmentToStack() {
+        mStackLevel++;
+        Fragment newFragment = new CountingFragment(mStackLevel);
+        FragmentTransaction ft = openFragmentTransaction();
+        ft.replace(R.id.simple_fragment, newFragment);
+        ft.setTransition(FragmentTransaction.TRANSIT_WALLPAPER_INTRA_OPEN);
+        ft.addToBackStack(null);
+        ft.commit();
+    }
+    
+    public static class CountingFragment extends Fragment {
+        int mNum;
+        
+        public CountingFragment() {
+            mNum = -1;
+        }
+        
+        public CountingFragment(int num) {
+            mNum = num;
+        }
+        
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState != null) {
+                mNum = savedInstanceState.getInt("num");
+            }
+        }
+
+        @Override
+        public void onSaveInstanceState(Bundle outState) {
+            super.onSaveInstanceState(outState);
+            outState.putInt("num", mNum);
+        }
+
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            View v = inflater.inflate(R.layout.hello_world, container, false);
+            View tv = v.findViewById(R.id.text);
+            ((TextView)tv).setText("Fragment #" + mNum);
+            return v;
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/ExpandableList2.java b/samples/ApiDemos/src/com/example/android/apis/view/ExpandableList2.java
index 5784122..8b2cb43 100644
--- a/samples/ApiDemos/src/com/example/android/apis/view/ExpandableList2.java
+++ b/samples/ApiDemos/src/com/example/android/apis/view/ExpandableList2.java
@@ -17,59 +17,68 @@
 package com.example.android.apis.view;
 
 import android.app.ExpandableListActivity;
+import android.content.AsyncQueryHandler;
 import android.content.ContentUris;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.provider.Contacts.People;
-import android.widget.ExpandableListAdapter;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.widget.CursorTreeAdapter;
 import android.widget.SimpleCursorTreeAdapter;
 
-
 /**
  * Demonstrates expandable lists backed by Cursors
  */
 public class ExpandableList2 extends ExpandableListActivity {
-    private int mGroupIdColumnIndex; 
-    
-    private String mPhoneNumberProjection[] = new String[] {
-            People.Phones._ID, People.Phones.NUMBER
+
+    private static final String[] CONTACTS_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME
+    };
+    private static final int GROUP_ID_COLUMN_INDEX = 0;
+
+    private static final String[] PHONE_NUMBER_PROJECTION = new String[] {
+            Phone._ID,
+            Phone.NUMBER
     };
 
-    
-    private ExpandableListAdapter mAdapter;
-    
+    private static final int TOKEN_GROUP = 0;
+    private static final int TOKEN_CHILD = 1;
 
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    private static final class QueryHandler extends AsyncQueryHandler {
+        private CursorTreeAdapter mAdapter;
 
-        // Query for people
-        Cursor groupCursor = managedQuery(People.CONTENT_URI,
-                new String[] {People._ID, People.NAME}, null, null, null);
+        public QueryHandler(Context context, CursorTreeAdapter adapter) {
+            super(context.getContentResolver());
+            this.mAdapter = adapter;
+        }
 
-        // Cache the ID column index
-        mGroupIdColumnIndex = groupCursor.getColumnIndexOrThrow(People._ID);
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            switch (token) {
+            case TOKEN_GROUP:
+                mAdapter.setGroupCursor(cursor);
+                break;
 
-        // Set up our adapter
-        mAdapter = new MyExpandableListAdapter(groupCursor,
-                this,
-                android.R.layout.simple_expandable_list_item_1,
-                android.R.layout.simple_expandable_list_item_1,
-                new String[] {People.NAME}, // Name for group layouts
-                new int[] {android.R.id.text1},
-                new String[] {People.NUMBER}, // Number for child layouts
-                new int[] {android.R.id.text1});
-        setListAdapter(mAdapter);
+            case TOKEN_CHILD:
+                int groupPosition = (Integer) cookie;
+                mAdapter.setChildrenCursor(groupPosition, cursor);
+                break;
+            }
+        }
     }
 
     public class MyExpandableListAdapter extends SimpleCursorTreeAdapter {
 
-        public MyExpandableListAdapter(Cursor cursor, Context context, int groupLayout,
+        // Note that the constructor does not take a Cursor. This is done to avoid querying the 
+        // database on the main thread.
+        public MyExpandableListAdapter(Context context, int groupLayout,
                 int childLayout, String[] groupFrom, int[] groupTo, String[] childrenFrom,
                 int[] childrenTo) {
-            super(context, cursor, groupLayout, groupFrom, groupTo, childLayout, childrenFrom,
+
+            super(context, null, groupLayout, groupFrom, groupTo, childLayout, childrenFrom,
                     childrenTo);
         }
 
@@ -78,15 +87,52 @@
             // Given the group, we return a cursor for all the children within that group 
 
             // Return a cursor that points to this contact's phone numbers
-            Uri.Builder builder = People.CONTENT_URI.buildUpon();
-            ContentUris.appendId(builder, groupCursor.getLong(mGroupIdColumnIndex));
-            builder.appendEncodedPath(People.Phones.CONTENT_DIRECTORY);
+            Uri.Builder builder = Contacts.CONTENT_URI.buildUpon();
+            ContentUris.appendId(builder, groupCursor.getLong(GROUP_ID_COLUMN_INDEX));
+            builder.appendEncodedPath(Contacts.Data.CONTENT_DIRECTORY);
             Uri phoneNumbersUri = builder.build();
 
-            // The returned Cursor MUST be managed by us, so we use Activity's helper
-            // functionality to manage it for us.
-            return managedQuery(phoneNumbersUri, mPhoneNumberProjection, null, null, null);
-        }
+            mQueryHandler.startQuery(TOKEN_CHILD, groupCursor.getPosition(), phoneNumbersUri, 
+                    PHONE_NUMBER_PROJECTION, Phone.MIMETYPE + "=?", 
+                    new String[] { Phone.CONTENT_ITEM_TYPE }, null);
 
+            return null;
+        }
+    }
+
+    private QueryHandler mQueryHandler;
+    private CursorTreeAdapter mAdapter;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Set up our adapter
+        mAdapter = new MyExpandableListAdapter(
+                this,
+                android.R.layout.simple_expandable_list_item_1,
+                android.R.layout.simple_expandable_list_item_1,
+                new String[] { Contacts.DISPLAY_NAME }, // Name for group layouts
+                new int[] { android.R.id.text1 },
+                new String[] { Phone.NUMBER }, // Number for child layouts
+                new int[] { android.R.id.text1 });
+
+        setListAdapter(mAdapter);
+
+        mQueryHandler = new QueryHandler(this, mAdapter);
+
+        // Query for people
+        mQueryHandler.startQuery(TOKEN_GROUP, null, Contacts.CONTENT_URI, CONTACTS_PROJECTION, 
+                Contacts.HAS_PHONE_NUMBER + "=1", null, null);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        // Null out the group cursor. This will cause the group cursor and all of the child cursors
+        // to be closed.
+        mAdapter.changeCursor(null);
+        mAdapter = null;
     }
 }
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/Gallery1.java b/samples/ApiDemos/src/com/example/android/apis/view/Gallery1.java
index 7aaaaef..b252d5a 100644
--- a/samples/ApiDemos/src/com/example/android/apis/view/Gallery1.java
+++ b/samples/ApiDemos/src/com/example/android/apis/view/Gallery1.java
@@ -71,8 +71,25 @@
     }
 
     public class ImageAdapter extends BaseAdapter {
-        int mGalleryItemBackground;
-        
+        private static final int ITEM_WIDTH = 136;
+        private static final int ITEM_HEIGHT = 88;
+
+        private final int mGalleryItemBackground;
+        private final Context mContext;
+
+        private final Integer[] mImageIds = {
+                R.drawable.gallery_photo_1,
+                R.drawable.gallery_photo_2,
+                R.drawable.gallery_photo_3,
+                R.drawable.gallery_photo_4,
+                R.drawable.gallery_photo_5,
+                R.drawable.gallery_photo_6,
+                R.drawable.gallery_photo_7,
+                R.drawable.gallery_photo_8
+        };
+
+        private final float mDensity;
+
         public ImageAdapter(Context c) {
             mContext = c;
             // See res/values/attrs.xml for the <declare-styleable> that defines
@@ -81,6 +98,8 @@
             mGalleryItemBackground = a.getResourceId(
                     R.styleable.Gallery1_android_galleryItemBackground, 0);
             a.recycle();
+
+            mDensity = c.getResources().getDisplayMetrics().density;
         }
 
         public int getCount() {
@@ -96,30 +115,25 @@
         }
 
         public View getView(int position, View convertView, ViewGroup parent) {
-            ImageView i = new ImageView(mContext);
+            ImageView imageView;
+            if (convertView == null) {
+                convertView = new ImageView(mContext);
 
-            i.setImageResource(mImageIds[position]);
-            i.setScaleType(ImageView.ScaleType.FIT_XY);
-            i.setLayoutParams(new Gallery.LayoutParams(136, 88));
+                imageView = (ImageView) convertView;
+                imageView.setScaleType(ImageView.ScaleType.FIT_XY);
+                imageView.setLayoutParams(new Gallery.LayoutParams(
+                        (int) (ITEM_WIDTH * mDensity + 0.5f),
+                        (int) (ITEM_HEIGHT * mDensity + 0.5f)));
             
-            // The preferred Gallery item background
-            i.setBackgroundResource(mGalleryItemBackground);
-            
-            return i;
+                // The preferred Gallery item background
+                imageView.setBackgroundResource(mGalleryItemBackground);
+            } else {
+                imageView = (ImageView) convertView;
+            }
+
+            imageView.setImageResource(mImageIds[position]);
+
+            return imageView;
         }
-
-        private Context mContext;
-
-        private Integer[] mImageIds = {
-                R.drawable.gallery_photo_1,
-                R.drawable.gallery_photo_2,
-                R.drawable.gallery_photo_3,
-                R.drawable.gallery_photo_4,
-                R.drawable.gallery_photo_5,
-                R.drawable.gallery_photo_6,
-                R.drawable.gallery_photo_7,
-                R.drawable.gallery_photo_8
-        };
     }
-
 }
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/List4.java b/samples/ApiDemos/src/com/example/android/apis/view/List4.java
index a140e60..9c18a5d 100644
--- a/samples/ApiDemos/src/com/example/android/apis/view/List4.java
+++ b/samples/ApiDemos/src/com/example/android/apis/view/List4.java
@@ -16,8 +16,8 @@
 
 package com.example.android.apis.view;
 
-//Need the following import to get access to the app resources, since this
-//class is in a sub-package.
+import com.example.android.apis.Shakespeare;
+
 import android.app.ListActivity;
 import android.content.Context;
 import android.os.Bundle;
@@ -59,7 +59,7 @@
          * @see android.widget.ListAdapter#getCount()
          */
         public int getCount() {
-            return mTitles.length;
+            return Shakespeare.TITLES.length;
         }
 
         /**
@@ -92,12 +92,12 @@
         public View getView(int position, View convertView, ViewGroup parent) {
             SpeechView sv;
             if (convertView == null) {
-                sv = new SpeechView(mContext, mTitles[position],
-                        mDialogue[position]);
+                sv = new SpeechView(mContext, Shakespeare.TITLES[position],
+                        Shakespeare.DIALOGUE[position]);
             } else {
                 sv = (SpeechView) convertView;
-                sv.setTitle(mTitles[position]);
-                sv.setDialogue(mDialogue[position]);
+                sv.setTitle(Shakespeare.TITLES[position]);
+                sv.setDialogue(Shakespeare.DIALOGUE[position]);
             }
 
             return sv;
@@ -107,226 +107,6 @@
          * Remember our context so we can use it when constructing views.
          */
         private Context mContext;
-        
-        /**
-         * Our data, part 1.
-         */
-        private String[] mTitles = 
-        {
-                "Henry IV (1)",   
-                "Henry V",
-                "Henry VIII",       
-                "Richard II",
-                "Richard III",
-                "Merchant of Venice",  
-                "Othello",
-                "King Lear"
-        };
-        
-        /**
-         * Our data, part 2.
-         */
-        private String[] mDialogue = 
-        {
-                "So shaken as we are, so wan with care," +
-                "Find we a time for frighted peace to pant," +
-                "And breathe short-winded accents of new broils" +
-                "To be commenced in strands afar remote." +
-                "No more the thirsty entrance of this soil" +
-                "Shall daub her lips with her own children's blood;" +
-                "Nor more shall trenching war channel her fields," +
-                "Nor bruise her flowerets with the armed hoofs" +
-                "Of hostile paces: those opposed eyes," +
-                "Which, like the meteors of a troubled heaven," +
-                "All of one nature, of one substance bred," +
-                "Did lately meet in the intestine shock" +
-                "And furious close of civil butchery" +
-                "Shall now, in mutual well-beseeming ranks," +
-                "March all one way and be no more opposed" +
-                "Against acquaintance, kindred and allies:" +
-                "The edge of war, like an ill-sheathed knife," +
-                "No more shall cut his master. Therefore, friends," +
-                "As far as to the sepulchre of Christ," +
-                "Whose soldier now, under whose blessed cross" +
-                "We are impressed and engaged to fight," +
-                "Forthwith a power of English shall we levy;" +
-                "Whose arms were moulded in their mothers' womb" +
-                "To chase these pagans in those holy fields" +
-                "Over whose acres walk'd those blessed feet" +
-                "Which fourteen hundred years ago were nail'd" +
-                "For our advantage on the bitter cross." +
-                "But this our purpose now is twelve month old," +
-                "And bootless 'tis to tell you we will go:" +
-                "Therefore we meet not now. Then let me hear" +
-                "Of you, my gentle cousin Westmoreland," +
-                "What yesternight our council did decree" +
-                "In forwarding this dear expedience.",
-                
-                "Hear him but reason in divinity," + 
-                "And all-admiring with an inward wish" + 
-                "You would desire the king were made a prelate:" + 
-                "Hear him debate of commonwealth affairs," + 
-                "You would say it hath been all in all his study:" + 
-                "List his discourse of war, and you shall hear" + 
-                "A fearful battle render'd you in music:" + 
-                "Turn him to any cause of policy," + 
-                "The Gordian knot of it he will unloose," + 
-                "Familiar as his garter: that, when he speaks," + 
-                "The air, a charter'd libertine, is still," + 
-                "And the mute wonder lurketh in men's ears," + 
-                "To steal his sweet and honey'd sentences;" + 
-                "So that the art and practic part of life" + 
-                "Must be the mistress to this theoric:" + 
-                "Which is a wonder how his grace should glean it," + 
-                "Since his addiction was to courses vain," + 
-                "His companies unletter'd, rude and shallow," + 
-                "His hours fill'd up with riots, banquets, sports," + 
-                "And never noted in him any study," + 
-                "Any retirement, any sequestration" + 
-                "From open haunts and popularity.",
-
-                "I come no more to make you laugh: things now," +
-                "That bear a weighty and a serious brow," +
-                "Sad, high, and working, full of state and woe," +
-                "Such noble scenes as draw the eye to flow," +
-                "We now present. Those that can pity, here" +
-                "May, if they think it well, let fall a tear;" +
-                "The subject will deserve it. Such as give" +
-                "Their money out of hope they may believe," +
-                "May here find truth too. Those that come to see" +
-                "Only a show or two, and so agree" +
-                "The play may pass, if they be still and willing," +
-                "I'll undertake may see away their shilling" +
-                "Richly in two short hours. Only they" +
-                "That come to hear a merry bawdy play," +
-                "A noise of targets, or to see a fellow" +
-                "In a long motley coat guarded with yellow," +
-                "Will be deceived; for, gentle hearers, know," +
-                "To rank our chosen truth with such a show" +
-                "As fool and fight is, beside forfeiting" +
-                "Our own brains, and the opinion that we bring," +
-                "To make that only true we now intend," +
-                "Will leave us never an understanding friend." +
-                "Therefore, for goodness' sake, and as you are known" +
-                "The first and happiest hearers of the town," +
-                "Be sad, as we would make ye: think ye see" +
-                "The very persons of our noble story" +
-                "As they were living; think you see them great," +
-                "And follow'd with the general throng and sweat" +
-                "Of thousand friends; then in a moment, see" +
-                "How soon this mightiness meets misery:" +
-                "And, if you can be merry then, I'll say" +
-                "A man may weep upon his wedding-day.",
-                
-                "First, heaven be the record to my speech!" + 
-                "In the devotion of a subject's love," + 
-                "Tendering the precious safety of my prince," + 
-                "And free from other misbegotten hate," + 
-                "Come I appellant to this princely presence." + 
-                "Now, Thomas Mowbray, do I turn to thee," + 
-                "And mark my greeting well; for what I speak" + 
-                "My body shall make good upon this earth," + 
-                "Or my divine soul answer it in heaven." + 
-                "Thou art a traitor and a miscreant," + 
-                "Too good to be so and too bad to live," + 
-                "Since the more fair and crystal is the sky," + 
-                "The uglier seem the clouds that in it fly." + 
-                "Once more, the more to aggravate the note," + 
-                "With a foul traitor's name stuff I thy throat;" + 
-                "And wish, so please my sovereign, ere I move," + 
-                "What my tongue speaks my right drawn sword may prove.",
-                
-                "Now is the winter of our discontent" + 
-                "Made glorious summer by this sun of York;" + 
-                "And all the clouds that lour'd upon our house" + 
-                "In the deep bosom of the ocean buried." + 
-                "Now are our brows bound with victorious wreaths;" + 
-                "Our bruised arms hung up for monuments;" + 
-                "Our stern alarums changed to merry meetings," + 
-                "Our dreadful marches to delightful measures." + 
-                "Grim-visaged war hath smooth'd his wrinkled front;" + 
-                "And now, instead of mounting barded steeds" + 
-                "To fright the souls of fearful adversaries," + 
-                "He capers nimbly in a lady's chamber" + 
-                "To the lascivious pleasing of a lute." + 
-                "But I, that am not shaped for sportive tricks," + 
-                "Nor made to court an amorous looking-glass;" + 
-                "I, that am rudely stamp'd, and want love's majesty" + 
-                "To strut before a wanton ambling nymph;" + 
-                "I, that am curtail'd of this fair proportion," + 
-                "Cheated of feature by dissembling nature," + 
-                "Deformed, unfinish'd, sent before my time" + 
-                "Into this breathing world, scarce half made up," + 
-                "And that so lamely and unfashionable" + 
-                "That dogs bark at me as I halt by them;" + 
-                "Why, I, in this weak piping time of peace," + 
-                "Have no delight to pass away the time," + 
-                "Unless to spy my shadow in the sun" + 
-                "And descant on mine own deformity:" + 
-                "And therefore, since I cannot prove a lover," + 
-                "To entertain these fair well-spoken days," + 
-                "I am determined to prove a villain" + 
-                "And hate the idle pleasures of these days." + 
-                "Plots have I laid, inductions dangerous," + 
-                "By drunken prophecies, libels and dreams," + 
-                "To set my brother Clarence and the king" + 
-                "In deadly hate the one against the other:" + 
-                "And if King Edward be as true and just" + 
-                "As I am subtle, false and treacherous," + 
-                "This day should Clarence closely be mew'd up," + 
-                "About a prophecy, which says that 'G'" + 
-                "Of Edward's heirs the murderer shall be." + 
-                "Dive, thoughts, down to my soul: here" + 
-                "Clarence comes.",
-                
-                "To bait fish withal: if it will feed nothing else," + 
-                "it will feed my revenge. He hath disgraced me, and" + 
-                "hindered me half a million; laughed at my losses," + 
-                "mocked at my gains, scorned my nation, thwarted my" + 
-                "bargains, cooled my friends, heated mine" + 
-                "enemies; and what's his reason? I am a Jew. Hath" + 
-                "not a Jew eyes? hath not a Jew hands, organs," + 
-                "dimensions, senses, affections, passions? fed with" + 
-                "the same food, hurt with the same weapons, subject" + 
-                "to the same diseases, healed by the same means," + 
-                "warmed and cooled by the same winter and summer, as" + 
-                "a Christian is? If you prick us, do we not bleed?" + 
-                "if you tickle us, do we not laugh? if you poison" + 
-                "us, do we not die? and if you wrong us, shall we not" + 
-                "revenge? If we are like you in the rest, we will" + 
-                "resemble you in that. If a Jew wrong a Christian," + 
-                "what is his humility? Revenge. If a Christian" + 
-                "wrong a Jew, what should his sufferance be by" + 
-                "Christian example? Why, revenge. The villany you" + 
-                "teach me, I will execute, and it shall go hard but I" + 
-                "will better the instruction.",
-                
-                "Virtue! a fig! 'tis in ourselves that we are thus" + 
-                "or thus. Our bodies are our gardens, to the which" + 
-                "our wills are gardeners: so that if we will plant" + 
-                "nettles, or sow lettuce, set hyssop and weed up" + 
-                "thyme, supply it with one gender of herbs, or" + 
-                "distract it with many, either to have it sterile" + 
-                "with idleness, or manured with industry, why, the" + 
-                "power and corrigible authority of this lies in our" + 
-                "wills. If the balance of our lives had not one" + 
-                "scale of reason to poise another of sensuality, the" + 
-                "blood and baseness of our natures would conduct us" + 
-                "to most preposterous conclusions: but we have" + 
-                "reason to cool our raging motions, our carnal" + 
-                "stings, our unbitted lusts, whereof I take this that" + 
-                "you call love to be a sect or scion.",
-
-                "Blow, winds, and crack your cheeks! rage! blow!" + 
-                "You cataracts and hurricanoes, spout" + 
-                "Till you have drench'd our steeples, drown'd the cocks!" + 
-                "You sulphurous and thought-executing fires," + 
-                "Vaunt-couriers to oak-cleaving thunderbolts," + 
-                "Singe my white head! And thou, all-shaking thunder," + 
-                "Smite flat the thick rotundity o' the world!" + 
-                "Crack nature's moulds, an germens spill at once," + 
-                "That make ingrateful man!"
-        };
     }
     
     /**
diff --git a/samples/BrowserPlugin/jni/PluginObject.h b/samples/BrowserPlugin/jni/PluginObject.h
index 0ebed28..296749a 100644
--- a/samples/BrowserPlugin/jni/PluginObject.h
+++ b/samples/BrowserPlugin/jni/PluginObject.h
@@ -48,7 +48,7 @@
 public:
     SubPlugin(NPP inst) : m_inst(inst) {}
     virtual ~SubPlugin() {}
-    virtual int16 handleEvent(const ANPEvent* evt) = 0;
+    virtual int16_t handleEvent(const ANPEvent* evt) = 0;
     virtual bool supportsDrawingModel(ANPDrawingModel) = 0;
 
     int getPluginWidth();
diff --git a/samples/BrowserPlugin/jni/animation/AnimationPlugin.cpp b/samples/BrowserPlugin/jni/animation/AnimationPlugin.cpp
index 72a11c9..6e93fb6 100644
--- a/samples/BrowserPlugin/jni/animation/AnimationPlugin.cpp
+++ b/samples/BrowserPlugin/jni/animation/AnimationPlugin.cpp
@@ -35,12 +35,12 @@
 extern ANPPathInterfaceV0      gPathI;
 extern ANPWindowInterfaceV0    gWindowI;
 
-static uint16 rnd16(float x, int inset) {
+static uint16_t rnd16(float x, int inset) {
     int ix = (int)roundf(x) + inset;
     if (ix < 0) {
         ix = 0;
     }
-    return static_cast<uint16>(ix);
+    return static_cast<uint16_t>(ix);
 }
 
 static void inval(NPP instance, const ANPRectF& r, bool doAA) {
@@ -173,7 +173,7 @@
     gWindowI.clearVisibleRects(instance);
 }
 
-int16 BallAnimation::handleEvent(const ANPEvent* evt) {
+int16_t BallAnimation::handleEvent(const ANPEvent* evt) {
     NPP instance = this->inst();
 
     switch (evt->eventType) {
diff --git a/samples/BrowserPlugin/jni/animation/AnimationPlugin.h b/samples/BrowserPlugin/jni/animation/AnimationPlugin.h
index ef2a3f5..4a5b4e8 100644
--- a/samples/BrowserPlugin/jni/animation/AnimationPlugin.h
+++ b/samples/BrowserPlugin/jni/animation/AnimationPlugin.h
@@ -33,7 +33,7 @@
     BallAnimation(NPP inst);
     virtual ~BallAnimation();
     virtual bool supportsDrawingModel(ANPDrawingModel);
-    virtual int16 handleEvent(const ANPEvent* evt);
+    virtual int16_t handleEvent(const ANPEvent* evt);
 private:
     void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
     void showEntirePluginOnScreen();
diff --git a/samples/BrowserPlugin/jni/audio/AudioPlugin.cpp b/samples/BrowserPlugin/jni/audio/AudioPlugin.cpp
index 9731f19..8defef4 100644
--- a/samples/BrowserPlugin/jni/audio/AudioPlugin.cpp
+++ b/samples/BrowserPlugin/jni/audio/AudioPlugin.cpp
@@ -44,12 +44,12 @@
     browser->invalidaterect(instance, NULL);
 }
 
-static uint16 rnd16(float x, int inset) {
+static uint16_t rnd16(float x, int inset) {
     int ix = (int)roundf(x) + inset;
     if (ix < 0) {
         ix = 0;
     }
-    return static_cast<uint16>(ix);
+    return static_cast<uint16_t>(ix);
 }
 
 static void inval(NPP instance, const ANPRectF& r, bool doAA) {
@@ -272,7 +272,7 @@
     return (input == m_activeRect) ? m_paintActiveRect : m_paintRect;
 }
 
-int16 AudioPlugin::handleEvent(const ANPEvent* evt) {
+int16_t AudioPlugin::handleEvent(const ANPEvent* evt) {
     NPP instance = this->inst();
 
     switch (evt->eventType) {
diff --git a/samples/BrowserPlugin/jni/audio/AudioPlugin.h b/samples/BrowserPlugin/jni/audio/AudioPlugin.h
index 129d33a..0f88a92 100644
--- a/samples/BrowserPlugin/jni/audio/AudioPlugin.h
+++ b/samples/BrowserPlugin/jni/audio/AudioPlugin.h
@@ -42,7 +42,7 @@
     AudioPlugin(NPP inst);
     virtual ~AudioPlugin();
     virtual bool supportsDrawingModel(ANPDrawingModel);
-    virtual int16 handleEvent(const ANPEvent* evt);
+    virtual int16_t handleEvent(const ANPEvent* evt);
 private:
     void draw(ANPCanvas*);
     void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
diff --git a/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp b/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
index f749639..515acbe 100644
--- a/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
+++ b/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
@@ -179,7 +179,7 @@
     gSurfaceI.unlock(env, m_surface);
 }
 
-int16 BackgroundPlugin::handleEvent(const ANPEvent* evt) {
+int16_t BackgroundPlugin::handleEvent(const ANPEvent* evt) {
     switch (evt->eventType) {
         case kDraw_ANPEventType:
             gLogI.log(kError_ANPLogType, " ------ %p the plugin did not request draw events", inst());
@@ -233,10 +233,10 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 #define TIMER_INTERVAL     50
-static void timer_oneshot(NPP instance, uint32 timerID);
-static void timer_repeat(NPP instance, uint32 timerID);
-static void timer_neverfires(NPP instance, uint32 timerID);
-static void timer_latency(NPP instance, uint32 timerID);
+static void timer_oneshot(NPP instance, uint32_t timerID);
+static void timer_repeat(NPP instance, uint32_t timerID);
+static void timer_neverfires(NPP instance, uint32_t timerID);
+static void timer_latency(NPP instance, uint32_t timerID);
 
 void BackgroundPlugin::test_timers() {
     NPP instance = this->inst();
@@ -255,18 +255,18 @@
     browser->scheduletimer(instance, TIMER_INTERVAL, true, timer_latency);
     mStartTime = mPrevTime = getMSecs();
     // test unschedule immediately
-    uint32 id = browser->scheduletimer(instance, 100, false, timer_neverfires);
+    uint32_t id = browser->scheduletimer(instance, 100, false, timer_neverfires);
     browser->unscheduletimer(instance, id);
     // test double unschedule (should be no-op)
     browser->unscheduletimer(instance, id);
 
 }
 
-static void timer_oneshot(NPP instance, uint32 timerID) {
+static void timer_oneshot(NPP instance, uint32_t timerID) {
     gLogI.log(kDebug_ANPLogType, "-------- oneshot timer\n");
 }
 
-static void timer_repeat(NPP instance, uint32 timerID) {
+static void timer_repeat(NPP instance, uint32_t timerID) {
     BackgroundPlugin *obj = ((BackgroundPlugin*) ((PluginObject*) instance->pdata)->activePlugin);
 
     gLogI.log(kDebug_ANPLogType, "-------- repeat timer %d\n",
@@ -276,11 +276,11 @@
     }
 }
 
-static void timer_neverfires(NPP instance, uint32 timerID) {
+static void timer_neverfires(NPP instance, uint32_t timerID) {
     gLogI.log(kError_ANPLogType, "-------- timer_neverfires!!!\n");
 }
 
-static void timer_latency(NPP instance, uint32 timerID) {
+static void timer_latency(NPP instance, uint32_t timerID) {
     BackgroundPlugin *obj = ((BackgroundPlugin*) ((PluginObject*) instance->pdata)->activePlugin);
 
     obj->mTimerLatencyCurrentCount += 1;
diff --git a/samples/BrowserPlugin/jni/background/BackgroundPlugin.h b/samples/BrowserPlugin/jni/background/BackgroundPlugin.h
index ebd77d1..e0b0597 100644
--- a/samples/BrowserPlugin/jni/background/BackgroundPlugin.h
+++ b/samples/BrowserPlugin/jni/background/BackgroundPlugin.h
@@ -32,7 +32,7 @@
 public:
     BackgroundPlugin(NPP inst);
     virtual ~BackgroundPlugin();
-    virtual int16 handleEvent(const ANPEvent* evt);
+    virtual int16_t handleEvent(const ANPEvent* evt);
     virtual jobject getSurface();
 
     // Timer Testing Variables
diff --git a/samples/BrowserPlugin/jni/form/FormPlugin.cpp b/samples/BrowserPlugin/jni/form/FormPlugin.cpp
index 5a536d9..110afaa 100644
--- a/samples/BrowserPlugin/jni/form/FormPlugin.cpp
+++ b/samples/BrowserPlugin/jni/form/FormPlugin.cpp
@@ -43,12 +43,12 @@
     browser->invalidaterect(instance, NULL);
 }
 
-static uint16 rnd16(float x, int inset) {
+static uint16_t rnd16(float x, int inset) {
     int ix = (int)roundf(x) + inset;
     if (ix < 0) {
         ix = 0;
     }
-    return static_cast<uint16>(ix);
+    return static_cast<uint16_t>(ix);
 }
 
 static void inval(NPP instance, const ANPRectF& r, bool doAA) {
@@ -199,7 +199,7 @@
     }
 }
 
-int16 FormPlugin::handleEvent(const ANPEvent* evt) {
+int16_t FormPlugin::handleEvent(const ANPEvent* evt) {
     NPP instance = this->inst();
 
     switch (evt->eventType) {
diff --git a/samples/BrowserPlugin/jni/form/FormPlugin.h b/samples/BrowserPlugin/jni/form/FormPlugin.h
index 041ffb8..9f985a3 100644
--- a/samples/BrowserPlugin/jni/form/FormPlugin.h
+++ b/samples/BrowserPlugin/jni/form/FormPlugin.h
@@ -39,7 +39,7 @@
     FormPlugin(NPP inst);
     virtual ~FormPlugin();
     virtual bool supportsDrawingModel(ANPDrawingModel);
-    virtual int16 handleEvent(const ANPEvent* evt);
+    virtual int16_t handleEvent(const ANPEvent* evt);
 private:
     void draw(ANPCanvas*);
     void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
diff --git a/samples/BrowserPlugin/jni/main.cpp b/samples/BrowserPlugin/jni/main.cpp
index 3e8fee7..07bae66 100644
--- a/samples/BrowserPlugin/jni/main.cpp
+++ b/samples/BrowserPlugin/jni/main.cpp
@@ -41,19 +41,19 @@
 
 #define EXPORT __attribute__((visibility("default")))
 
-NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc,
+NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc,
         char* argn[], char* argv[], NPSavedData* saved);
 NPError NPP_Destroy(NPP instance, NPSavedData** save);
 NPError NPP_SetWindow(NPP instance, NPWindow* window);
 NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream,
-        NPBool seekable, uint16* stype);
+        NPBool seekable, uint16_t* stype);
 NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason);
-int32   NPP_WriteReady(NPP instance, NPStream* stream);
-int32   NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len,
+int32_t   NPP_WriteReady(NPP instance, NPStream* stream);
+int32_t   NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len,
         void* buffer);
 void    NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname);
 void    NPP_Print(NPP instance, NPPrint* platformPrint);
-int16   NPP_HandleEvent(NPP instance, void* event);
+int16_t   NPP_HandleEvent(NPP instance, void* event);
 void    NPP_URLNotify(NPP instance, const char* URL, NPReason reason,
         void* notifyData);
 NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value);
@@ -151,7 +151,7 @@
     return "application/x-testbrowserplugin:tst:Test plugin mimetype is application/x-testbrowserplugin";
 }
 
-NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc,
+NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc,
                 char* argn[], char* argv[], NPSavedData* saved)
 {
 
@@ -290,7 +290,7 @@
     return NPERR_NO_ERROR;
 }
 
-NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype)
+NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)
 {
     *stype = NP_ASFILEONLY;
     return NPERR_NO_ERROR;
@@ -301,12 +301,12 @@
     return NPERR_NO_ERROR;
 }
 
-int32 NPP_WriteReady(NPP instance, NPStream* stream)
+int32_t NPP_WriteReady(NPP instance, NPStream* stream)
 {
     return 0;
 }
 
-int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer)
+int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer)
 {
     return 0;
 }
@@ -319,7 +319,7 @@
 {
 }
 
-int16 NPP_HandleEvent(NPP instance, void* event)
+int16_t NPP_HandleEvent(NPP instance, void* event)
 {
     PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata);
     const ANPEvent* evt = reinterpret_cast<const ANPEvent*>(event);
diff --git a/samples/BrowserPlugin/jni/navigation/NavigationPlugin.cpp b/samples/BrowserPlugin/jni/navigation/NavigationPlugin.cpp
index 99667a4..01d9a79 100644
--- a/samples/BrowserPlugin/jni/navigation/NavigationPlugin.cpp
+++ b/samples/BrowserPlugin/jni/navigation/NavigationPlugin.cpp
@@ -43,12 +43,12 @@
     browser->invalidaterect(instance, NULL);
 }
 
-static uint16 rnd16(float x, int inset) {
+static uint16_t rnd16(float x, int inset) {
     int ix = (int)roundf(x) + inset;
     if (ix < 0) {
         ix = 0;
     }
-    return static_cast<uint16>(ix);
+    return static_cast<uint16_t>(ix);
 }
 
 static void inval(NPP instance, const ANPRectF& r, bool doAA) {
@@ -163,7 +163,7 @@
     return (input == m_activeNav) ? m_paintActive : m_paintDisabled;
 }
 
-int16 NavigationPlugin::handleEvent(const ANPEvent* evt) {
+int16_t NavigationPlugin::handleEvent(const ANPEvent* evt) {
     NPP instance = this->inst();
 
     switch (evt->eventType) {
diff --git a/samples/BrowserPlugin/jni/navigation/NavigationPlugin.h b/samples/BrowserPlugin/jni/navigation/NavigationPlugin.h
index ca12ae7..c2044bb 100644
--- a/samples/BrowserPlugin/jni/navigation/NavigationPlugin.h
+++ b/samples/BrowserPlugin/jni/navigation/NavigationPlugin.h
@@ -33,7 +33,7 @@
 	NavigationPlugin(NPP inst);
     virtual ~NavigationPlugin();
     virtual bool supportsDrawingModel(ANPDrawingModel);
-    virtual int16 handleEvent(const ANPEvent* evt);
+    virtual int16_t handleEvent(const ANPEvent* evt);
 private:
     void draw(ANPCanvas*);
     void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
diff --git a/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp b/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp
index 71b9f24..abe6805 100644
--- a/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp
+++ b/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp
@@ -273,7 +273,7 @@
     }
 }
 
-int16 PaintPlugin::handleEvent(const ANPEvent* evt) {
+int16_t PaintPlugin::handleEvent(const ANPEvent* evt) {
     switch (evt->eventType) {
         case kTouch_ANPEventType: {
             float x = (float) evt->data.touch.x;
diff --git a/samples/BrowserPlugin/jni/paint/PaintPlugin.h b/samples/BrowserPlugin/jni/paint/PaintPlugin.h
index 035e51b..ffdf624 100644
--- a/samples/BrowserPlugin/jni/paint/PaintPlugin.h
+++ b/samples/BrowserPlugin/jni/paint/PaintPlugin.h
@@ -33,7 +33,7 @@
 public:
     PaintPlugin(NPP inst);
     virtual ~PaintPlugin();
-    virtual int16 handleEvent(const ANPEvent* evt);
+    virtual int16_t handleEvent(const ANPEvent* evt);
     virtual jobject getSurface();
 
 private:
diff --git a/samples/BrowserPlugin/jni/video/VideoPlugin.cpp b/samples/BrowserPlugin/jni/video/VideoPlugin.cpp
index f24295b..bf3ab76 100644
--- a/samples/BrowserPlugin/jni/video/VideoPlugin.cpp
+++ b/samples/BrowserPlugin/jni/video/VideoPlugin.cpp
@@ -103,7 +103,7 @@
     }
 }
 
-int16 VideoPlugin::handleEvent(const ANPEvent* evt) {
+int16_t VideoPlugin::handleEvent(const ANPEvent* evt) {
     switch (evt->eventType) {
         case kLifecycle_ANPEventType: {
             switch (evt->data.lifecycle.action) {
diff --git a/samples/BrowserPlugin/jni/video/VideoPlugin.h b/samples/BrowserPlugin/jni/video/VideoPlugin.h
index 701e2d0..fb15386 100644
--- a/samples/BrowserPlugin/jni/video/VideoPlugin.h
+++ b/samples/BrowserPlugin/jni/video/VideoPlugin.h
@@ -32,7 +32,7 @@
 public:
     VideoPlugin(NPP inst);
     virtual ~VideoPlugin();
-    virtual int16 handleEvent(const ANPEvent* evt);
+    virtual int16_t handleEvent(const ANPEvent* evt);
     virtual jobject getSurface();
 
 private:
diff --git a/samples/XmlAdapters/Android.mk b/samples/XmlAdapters/Android.mk
new file mode 100644
index 0000000..24a3327
--- /dev/null
+++ b/samples/XmlAdapters/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := XmlAdaptersSample
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/XmlAdapters/AndroidManifest.xml b/samples/XmlAdapters/AndroidManifest.xml
new file mode 100644
index 0000000..e4ac4d8
--- /dev/null
+++ b/samples/XmlAdapters/AndroidManifest.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.xmladapters">
+    
+    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <application android:label="@string/app_name">
+        <activity android:name="ContactsListActivity"
+                  android:label="@string/contacts_list_activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="PhotosListActivity"
+                  android:label="@string/photos_list_activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="RssReaderActivity"
+                  android:label="@string/rss_reader_activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <provider android:name="android.content.XmlDocumentProvider"
+           android:authorities="xmldocument" />
+
+    </application>
+
+</manifest>
+
diff --git a/samples/XmlAdapters/_index.html b/samples/XmlAdapters/_index.html
new file mode 100644
index 0000000..2ca3608
--- /dev/null
+++ b/samples/XmlAdapters/_index.html
@@ -0,0 +1,31 @@
+<p>This sample demonstrates the use of XML adapters.</p>
+
+<p>An XML Adapter is an XML file which defines the bindings between the data
+retrieved from a
+<a href="../../../reference/android/content/ContentProvider.html"><code>ContentProvider</code></a>
+and the different views of a layout. They are provided by the
+<a href="../../../reference/android/widget/Adapters.html"><code>Adapters</code></a>
+class.</p>
+
+Three list activities are provided which illustrate this:
+<ul>
+  <li><a href="src/com/example/android/xmladapters/ContactsListActivity.html"><strong>
+  ContactsListActivity</strong></a> uses the device's Contacts provider as its input source.
+  Contacts with a phone number are displayed, their photo being retrieved by a dedicated
+  <a href="src/com/example/android/xmladapters/ContactPhotoBinder.html"><code>ContactPhotoBinder
+  </code></a>.</li>
+
+  <li><a href="src/com/example/android/xmladapters/PhotosListActivity.html"><strong>
+  PhotosListActivity</strong></a> retrieves an RSS photo feed and displays the images and their
+  titles. The images are downloaded from the URL found in the feed using the
+  <a href="src/com/example/android/xmladapters/ImageDownloader.html"><code>ImageDownloader</code>
+  </a> helper class.</li>
+
+  <li><a href="src/com/example/android/xmladapters/RssReaderActivity.html"><strong>
+  RssReaderActivity</strong></a> also displays items extracted from an RSS feed. An additional
+  <a href="src/com/example/android/xmladapters/UrlIntentListener.html"><code>UrlIntentListener
+  </code></a> is used to open a browser when one of the news item is tapped.</li>
+</ul>
+
+<img alt="XmlPhotosAdapter" src="../images/XmlPhotosAdapter.png" />
+<img alt="XmlRssReader" src="../images/XmlRssReader.png" />
diff --git a/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png b/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png
new file mode 100644
index 0000000..a60565a
--- /dev/null
+++ b/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png
Binary files differ
diff --git a/samples/XmlAdapters/res/layout/contact_item.xml b/samples/XmlAdapters/res/layout/contact_item.xml
new file mode 100644
index 0000000..6fcb109
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/contact_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeight">
+
+    <TextView
+        android:id="@+id/name"
+        android:layout_width="0px"
+        android:layout_weight="1.0"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:gravity="center_vertical"
+        android:drawablePadding="6dip"
+        android:paddingLeft="6dip"
+        android:paddingRight="6dip" />
+
+    <ImageView
+        android:id="@+id/star"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />    
+
+</LinearLayout>
diff --git a/samples/XmlAdapters/res/layout/contacts_list.xml b/samples/XmlAdapters/res/layout/contacts_list.xml
new file mode 100644
index 0000000..973fe4b
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/contacts_list.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+ 
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ListView
+        android:id="@android:id/list"
+        android:adapter="@xml/contacts"
+        android:layout_width="match_parent" 
+        android:layout_height="match_parent" />
+
+    <TextView android:id="@android:id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@string/no_contacts"
+        android:visibility="gone" />
+
+</merge>    
diff --git a/samples/XmlAdapters/res/layout/photo_item.xml b/samples/XmlAdapters/res/layout/photo_item.xml
new file mode 100644
index 0000000..f24b143
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/photo_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeight">
+
+    <ImageView
+        android:id="@+id/photo"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="6dip"
+        android:paddingTop="4dip"
+        android:paddingBottom="4dip" />
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="0px"
+        android:layout_weight="1.0"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:gravity="center_vertical"
+        android:paddingLeft="6dip"
+        android:paddingRight="6dip" />
+
+</LinearLayout>
diff --git a/samples/XmlAdapters/res/layout/photos_list.xml b/samples/XmlAdapters/res/layout/photos_list.xml
new file mode 100644
index 0000000..5756f37
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/photos_list.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ListView
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <TextView android:id="@android:id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@string/no_photos"
+        android:visibility="gone" />
+
+</merge>
diff --git a/samples/XmlAdapters/res/layout/rss_feed_item.xml b/samples/XmlAdapters/res/layout/rss_feed_item.xml
new file mode 100644
index 0000000..3975aec
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/rss_feed_item.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/item_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:minHeight="?android:attr/listPreferredItemHeight">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="fill_parent"
+        android:layout_weight="1.0"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:gravity="left"
+        android:paddingLeft="6dip"
+        android:paddingRight="6dip" />
+
+    <TextView
+        android:id="@+id/date"
+        android:layout_width="fill_parent"
+        android:layout_weight="1.0"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:gravity="left"
+        android:paddingLeft="6dip"
+        android:paddingRight="6dip" />
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:minHeight="?android:attr/listPreferredItemHeight">
+
+       <ImageView
+           android:id="@+id/image"
+           android:layout_width="wrap_content"
+           android:layout_height="match_parent"
+           android:gravity="center_vertical"
+           android:paddingLeft="6dip" />
+
+       <TextView
+           android:id="@+id/description"
+           android:layout_width="fill_parent"
+           android:layout_weight="1.0"
+           android:layout_height="wrap_content"
+           android:textAppearance="?android:attr/textAppearanceSmall"
+           android:gravity="left"
+           android:paddingLeft="6dip"
+           android:paddingRight="6dip" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/samples/XmlAdapters/res/layout/rss_feeds_list.xml b/samples/XmlAdapters/res/layout/rss_feeds_list.xml
new file mode 100644
index 0000000..b761dcf
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/rss_feeds_list.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ListView
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <TextView android:id="@android:id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@string/no_rss_feed"
+        android:visibility="gone" />
+
+</merge>
diff --git a/samples/XmlAdapters/res/values/strings.xml b/samples/XmlAdapters/res/values/strings.xml
new file mode 100644
index 0000000..1ae158e
--- /dev/null
+++ b/samples/XmlAdapters/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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" BASI
+     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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">Xml Adapters</string>
+    <string name="contacts_list_activity">Xml Contacts Adapter</string>
+    <string name="photos_list_activity">Xml Photos Adapter</string>
+    <string name="rss_reader_activity">Xml RSS Reader</string>
+    <string name="no_contacts">No contacts available</string>
+    <string name="no_photos">Loading photos...</string>
+    <string name="no_rss_feed">Loading RSS feed...</string>
+
+</resources>
diff --git a/samples/XmlAdapters/res/xml/contacts.xml b/samples/XmlAdapters/res/xml/contacts.xml
new file mode 100644
index 0000000..02cfbca
--- /dev/null
+++ b/samples/XmlAdapters/res/xml/contacts.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+  android:uri="content://com.android.contacts/contacts"
+  android:selection="has_phone_number=1"
+  android:layout="@layout/contact_item">
+
+  <bind android:from="display_name" android:to="@id/name" android:as="string" />
+    <bind android:from="starred" android:to="@id/star" android:as="drawable">
+        <map android:fromValue="0" android:toValue="@android:drawable/star_big_off" />
+        <map android:fromValue="1" android:toValue="@android:drawable/star_big_on" />
+    </bind>
+    <bind android:from="_id" android:to="@id/name"
+          android:as="com.example.android.xmladapters.ContactPhotoBinder" />
+
+</cursor-adapter>
diff --git a/samples/XmlAdapters/res/xml/photos.xml b/samples/XmlAdapters/res/xml/photos.xml
new file mode 100644
index 0000000..a62e62e
--- /dev/null
+++ b/samples/XmlAdapters/res/xml/photos.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+    android:selection="/feed/entry"
+    android:layout="@layout/photo_item">
+
+    <bind android:from="/summary" android:to="@id/title" android:as="string" />
+    <bind android:from="/media:group/media:thumbnail\@url" android:to="@id/photo"
+      android:as="com.example.android.xmladapters.UrlImageBinder" />
+
+</cursor-adapter>
diff --git a/samples/XmlAdapters/res/xml/rss_feed.xml b/samples/XmlAdapters/res/xml/rss_feed.xml
new file mode 100644
index 0000000..a5f09f1
--- /dev/null
+++ b/samples/XmlAdapters/res/xml/rss_feed.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     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.
+-->
+
+<cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+    android:selection="/rss/channel/item"
+    android:layout="@layout/rss_feed_item">
+
+    <bind android:from="/title" android:to="@id/title" android:as="string" />
+    <bind android:from="/media:content@url" android:to="@id/image"
+      android:as="com.example.android.xmladapters.UrlImageBinder"/>
+    <bind android:from="/media:description" android:to="@id/description" android:as="string" />
+    <bind android:from="/guid" android:to="@id/item_layout" android:as="tag" />
+    <bind android:from="/pubDate" android:to="@id/date" android:as="string">
+       <transform android:withExpression="Published on {/pubDate}." />
+    </bind>
+
+</cursor-adapter>
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java
new file mode 100644
index 0000000..a5d556f
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.xmladapters;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.view.View;
+import android.widget.Adapters;
+import android.widget.TextView;
+
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * This custom cursor binder is used by the adapter defined in res/xml to
+ * bind contacts photos to their respective list item. This binder simply
+ * queries a contact's photo based on the contact's id and sets the
+ * photo as a compound drawable on the TextView used to display the contact's
+ * name.
+ */
+public class ContactPhotoBinder extends Adapters.CursorBinder {
+    private static final int PHOTO_SIZE_DIP = 54;
+    
+    private final Drawable mDefault;
+    private final HashMap<Long, Drawable> mCache;
+    private final Resources mResources;
+    private final int mPhotoSize;
+
+    public ContactPhotoBinder(Context context, Adapters.CursorTransformation transformation) {
+        super(context, transformation);
+
+        mResources = mContext.getResources();
+        // Default picture used when a contact does not provide one
+        mDefault = mResources.getDrawable(R.drawable.ic_contact_picture);
+        // Cache used to avoid re-querying contacts photos every time
+        mCache = new HashMap<Long, Drawable>();
+        // Compute the size of the photo based on the display's density
+        mPhotoSize = (int) (PHOTO_SIZE_DIP * mResources.getDisplayMetrics().density + 0.5f);
+    }
+
+    @Override
+    public boolean bind(View view, Cursor cursor, int columnIndex) {
+        final long id = cursor.getLong(columnIndex);
+        
+        // First check whether we have already cached the contact's photo
+        Drawable d = mCache.get(id);
+        
+        if (d == null) {
+            // If the photo wasn't in the cache, ask the contacts provider for
+            // an input stream we can use to load the photo
+            Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
+            InputStream stream = ContactsContract.Contacts.openContactPhotoInputStream(
+                    mContext.getContentResolver(), uri);
+    
+            // Creates the drawable for the contact's photo or use our fallback drawable
+            if (stream != null) {
+                // decoding the bitmap could be done in a worker thread too.
+                Bitmap bitmap = BitmapFactory.decodeStream(stream);
+                d = new BitmapDrawable(mResources, bitmap);
+            } else {
+                d = mDefault;
+            }
+
+            d.setBounds(0, 0, mPhotoSize, mPhotoSize);
+            ((TextView) view).setCompoundDrawables(d, null, null, null);
+
+            // Remember the photo associated with this contact
+            mCache.put(id, d);
+        } else {
+            ((TextView) view).setCompoundDrawables(d, null, null, null);
+        }
+
+        return true;
+    }
+}
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java
new file mode 100644
index 0000000..bf5ab58
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.xmladapters;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+
+/**
+ * This activity demonstrates how to create a complex UI using a ListView
+ * and an adapter defined in XML.
+ * 
+ * The following activity shows a list of contacts, their starred status
+ * and their photos, using the adapter defined in res/xml.
+ */
+public class ContactsListActivity extends ListActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.contacts_list);
+    }
+}
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/ImageDownloader.java b/samples/XmlAdapters/src/com/example/android/xmladapters/ImageDownloader.java
new file mode 100644
index 0000000..c84f9d5
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/ImageDownloader.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.xmladapters;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpGet;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.http.AndroidHttpClient;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.ImageView;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This helper class download images from the Internet and binds those with the provided ImageView.
+ *
+ * <p>It requires the INTERNET permission, which should be added to your application's manifest
+ * file.</p>
+ *
+ * A local cache of downloaded images is maintained internally to improve performance.
+ */
+public class ImageDownloader {
+    private static final String LOG_TAG = "ImageDownloader";
+
+    private static final int HARD_CACHE_CAPACITY = 40;
+    private static final int DELAY_BEFORE_PURGE = 30 * 1000; // in milliseconds
+
+    // Hard cache, with a fixed maximum capacity and a life duration
+    private final HashMap<String, Bitmap> sHardBitmapCache =
+        new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
+        @Override
+        protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {
+            if (size() > HARD_CACHE_CAPACITY) {
+                // Entries push-out of hard reference cache are transferred to soft reference cache
+                sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
+                return true;
+            } else
+                return false;
+        }
+    };
+
+    // Soft cache for bitmap kicked out of hard cache
+    private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache =
+        new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);
+
+    private final Handler purgeHandler = new Handler();
+
+    private final Runnable purger = new Runnable() {
+        public void run() {
+            clearCache();
+        }
+    };
+
+    /**
+     * Download the specified image from the Internet and binds it to the provided ImageView. The
+     * binding is immediate if the image is found in the cache and will be done asynchronously
+     * otherwise. A null bitmap will be associated to the ImageView if an error occurs.
+     *
+     * @param url The URL of the image to download.
+     * @param imageView The ImageView to bind the downloaded image to.
+     */
+    public void download(String url, ImageView imageView) {
+        download(url, imageView, null);
+    }
+
+    /**
+     * Same as {@link #download(String, ImageView)}, with the possibility to provide an additional
+     * cookie that will be used when the image will be retrieved.
+     *
+     * @param url The URL of the image to download.
+     * @param imageView The ImageView to bind the downloaded image to.
+     * @param cookie A cookie String that will be used by the http connection.
+     */
+    public void download(String url, ImageView imageView, String cookie) {
+        resetPurgeTimer();
+        Bitmap bitmap = getBitmapFromCache(url);
+
+        if (bitmap == null) {
+            forceDownload(url, imageView, cookie);
+        } else {
+            cancelPotentialDownload(url, imageView);
+            imageView.setImageBitmap(bitmap);
+        }
+    }
+
+    /*
+     * Same as download but the image is always downloaded and the cache is not used.
+     * Kept private at the moment as its interest is not clear.
+       private void forceDownload(String url, ImageView view) {
+          forceDownload(url, view, null);
+       }
+     */
+
+    /**
+     * Same as download but the image is always downloaded and the cache is not used.
+     * Kept private at the moment as its interest is not clear.
+     */
+    private void forceDownload(String url, ImageView imageView, String cookie) {
+        // State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.
+        if (url == null) {
+            imageView.setImageDrawable(null);
+            return;
+        }
+
+        if (cancelPotentialDownload(url, imageView)) {
+            BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
+            DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
+            imageView.setImageDrawable(downloadedDrawable);
+            task.execute(url, cookie);
+        }
+    }
+
+    /**
+     * Clears the image cache used internally to improve performance. Note that for memory
+     * efficiency reasons, the cache will automatically be cleared after a certain inactivity delay.
+     */
+    public void clearCache() {
+        sHardBitmapCache.clear();
+        sSoftBitmapCache.clear();
+    }
+
+    private void resetPurgeTimer() {
+        purgeHandler.removeCallbacks(purger);
+        purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
+    }
+
+    /**
+     * Returns true if the current download has been canceled or if there was no download in
+     * progress on this image view.
+     * Returns false if the download in progress deals with the same url. The download is not
+     * stopped in that case.
+     */
+    private static boolean cancelPotentialDownload(String url, ImageView imageView) {
+        BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
+
+        if (bitmapDownloaderTask != null) {
+            String bitmapUrl = bitmapDownloaderTask.url;
+            if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
+                bitmapDownloaderTask.cancel(true);
+            } else {
+                // The same URL is already being downloaded.
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @param imageView Any imageView
+     * @return Retrieve the currently active download task (if any) associated with this imageView.
+     * null if there is no such task.
+     */
+    private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
+        if (imageView != null) {
+            Drawable drawable = imageView.getDrawable();
+            if (drawable instanceof DownloadedDrawable) {
+                DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
+                return downloadedDrawable.getBitmapDownloaderTask();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param url The URL of the image that will be retrieved from the cache.
+     * @return The cached bitmap or null if it was not found.
+     */
+    private Bitmap getBitmapFromCache(String url) {
+        // First try the hard reference cache
+        synchronized (sHardBitmapCache) {
+            final Bitmap bitmap = sHardBitmapCache.get(url);
+            if (bitmap != null) {
+                // Bitmap found in hard cache
+                // Move element to first position, so that it is removed last
+                sHardBitmapCache.remove(url);
+                sHardBitmapCache.put(url, bitmap);
+                return bitmap;
+            }
+        }
+
+        // Then try the soft reference cache
+        SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
+        if (bitmapReference != null) {
+            final Bitmap bitmap = bitmapReference.get();
+            if (bitmap != null) {
+                // Bitmap found in soft cache
+                return bitmap;
+            } else {
+                // Soft reference has been Garbage Collected
+                sSoftBitmapCache.remove(url);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * The actual AsyncTask that will asynchronously download the image.
+     */
+    class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
+        private static final int IO_BUFFER_SIZE = 4 * 1024;
+        private String url;
+        private final WeakReference<ImageView> imageViewReference;
+
+        public BitmapDownloaderTask(ImageView imageView) {
+            imageViewReference = new WeakReference<ImageView>(imageView);
+        }
+
+        /**
+         * Actual download method.
+         */
+        @Override
+        protected Bitmap doInBackground(String... params) {
+            final AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
+            url = params[0];
+            final HttpGet getRequest = new HttpGet(url);
+            String cookie = params[1];
+            if (cookie != null) {
+                getRequest.setHeader("cookie", cookie);
+            }
+
+            try {
+                HttpResponse response = client.execute(getRequest);
+                final int statusCode = response.getStatusLine().getStatusCode();
+                if (statusCode != HttpStatus.SC_OK) {
+                    Log.w("ImageDownloader", "Error " + statusCode +
+                            " while retrieving bitmap from " + url);
+                    return null;
+                }
+
+                final HttpEntity entity = response.getEntity();
+                if (entity != null) {
+                    InputStream inputStream = null;
+                    OutputStream outputStream = null;
+                    try {
+                        inputStream = entity.getContent();
+                        final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+                        outputStream = new BufferedOutputStream(dataStream, IO_BUFFER_SIZE);
+                        copy(inputStream, outputStream);
+                        outputStream.flush();
+
+                        final byte[] data = dataStream.toByteArray();
+                        final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+
+                        // FIXME : Should use BitmapFactory.decodeStream(inputStream) instead.
+                        //final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
+
+                        return bitmap;
+
+                    } finally {
+                        if (inputStream != null) {
+                            inputStream.close();
+                        }
+                        if (outputStream != null) {
+                            outputStream.close();
+                        }
+                        entity.consumeContent();
+                    }
+                }
+            } catch (IOException e) {
+                getRequest.abort();
+                Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
+            } catch (IllegalStateException e) {
+                getRequest.abort();
+                Log.w(LOG_TAG, "Incorrect URL: " + url);
+            } catch (Exception e) {
+                getRequest.abort();
+                Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
+            } finally {
+                if (client != null) {
+                    client.close();
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Once the image is downloaded, associates it to the imageView
+         */
+        @Override
+        protected void onPostExecute(Bitmap bitmap) {
+            if (isCancelled()) {
+                bitmap = null;
+            }
+
+            // Add bitmap to cache
+            if (bitmap != null) {
+                synchronized (sHardBitmapCache) {
+                    sHardBitmapCache.put(url, bitmap);
+                }
+            }
+
+            if (imageViewReference != null) {
+                ImageView imageView = imageViewReference.get();
+                BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
+                // Change bitmap only if this process is still associated with it
+                if (this == bitmapDownloaderTask) {
+                    imageView.setImageBitmap(bitmap);
+                }
+            }
+        }
+
+        public void copy(InputStream in, OutputStream out) throws IOException {
+            byte[] b = new byte[IO_BUFFER_SIZE];
+            int read;
+            while ((read = in.read(b)) != -1) {
+                out.write(b, 0, read);
+            }
+        }
+    }
+
+    /**
+     * A fake Drawable that will be attached to the imageView while the download is in progress.
+     *
+     * <p>Contains a reference to the actual download task, so that a download task can be stopped
+     * if a new binding is required, and makes sure that only the last started download process can
+     * bind its result, independently of the download finish order.</p>
+     */
+    static class DownloadedDrawable extends ColorDrawable {
+        private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
+
+        public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
+            super(Color.BLACK);
+            bitmapDownloaderTaskReference =
+                new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
+        }
+
+        public BitmapDownloaderTask getBitmapDownloaderTask() {
+            return bitmapDownloaderTaskReference.get();
+        }
+    }
+}
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/PhotosListActivity.java b/samples/XmlAdapters/src/com/example/android/xmladapters/PhotosListActivity.java
new file mode 100644
index 0000000..1da151d
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/PhotosListActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.xmladapters;
+
+import android.app.ListActivity;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.Adapters;
+
+/**
+ * This activity uses a custom cursor adapter which fetches a XML photo feed and parses the XML to
+ * extract the images' URL and their title.
+ */
+public class PhotosListActivity extends ListActivity {
+    private static final String PICASA_FEED_URL =
+        "http://picasaweb.google.com/data/feed/api/featured?max-results=50&thumbsize=144c";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.photos_list);
+        setListAdapter(Adapters.loadCursorAdapter(this, R.xml.photos,
+                "content://xmldocument/?url=" + Uri.encode(PICASA_FEED_URL)));
+    }
+}
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/RssReaderActivity.java b/samples/XmlAdapters/src/com/example/android/xmladapters/RssReaderActivity.java
new file mode 100644
index 0000000..fb3e4c1
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/RssReaderActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.xmladapters;
+
+import android.app.ListActivity;
+import android.content.XmlDocumentProvider;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.Adapters;
+import android.widget.AdapterView.OnItemClickListener;
+
+/**
+ * This example demonstrate the creation of a simple RSS feed reader using the XML adapter syntax.
+ * The different elements of the feed are extracted using an {@link XmlDocumentProvider} and are
+ * binded to the different views. An {@link OnItemClickListener} is also added, which will open a
+ * browser on the associated news item page.
+ */
+public class RssReaderActivity extends ListActivity {
+    private static final String FEED_URI = "http://feeds.nytimes.com/nyt/rss/HomePage";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.rss_feeds_list);
+        setListAdapter(Adapters.loadCursorAdapter(this, R.xml.rss_feed,
+                "content://xmldocument/?url=" + Uri.encode(FEED_URI)));
+        getListView().setOnItemClickListener(new UrlIntentListener());
+    }
+}
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/UrlImageBinder.java b/samples/XmlAdapters/src/com/example/android/xmladapters/UrlImageBinder.java
new file mode 100644
index 0000000..33b1e8e
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/UrlImageBinder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.xmladapters;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.widget.Adapters;
+import android.widget.ImageView;
+
+/**
+ * This CursorBinder binds the provided image URL to an ImageView by downloading the image from the
+ * Internet.
+ */
+public class UrlImageBinder extends Adapters.CursorBinder {
+
+    private final ImageDownloader imageDownloader;
+
+    public UrlImageBinder(Context context, Adapters.CursorTransformation transformation) {
+        super(context, transformation);
+        imageDownloader = new ImageDownloader();
+    }
+
+    @Override
+    public boolean bind(View view, Cursor cursor, int columnIndex) {
+        if (view instanceof ImageView) {
+            final String url = mTransformation.transform(cursor, columnIndex);
+            imageDownloader.download(url, (ImageView) view);
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/UrlIntentListener.java b/samples/XmlAdapters/src/com/example/android/xmladapters/UrlIntentListener.java
new file mode 100644
index 0000000..af814d1
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/UrlIntentListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.example.android.xmladapters;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+
+/**
+ * A listener which expects a URL as a tag of the view it is associated with. It then opens the URL
+ * in the browser application.
+ */
+public class UrlIntentListener implements OnItemClickListener {
+
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        final String url = view.getTag().toString();
+        final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        final Context context = parent.getContext();
+        context.startActivity(intent);
+    }
+
+}
diff --git a/simulator/app/PropertyServer.cpp b/simulator/app/PropertyServer.cpp
index eb1bfc3..ad2f5f6 100644
--- a/simulator/app/PropertyServer.cpp
+++ b/simulator/app/PropertyServer.cpp
@@ -90,6 +90,9 @@
         { "ro.build.date", "Wed Nov 28 07:44:14 PST 2007" },
         { "ro.build.date.utc", "1196264654" },
         { "ro.build.type", "eng" },
+        { "ro.build.version.sdk", "8" },
+        { "ro.build.version.codename", "Honeycomb" },
+        { "ro.build.version.release", "Honeycomb" },
         { "ro.product.device", "simulator" /*"sooner"*/ },
         { "ro.product.brand", "generic" },
         { "ro.build.user", "fadden" },
@@ -99,12 +102,16 @@
         { "ro.radio.use-ppp", "no" },
         { "ro.FOREGROUND_APP_ADJ", "0" },
         { "ro.VISIBLE_APP_ADJ", "1" },
+        { "ro.PERCEPTIBLE_APP_ADJ", "2" },
+        { "ro.HEAVY_WEIGHT_APP_ADJ", "3" },
         { "ro.SECONDARY_SERVER_ADJ", "2" },
         { "ro.HIDDEN_APP_MIN_ADJ", "7" },
         { "ro.CONTENT_PROVIDER_ADJ", "14" },
         { "ro.EMPTY_APP_ADJ", "15" },
         { "ro.FOREGROUND_APP_MEM", "1536" },
         { "ro.VISIBLE_APP_MEM", "2048" },
+        { "ro.PERCEPTIBLE_APP_MEM", "4096" },
+        { "ro.HEAVY_WEIGHT_APP_MEM", "4096" },
         { "ro.SECONDARY_SERVER_MEM", "4096" },
         { "ro.HIDDEN_APP_MEM", "8192" },
         { "ro.EMPTY_APP_MEM", "16384" },
@@ -141,6 +148,7 @@
         { "log.redirect-stdio", "false" },          // -Xlog-stdio
 
         /* SurfaceFlinger options */
+        { "ro.sf.lcd_density", "160" },
         { "debug.sf.nobootanimation", "1" },
         { "debug.sf.showupdates", "0" },
         { "debug.sf.showcpu", "0" },
diff --git a/testrunner/create_test.py b/testrunner/create_test.py
index 2fc3a3c..faea013 100755
--- a/testrunner/create_test.py
+++ b/testrunner/create_test.py
@@ -98,7 +98,7 @@
     IOError: tests/AndroidManifest.xml cannot be opened for writing
   """
   # skip if file already exists
-  tests_path = "%s/%s" % (manifest.app_path, TestsConsts.TESTS_FOLDER)
+  tests_path = "%s/%s" % (manifest.GetAppPath(), TestsConsts.TESTS_FOLDER)
   tests_manifest_path = "%s/%s" % (tests_path, manifest.FILENAME)
   if os.path.exists(tests_manifest_path):
     _PrintMessage("%s already exists, not overwritten" % tests_manifest_path)
diff --git a/testrunner/runtest.py b/testrunner/runtest.py
index 4a86a63..19102c6 100755
--- a/testrunner/runtest.py
+++ b/testrunner/runtest.py
@@ -223,9 +223,14 @@
 
   def _DoBuild(self):
     logger.SilentLog("Building tests...")
+
+    tests = self._GetTestsToRun()
+    # turn off dalvik verifier if necessary
+    self._TurnOffVerifier(tests)
+    self._DoFullBuild(tests)
+
     target_set = Set()
     extra_args_set = Set()
-    tests = self._GetTestsToRun()
     for test_suite in tests:
       self._AddBuildTarget(test_suite, target_set, extra_args_set)
 
@@ -251,23 +256,9 @@
           logger.Log(cmd)
           run_command.RunCommand(cmd, return_output=False)
 
-      # hack to build cts dependencies
-      # TODO: remove this when build dependency support added to runtest or
-      # cts dependencies are removed
-      if self._IsCtsTests(tests):
-        # need to use make since these fail building with ONE_SHOT_MAKEFILE
-        cmd = ('make -j%s CtsTestStubs android.core.tests.runner' %
-               self._options.make_jobs)
-        logger.Log(cmd)
-        if not self._options.preview:
-          old_dir = os.getcwd()
-          os.chdir(self._root_path)
-          run_command.RunCommand(cmd, return_output=False)
-          os.chdir(old_dir)
-      # turn off dalvik verifier if necessary
-      self._TurnOffVerifier(tests)
       target_build_string = " ".join(list(target_set))
       extra_args_string = " ".join(list(extra_args_set))
+
       # mmm cannot be used from python, so perform a similar operation using
       # ONE_SHOT_MAKEFILE
       cmd = 'ONE_SHOT_MAKEFILE="%s" make -j%s -C "%s" files %s' % (
@@ -286,12 +277,43 @@
         logger.Log("Syncing to device...")
         self._adb.Sync(runtime_restart=rebuild_libcore)
 
+  def _DoFullBuild(self, tests):
+    """If necessary, run a full 'make' command for the tests that need it."""
+    extra_args_set = Set()
+
+    # hack to build cts dependencies
+    # TODO: remove this when cts dependencies are removed
+    if self._IsCtsTests(tests):
+      # need to use make since these fail building with ONE_SHOT_MAKEFILE
+      extra_args_set.add('CtsTestStubs')
+      extra_args_set.add('android.core.tests.runner')
+    for test in tests:
+      if test.IsFullMake():
+        if test.GetExtraBuildArgs():
+          # extra args contains the args to pass to 'make'
+          extra_args_set.add(test.GetExtraBuildArgs())
+        else:
+          logger.Log("Warning: test %s needs a full build but does not specify"
+                     " extra_build_args" % test.GetName())
+
+    # check if there is actually any tests that required a full build
+    if extra_args_set:
+      cmd = ('make -j%s %s' % (self._options.make_jobs,
+                               ' '.join(list(extra_args_set))))
+      logger.Log(cmd)
+      if not self._options.preview:
+        old_dir = os.getcwd()
+        os.chdir(self._root_path)
+        run_command.RunCommand(cmd, return_output=False)
+        os.chdir(old_dir)
+
   def _AddBuildTarget(self, test_suite, target_set, extra_args_set):
-    build_dir = test_suite.GetBuildPath()
-    if self._AddBuildTargetPath(build_dir, target_set):
-      extra_args_set.add(test_suite.GetExtraBuildArgs())
-    for path in test_suite.GetBuildDependencies(self._options):
-      self._AddBuildTargetPath(path, target_set)
+    if not test_suite.IsFullMake():
+      build_dir = test_suite.GetBuildPath()
+      if self._AddBuildTargetPath(build_dir, target_set):
+        extra_args_set.add(test_suite.GetExtraBuildArgs())
+      for path in test_suite.GetBuildDependencies(self._options):
+        self._AddBuildTargetPath(path, target_set)
 
   def _AddBuildTargetPath(self, build_dir, target_set):
     if build_dir is not None:
@@ -365,6 +387,7 @@
           self._adb.SendCommand("reboot")
           self._adb.SendCommand("wait-for-device", timeout_time=60,
                                 retry_count=3)
+          self._adb.EnableAdbRoot()
 
   def RunTests(self):
     """Main entry method - executes the tests according to command line args."""
diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index f83d32d..3209574 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -395,7 +395,8 @@
 <test name="browser"
     build_path="packages/apps/Browser"
     package="com.android.browser.tests"
-    coverage_target="Browser" />
+    coverage_target="Browser"
+    continuous="true" />
 
 <test name="calculator"
     build_path="packages/apps/Calculator"
@@ -499,6 +500,12 @@
     description="Google test."
     extra_build_args="GTEST_TESTS=1" />
 
+<!-- Libjingle -->
+<test-native name="libjingle"
+    build_path="vendor/google/libraries/libjingle"
+    description="Libjingle."
+    full_make="true"
+    extra_build_args="LIBJINGLE_TESTS=1" />
 
 <!-- host java tests -->
 <test-host name="cts-appsecurity"
diff --git a/testrunner/test_defs.xsd b/testrunner/test_defs.xsd
index 7134459..6781941 100644
--- a/testrunner/test_defs.xsd
+++ b/testrunner/test_defs.xsd
@@ -41,7 +41,7 @@
         <xs:attribute name="name" type="xs:string" use="required" />
 
         <!-- File system path, relative to Android build root, to this
-        package's Android.mk file. -->
+        package's Android.mk file.-->
         <xs:attribute name="build_path" type="xs:string" use="required" />
 
         <!-- Include test in continuous test system. -->
@@ -55,10 +55,17 @@
         test. -->
         <xs:attribute name="description" type="xs:string" use="optional" />
 
+        <!-- Specifies that a full 'make', as opposed to 'mmm' command, is
+        needed to build this test. The build command used will be
+        'make extra_build_args' -->
+        <xs:attribute name="full_make" type="xs:boolean"
+                    use="optional" />
+
         <!--  Extra arguments to append to build command when building this
         test. -->
         <xs:attribute name="extra_build_args" type="xs:string"
                     use="optional" />
+
     </xs:complexType>
 
     <!-- Java on device instrumentation test.
diff --git a/testrunner/test_defs/instrumentation_test.py b/testrunner/test_defs/instrumentation_test.py
index 8782615..40d6bed 100644
--- a/testrunner/test_defs/instrumentation_test.py
+++ b/testrunner/test_defs/instrumentation_test.py
@@ -17,9 +17,6 @@
 
 """TestSuite definition for Android instrumentation tests."""
 
-# python imports
-import os
-
 # local imports
 import coverage
 import errors
diff --git a/testrunner/test_defs/test_suite.py b/testrunner/test_defs/test_suite.py
index 90d5792..102a738 100644
--- a/testrunner/test_defs/test_suite.py
+++ b/testrunner/test_defs/test_suite.py
@@ -32,6 +32,7 @@
     self._suite = None
     self._description = ''
     self._extra_build_args = ''
+    self._is_full_make = False
 
   def GetName(self):
     return self._name
@@ -88,6 +89,13 @@
     self._extra_build_args = build_args
     return self
 
+  def IsFullMake(self):
+    return self._is_full_make
+
+  def SetIsFullMake(self, full_make):
+    self._is_full_make = full_make
+    return self
+
   def Run(self, options, adb):
     """Runs the test.
 
diff --git a/testrunner/test_defs/test_walker.py b/testrunner/test_defs/test_walker.py
index 06c4e6d..948d53e 100755
--- a/testrunner/test_defs/test_walker.py
+++ b/testrunner/test_defs/test_walker.py
@@ -65,20 +65,20 @@
     if not os.path.exists(path):
       logger.Log('%s does not exist' % path)
       return []
-    abspath = os.path.abspath(path)
+    realpath = os.path.realpath(path)
     # ensure path is in ANDROID_BUILD_ROOT
-    self._build_top = android_build.GetTop()
-    if not self._IsPathInBuildTree(abspath):
+    self._build_top = os.path.realpath(android_build.GetTop())
+    if not self._IsPathInBuildTree(realpath):
       logger.Log('%s is not a sub-directory of build root %s' %
                  (path, self._build_top))
       return []
 
     # first, assume path is a parent directory, which specifies to run all
     # tests within this directory
-    tests = self._FindSubTests(abspath, [])
+    tests = self._FindSubTests(realpath, [])
     if not tests:
       logger.SilentLog('No tests found within %s, searching upwards' % path)
-      tests = self._FindUpstreamTests(abspath)
+      tests = self._FindUpstreamTests(realpath)
     return tests
 
   def _IsPathInBuildTree(self, path):
diff --git a/testrunner/test_defs/xml_suite_helper.py b/testrunner/test_defs/xml_suite_helper.py
index b7ed83b..6cf2e6c 100644
--- a/testrunner/test_defs/xml_suite_helper.py
+++ b/testrunner/test_defs/xml_suite_helper.py
@@ -39,6 +39,7 @@
   _SUITE_ATTR = 'suite'
   _DESCRIPTION_ATTR = 'description'
   _EXTRA_BUILD_ARGS_ATTR = 'extra_build_args'
+  _FULL_MAKE_ATTR = 'full_make'
 
   def Parse(self, element):
     """Populates common suite attributes from given suite xml element.
@@ -79,6 +80,9 @@
                                                    default_value=''))
     test_suite.SetExtraBuildArgs(self._ParseAttribute(
         suite_element, self._EXTRA_BUILD_ARGS_ATTR, False, default_value=''))
+    test_suite.SetIsFullMake(self._ParseAttribute(
+        suite_element, self._FULL_MAKE_ATTR, False, default_value=False))
+
 
   def _ParseAttribute(self, suite_element, attribute_name, mandatory,
                       default_value=None):
diff --git a/tools/labpretest/README b/tools/labpretest/README
new file mode 100644
index 0000000..0f89035
--- /dev/null
+++ b/tools/labpretest/README
@@ -0,0 +1,53 @@
+Overview:
+
+The labpretest.sh script is designed to emulate a typical automated test lab
+session.  It puts a device into bootloader mode, reboots into bootloader mode,
+determines device type, erases user cache, flashes a generic userdata image,
+updates the bootloader image, updates the radio image, updates the system image
+and reboots, sets up for a monkey run and finally runs a random monkey test.
+It will repeat this based on an optional parameter(-i) or default to 100 times.
+It will detect if it is in a low battery situation and wait for it to charge
+again.
+
+The goal is to see if a device is ready for deployment to automated lab testing
+and can also be used to verify that lab infrastructure is ready for devices.
+The idea is to run this script at the same time for multiple devices, typically
+I would connect 8 devices to a host and run this script in 8 separate shell
+sessions and watch for failures.
+
+Running the script:
+
+If there is only one device attached to the host you can simply just run the
+script, it will detect the device and go through 100 cycles, running the monkey
+for 200 events each cycle.  The script ignores normal monkey failures. If you
+have multiple devices attached use the -d <device_id> parameter to target a
+specific devices.  Additional parameters are -i for how many cycles and -m for
+how many monkey events and finally -x to make it skip the monkey run portion
+altogether.
+
+Adding support for new devices or from scratch:
+
+The script uses included copies of adb and fastboot which are in in the tools/
+sub directory. If you are setting this up with only the script, create a tools
+sub directory and put adb and fastboot in it and make sure they are executable.
+Currently we use userdebug builds.
+
+Here are the steps to add a new device:
+
+  1) Create a new sub directory using the result of "fastboot getvar product".
+  2) Copy a build image to the new sub directory in our format.
+     (i.e. passion-img-24827.zip)
+  3) Copy a boot image to the new sub directory in our format.
+     (i.e. hboot.0.33.2012.img)
+  4) Copy a radio image to the new sub directory in our format.
+     (i.e. radio.4.04.00.03_2.img)
+  5) Copy a userdata.img file, possibly from one of the other directories.
+
+Customizations to the flashing process are handled by adding a custom_flash.sh
+file that is read in before the main loop starts. It allows you to add any non
+generic functions or details to the flashing process. You must use it to define
+the variable "bootpart" which is not defined by default. Also, use this file to
+rewrite the flash_device function and any others, etc...
+
+The script should handle the rest, unless there are radical changes to file
+names or the process.
\ No newline at end of file
diff --git a/tools/labpretest/labpretest.sh b/tools/labpretest/labpretest.sh
new file mode 100755
index 0000000..62238f0
--- /dev/null
+++ b/tools/labpretest/labpretest.sh
@@ -0,0 +1,582 @@
+#!/bin/bash
+#
+# Copyright 2010 Google Inc. All Rights Reserved.
+# Author: bgay@google.com (Bruce Gay)
+#
+# The labpretest.sh script is designed to emulate a typical automated test lab
+# session.  It puts a device into bootloader mode, reboots into bootloader mode,
+# determines device type, erases user cache, flashes a generic userdata image,
+# updates the bootloader image, updates the radio image, updates the system
+# image and reboots, sets up for a monkey run and finally runs a random monkey
+# test. It will repeat this based on an optional parameter(-i) or default to 100
+# times. It will detect if it is in a low battery situation and wait for it to
+# charge again.
+
+
+COUNT=100
+ROOT=$(cd `dirname $0` && pwd)
+ADB="$ROOT/tools/adb"
+FASTBOOT="$ROOT/tools/fastboot"
+MEVENTS=200
+NOMONKEY=0
+
+buildfile=''
+device=''
+product=''
+bootpart=''
+bootfile=''
+
+while getopts "d:i::m:xh" optionName; do
+  case "$optionName" in
+    d) device="$OPTARG";;
+    i) COUNT=$OPTARG;;
+    m) MEVENTS=$OPTARG;;
+    x) NOMONKEY=1;;
+    h) echo "options: [-d <device ID>, -i <loop count>, -m <monkey events> -x (skips monkey)]"; exit;;
+    *) echo "invalid parameter -$optionName"; exit -1;;
+  esac
+done
+
+declare -r COUNT
+declare -r MEVENTS
+declare -r NOMONKEY
+
+
+################################################
+# Prints output to console with time stamp
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+log_print()
+{
+  if [ -z "$1" ]; then
+    echo "# $(date +'%D %T')"
+  else
+    echo "# $(date +'%D %T'): $1"
+  fi
+}
+
+################################################
+# Blocks until battery level is at least
+# above TARGET if below LIMIT
+# Globals:
+#   ADB
+#   device
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+wait_for_battery()
+{
+  TARGET=80
+  LIMIT=20
+  local battery
+  local tick
+  log_print "checking battery level"
+  while [ "$battery" = "" ]; do
+    battery=`$ADB -s $device shell dumpsys battery | tr -d '\r' | awk '/level:/ {print $2}'`
+    sleep 2
+  done
+  if [ $battery -lt $LIMIT ]; then
+    log_print "Battery is low, waiting for charge"
+    while true; do
+      battery=`$ADB -s $device shell dumpsys battery | tr -d '\r' | awk '/level:/ {print $2}'`
+      if (( $battery >= $TARGET )); then break; fi
+      tick=$[$TARGET - $battery]
+      echo "battery charge level is $battery, sleeping for $tick seconds"
+      sleep $[$TARGET - $battery * 10]
+    done
+    log_print "resuming test run with battery level at $battery%"
+  else
+    log_print "resuming test run with battery level at $battery%"
+  fi
+}
+
+################################################
+# Blocks until device is in fastboot mode or
+# time out is reached
+# Globals:
+#   loop
+#   device
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+fastboot_wait_for_device()
+{
+  local fdevice=""
+  local n=0
+  while [ "$device" != "$fdevice" -a $n -le 30 ]; do
+    sleep 6
+    fdevice=`$FASTBOOT devices | sed -n "s/\($device\).*/\1/ p"`
+    let n+=1
+  done
+  if [ $n -gt 30 ]; then
+    log_print "device time out after $loop iterations"
+    exit
+  else
+    log_print "device returned and available"
+  fi
+}
+
+################################################
+# reboots device into fastboot mode or
+# time out is reached
+# Globals:
+#   device
+#   ADB
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+reboot_into_fastboot_from_adb()
+{
+  log_print "rebooting into bootloader and waiting for availability via fastboot"
+  $ADB -s $device reboot bootloader
+  fastboot_wait_for_device
+}
+
+################################################
+# reboots device into fastboot mode or
+# times out
+# Globals:
+#   device
+#   FASTBOOT
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+reboot_into_fastboot_from_fastboot()
+{
+  log_print "rebooting into bootloader and waiting for availability via fastboot"
+  $FASTBOOT -s $device reboot-bootloader
+  fastboot_wait_for_device
+}
+
+################################################
+# reboots device from fastboot to adb or
+# times out
+# Globals:
+#   device
+#   FASTBOOT
+#   ADB
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+reboot_into_adb_from_fastboot()
+{
+  log_print "rebooting and waiting for availability via adb"
+  $FASTBOOT -s $device reboot
+  $ADB -s $device wait-for-device
+}
+
+################################################
+# reboots device from fastboot to adb or
+# times out
+# Globals:
+#   device
+#   ADB
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+wait_for_boot_complete()
+{
+  log_print "waiting for device to finish booting"
+  local result=$($ADB -s $device shell getprop dev.bootcomplete)
+  local result_test=${result:1:1}
+  echo -n "."
+  while [ -z $result_test ]; do
+    sleep 1
+    echo -n "."
+    result=$($ADB -s $device shell getprop dev.bootcomplete)
+    result_test=${result:0:1}
+  done
+  log_print "finished booting"
+}
+
+################################################
+# fastboot flashes partition
+#
+# Globals:
+#   device
+#   FASTBOOT
+# Arguments:
+#   command_name
+#   command_parameters
+# Returns:
+#   None
+################################################
+fastboot_command()
+{
+  $FASTBOOT -s $device $1 $2 $3
+  sleep 5
+}
+
+################################################
+# fastboot command wrapper
+#
+# Globals:
+#   device
+#   FASTBOOT
+# Arguments:
+#   partition_name
+#   file_name
+# Returns:
+#   None
+################################################
+flash_partition()
+{
+  $FASTBOOT -s $device flash $1 $2
+  sleep 5
+}
+
+################################################
+# adb command wrapper
+#
+# Globals:
+#   device
+#   ADB
+# Arguments:
+#   command_name
+#   command_parameters
+# Returns:
+#   None
+################################################
+adb_command()
+{
+  $ADB -s $device $1 $2 $3 $4 $5
+  sleep 5
+}
+
+################################################
+# sets the name of the boot partition and
+# bootfile, then flashes device
+#
+# Globals:
+#   product
+#   ROOT
+#   bootloaderfile
+#   bootpart
+#   device
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+flash_bootloader_image()
+{
+  if [ "$bootpart" == '' ]; then
+    log_print "bootpart not defined"
+    exit
+  fi
+  if [ "$bootloaderfile" == '' ]; then
+    log_print "getting bootloader file for $product"
+    bootloaderfile=`ls -1 $ROOT/$product | sed -n 's/\(.*boot[0-9._]\+img\)/\1/ p'`
+    if [ "$bootloaderfile" == '' ]; then
+      log_print "bootloader file empty: $bootloaderfile"
+      exit
+    fi
+    if [ ! -e "$ROOT/$product/$bootloaderfile" ]; then
+      log_print "bootloader file not found: ./$product/$bootloaderfile"
+      exit
+    fi
+    log_print "using $ROOT/$product/$bootloaderfile as the bootloader image file"
+  fi
+  log_print "downloading bootloader image to $device"
+  flash_partition $bootpart $ROOT/$product/$bootloaderfile
+  reboot_into_fastboot_from_fastboot
+}
+
+################################################
+# sets the name of the radio partition and
+# radiofile and flashes device
+#
+# Globals:
+#   product
+#   ROOT
+#   radiofile
+#   radiopart
+#   device
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+flash_radio_image()
+{
+  if [ "$radiopart" == '' ]; then
+    log_print "setting radio partion to 'radio'"
+    radiopart='radio'
+  fi
+  if [ "$radiofile" == "" ]; then
+    log_print "getting radio file for $product"
+    radiofile=`ls -1 $ROOT/$product | sed -n 's/\(radio[0-9._A-Za-z]\+img\)/\1/ p'`
+    if [ "$radiofile" == "" ]; then
+      log_print "radio file empty: $radiofile"
+      exit
+    fi
+    if [ ! -e "$ROOT/$product/$radiofile" ]; then
+      log_print "radio file not found: ./$product/$radiofile"
+      exit
+    fi
+    log_print "using $ROOT/$product/$radiofile as the radio image file"
+  fi
+  log_print "downloading radio image to $device"
+  flash_partition $radiopart  $ROOT/$product/$radiofile
+  reboot_into_fastboot_from_fastboot
+}
+
+################################################
+# sets the name of the boot partition and
+# bootfile
+#
+# Globals:
+#   product
+#   ROOT
+#   buildfile
+#   device
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+flash_system_image()
+{
+  if [ "$buildfile" == "" ]; then
+    log_print "getting build file for $product"
+    buildfile=`\ls -1 $ROOT/$product 2>&1 | sed -n 's/\([a-z]\+-img-[0-9]\+.zip\)/\1/ p'`
+    if [ "$buildfile" == "" ]; then
+      log_print "build file empty: $buildfile"
+      exit
+    fi
+    if [ ! -e "$ROOT/$product/$buildfile" ]; then
+      log_print "build file not found: ./$product/$buildfile"
+      exit
+    fi
+    log_print "using $ROOT/$product/$buildfile as the system image file"
+  fi
+  log_print "downloading system image to $device"
+  fastboot_command update $ROOT/$product/$buildfile
+
+}
+################################################
+# flashes the userdata partition
+#
+# Globals:
+#   product
+#   ROOT
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+flash_userdata_image()
+{
+  log_print "flashing userdata..."
+  if [ -e $ROOT/$product/userdata.img ];then
+    flash_partition userdata $ROOT/$product/userdata.img
+  else
+    log_print "userdata.img file not found: $ROOT/$product/userdata.img"
+    exit
+  fi
+}
+
+
+################################################
+# flashes the device
+#
+# Globals:
+#   product
+#   ROOT
+#   FASTBOOT
+#   bootfile
+#   bootpart
+#   radiofile
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+flash_device()
+{
+  log_print "erasing cache..."
+  fastboot_command erase cache
+  flash_userdata_image
+  flash_bootloader_image
+  flash_radio_image
+  flash_system_image
+  #device has been rebooted
+  adb_command wait-for-device
+}
+
+################################################
+# gets the device product type and sets product
+#
+# Globals:
+#   product
+#   ROOT
+#   FASTBOOT
+#   device
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+set_product_type()
+{
+  if [ "$product" == "" ]; then
+    log_print "getting device product type"
+    product=`$FASTBOOT -s $device getvar product 2>&1 | sed -n 's/product: \([a-z]*\)\n*/\1/ p'`
+    if [ ! -e "$ROOT/$product" ]; then
+      log_print "device product id not supported: $product"
+      exit
+    fi
+  fi
+  log_print "using $product as device product id"
+}
+
+
+
+#start of script
+#test for dependencies
+if [ ! -e $ADB ]; then
+  echo "Error: adb not in path! Please correct this."
+  exit
+fi
+if [ ! -e $FASTBOOT ]; then
+  echo "Error: fastboot not in path! Please correct this."
+  exit
+fi
+#checks to see if the called device is available
+if [ "$device" != "" ]; then
+  tmpdevice=`$ADB devices | sed -n "s/\($device\).*/\1/ p"`
+  if [ "$device" != "$tmpdevice" ]; then
+      tmpdevice=`$FASTBOOT devices | sed -n "s/\($device\).*/\1/ p"`
+    if [ "$device" != "$tmpdevice" ]; then
+      echo "Warning: device not found... $device"
+      exit
+    else
+      echo "'Device '$device' found!'"
+      reboot_into_adb_from_fastboot
+      wait_for_boot_complete
+    fi
+  fi
+else
+  device=`$ADB devices | sed -n 's/.*\(^[0-9A-Z]\{2\}[0-9A-Z]*\).*/\1/ p'`
+  if [ `echo $device | wc -w` -ne 1 ]; then
+    echo 'There is more than one device found,'
+    echo 'please pass the correct device ID in as a parameter.'
+    exit
+  fi
+fi
+if [ "$device" == "" ]; then
+  echo 'Device not found via adb'
+  device=`$FASTBOOT devices | sed -n 's/.*\(^[0-9A-Z]\{2\}[0-9A-Z]*\).*/\1/ p'`
+  if [ `echo $device | wc -w` -ne 1 ]; then
+    echo "There is more than one device available,"
+    echo "please pass the correct device ID in as a parameter."
+    exit
+  fi
+  if [ "$device" == "" ]; then
+    echo 'Device not found via fastboot, please investigate'
+    exit
+  else
+    echo 'Device '$device' found!'
+    reboot_into_adb_from_fastboot
+    wait_for_boot_complete
+    echo 'Hammering on '$device
+  fi
+else
+  echo 'Hammering on '$device
+fi
+reboot_into_fastboot_from_adb
+set_product_type
+reboot_into_adb_from_fastboot
+wait_for_boot_complete
+
+#check for availability of a custom flash info file and retreive it
+if [ -e "$ROOT/$product/custom_flash.sh" ]; then
+  . $ROOT/$product/custom_flash.sh
+fi
+echo $'\n\n'
+
+#start of looping
+for ((loop=1 ; loop <= $COUNT ; loop++ )) ; do
+  echo ""
+  echo ""
+  echo ________________ $(date +'%D %T') - $loop - $device ______________________
+
+  log_print "setting adb root and sleeping for 7 seconds"
+  adb_command root
+  wait_for_battery
+  log_print "rebooting into bootloader and waiting for availability via fastboot"
+  reboot_into_fastboot_from_adb
+  # not necessary, but useful in testing
+  log_print "using fastboot to reboot to bootloader for test purposes"
+  reboot_into_fastboot_from_fastboot
+
+  #flashing the device
+  flash_device
+
+  #preping device for monkey run
+  log_print "setting adb root"
+  adb_command root
+  log_print "setting ro.monkey property"
+  adb_command shell setprop ro.monkey 1
+
+  log_print "waiting for device to finish booting"
+  result=$($ADB -s $device shell getprop dev.bootcomplete)
+  result_test=${result:1:1}
+  echo -n "."
+  while [ -z $result_test ]; do
+    sleep 1
+    echo -n "."
+    result=$($ADB -s $device shell getprop dev.bootcomplete)
+    result_test=${result:0:1}
+  done
+
+  log_print "finished booting"
+  log_print "waiting for the Package Manager"
+  result=$($ADB -s $device shell pm path android)
+  result_test=${result:0:7}
+  echo -n "."
+  while [ $result_test != "package" ]; do
+    sleep 1
+    echo -n "."
+    result=$($ADB -s $device shell pm path android)
+    result_test=${result:0:7}
+  done
+  echo "Package Manager available"
+
+  #lets you see what's going on
+  log_print "setting shell svc power stayon true"
+  adb_command shell svc power stayon true
+
+  #calls the monkey run if not skipped
+  if [ $NOMONKEY == 0 ]; then
+    seed=$(($(date +%s) % 99))
+    log_print "running short monkey run..."
+    $ADB -s $device shell monkey -p com.android.alarmclock -p com.android.browser -p com.android.calculator2 -p com.android.calendar -p com.android.camera -p com.android.contacts -p com.google.android.gm -p com.android.im -p com.android.launcher -p com.google.android.apps.maps -p com.android.mms -p com.android.music -p com.android.phone -p com.android.settings -p com.google.android.street -p com.android.vending -p com.google.android.youtube -p com.android.email -p com.google.android.voicesearch  -c android.intent.category.LAUNCHER  --ignore-security-exceptions  -s $seed $MEVENTS
+    log_print "finished running monkey, rinse, repeat..."
+  else
+    log_print "-x parameter used, skipping the monkey run"
+  fi
+
+  if [ $loop -eq $COUNT ]; then
+    log_print "device $device has returned, testing completed, count = $loop"
+    echo `echo "Device $device has returned, testing completed, count = $loop." > $ROOT/$device.log`
+  else
+    log_print "device $device has returned, rinse and repeat count = $loop"
+    echo `echo "Device $device has returned, rinse and repeat count = $loop." > $ROOT/$device.log`
+  fi
+done
diff --git a/tools/labpretest/nexusone/custom_flash.sh b/tools/labpretest/nexusone/custom_flash.sh
new file mode 100644
index 0000000..ae9d6fa
--- /dev/null
+++ b/tools/labpretest/nexusone/custom_flash.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+#
+# Copyright 2010 Google Inc. All Rights Reserved.
+# Author: bgay@google.com (Bruce Gay)
+#
+# used for flashing boot image on nexusone
+
+bootpart='hboot'
diff --git a/tools/labpretest/sholes/custom_flash.sh b/tools/labpretest/sholes/custom_flash.sh
new file mode 100644
index 0000000..8a08222
--- /dev/null
+++ b/tools/labpretest/sholes/custom_flash.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+#
+# Copyright 2010 Google Inc. All Rights Reserved.
+# Author: bgay@google.com (Bruce Gay)
+#
+# used for flashing bootloader image on sholes
+
+BOOTPART='motoboot'
+
+################################################
+# sets the name of the boot partition and
+# bootfile, then flashes device
+#
+# Globals:
+#   product
+#   ROOT
+#   BOOTPART
+#   bootloaderfile
+#   device
+# Arguments:
+#   None
+# Returns:
+#   None
+################################################
+flash_bootloader_image()
+{
+  if [ $product != "sholes" ]; then
+    log_print "Wrong device type, expected sholes!"
+    exit
+  fi
+  if [ "$bootloaderfile" == '' ]; then
+    log_print "getting bootloader file for $product"
+    secure=`$fastboot -s $device getvar secure 2>&1 | sed -n 's/secure: \([a-z]*\)\n*/\1/ p'`
+    if [ "$secure" = "no" ]; then
+      bootloaderfile=`ls -1 sholes/ | sed -n 's/^\(motoboot_unsecure.[0-9A-Z]*.img\)\n*/\1/ p'`
+    else
+      bootloaderfile=`ls -1 sholes/ | sed -n 's/^\(motoboot_secure.[0-9A-Z]*.img\)\n*/\1/ p'`
+    fi
+    if [ "$bootloaderfile" == '' ]; then
+      log_print "bootloader file empty: $bootloaderfile"
+      exit
+    fi
+    if [ ! -e "$ROOT/$product/$bootloaderfile" ]; then
+      log_print "bootloader file not found: ./$product/$bootloaderfile"
+      exit
+    fi
+    log_print "using $ROOT/$product/$bootloaderfile as the bootloader image file"
+  fi
+  log_print "downloading bootloader image to $device"
+  flash_partition $BOOTPART $ROOT/$product/$bootloaderfile
+  reboot_into_fastboot_from_fastboot
+}
diff --git a/tools/monkeyrunner/Android.mk b/tools/monkeyrunner/Android.mk
index d15c67e..21cf67a 100644
--- a/tools/monkeyrunner/Android.mk
+++ b/tools/monkeyrunner/Android.mk
@@ -16,3 +16,4 @@
 MONKEYRUNNER_LOCAL_DIR := $(call my-dir)
 include $(MONKEYRUNNER_LOCAL_DIR)/etc/Android.mk
 include $(MONKEYRUNNER_LOCAL_DIR)/src/Android.mk
+include $(MONKEYRUNNER_LOCAL_DIR)/test/Android.mk
diff --git a/tools/monkeyrunner/etc/manifest.txt b/tools/monkeyrunner/etc/manifest.txt
index 288be5f..706842e 100644
--- a/tools/monkeyrunner/etc/manifest.txt
+++ b/tools/monkeyrunner/etc/manifest.txt
@@ -1 +1 @@
-Main-Class: com.android.monkeyrunner.MonkeyRunner
+Main-Class: com.android.monkeyrunner.MonkeyRunnerStarter
diff --git a/tools/monkeyrunner/jython/test/MonkeyRunner_test.py b/tools/monkeyrunner/jython/test/MonkeyRunner_test.py
new file mode 100644
index 0000000..cc4d1f2
--- /dev/null
+++ b/tools/monkeyrunner/jython/test/MonkeyRunner_test.py
@@ -0,0 +1,54 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2010, The Android Open Source Project
+#
+# 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.
+
+"""Test cases for com.android.monkeyrunner.MonkeyRunner."""
+
+import time
+import unittest
+
+from com.android.monkeyrunner import MonkeyRunner
+
+
+class TestMonkeyRunnerArgParsing(unittest.TestCase):
+  """Test ArgParsing for the MonkeyRunner methods."""
+  def testWaitForConnectionNoArgs(self):
+    MonkeyRunner.waitForConnection()
+
+  def testWaitForConnectionSingleArg(self):
+    MonkeyRunner.waitForConnection(2)
+
+  def testWaitForConnectionDoubleArg(self):
+    MonkeyRunner.waitForConnection(2, '*')
+
+  def testWaitForConnectionKeywordArg(self):
+    MonkeyRunner.waitForConnection(timeout=2, deviceId='foo')
+
+  def testWaitForConnectionKeywordArgTooMany(self):
+    try:
+      MonkeyRunner.waitForConnection(timeout=2, deviceId='foo', extra='fail')
+    except TypeError:
+      return
+    self.fail('Should have raised TypeError')
+
+  def testSleep(self):
+    start = time.time()
+    MonkeyRunner.sleep(1.5)
+    end = time.time()
+
+    self.assertTrue(end - start >= 1.5)
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tools/monkeyrunner/jython/test/all_tests.py b/tools/monkeyrunner/jython/test/all_tests.py
new file mode 100644
index 0000000..2dd0ab4
--- /dev/null
+++ b/tools/monkeyrunner/jython/test/all_tests.py
@@ -0,0 +1,49 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2010, The Android Open Source Project
+#
+# 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.
+
+"""Test runner to run all the tests in this package."""
+
+import os
+import re
+import sys
+import unittest
+
+
+TESTCASE_RE = re.compile('_test\.py$')
+
+
+def AllTestFilesInDir(path):
+  """Finds all the unit test files in the given path."""
+  return filter(TESTCASE_RE.search, os.listdir(path))
+
+
+def suite(loader=unittest.defaultTestLoader):
+  """Creates the all_tests TestSuite."""
+  script_parent_path = os.path.abspath(os.path.dirname(sys.argv[0]))
+  # Find all the _test.py files in the same directory we are in
+  test_files = AllTestFilesInDir(script_parent_path)
+  # Convert them into module names
+  module_names = [os.path.splitext(f)[0] for f in test_files]
+  # And import them
+  modules = map(__import__, module_names)
+  # And create the test suite for all these modules
+  return unittest.TestSuite([loader.loadTestsFromModule(m) for m in modules])
+
+if __name__ == '__main__':
+  result = unittest.TextTestRunner().run(suite())
+  if not result.wasSuccessful():
+    # On failure return an error code
+    sys.exit(1)
diff --git a/tools/monkeyrunner/scripts/help.py b/tools/monkeyrunner/scripts/help.py
new file mode 100644
index 0000000..832d2cb
--- /dev/null
+++ b/tools/monkeyrunner/scripts/help.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env monkeyrunner
+# Copyright 2010, The Android Open Source Project
+#
+# 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.
+from com.android.monkeyrunner import MonkeyRunner as mr
+
+import os
+import sys
+
+supported_formats = ['html', 'text']
+
+if len(sys.argv) != 3:
+  print 'help.py: format output'
+  sys.exit(1)
+
+(format, saveto_path) = sys.argv[1:]
+
+if not format.lower() in supported_formats:
+  print 'format %s is not a supported format' % format
+  sys.exit(2)
+
+output = mr.help(format=format)
+if not output:
+  print 'Error generating help format'
+  sys.exit(3)
+
+dirname = os.path.dirname(saveto_path)
+try:
+    os.makedirs(dirname)
+except:
+    print 'oops'
+    pass # It already existed
+
+fp = open(saveto_path, 'w')
+fp.write(output)
+fp.close()
+
+sys.exit(0)
diff --git a/tools/monkeyrunner/src/Android.mk b/tools/monkeyrunner/src/Android.mk
index fb6b9c1..59337c6 100644
--- a/tools/monkeyrunner/src/Android.mk
+++ b/tools/monkeyrunner/src/Android.mk
@@ -22,30 +22,11 @@
 LOCAL_JAVA_LIBRARIES := \
 	ddmlib \
 	jython \
-	xmlwriter
-
+	guavalib \
+	clearsilver
+LOCAL_SHARED_LIBRARIES := libclearsilver-jni
+LOCAL_JAVA_RESOURCE_DIRS := resources
 
 LOCAL_MODULE := monkeyrunner
 
 include $(BUILD_HOST_JAVA_LIBRARY)
-
-# Build ext.jar
-# ============================================================
-
-ext_dirs := 	../../../../external/xmlwriter/src
-
-ext_src_files := $(call all-java-files-under,$(ext_dirs))
-
-# ====  the library  =========================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(ext_src_files)
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-#LOCAL_JAVA_LIBRARIES := core
-#LOCAL_STATIC_JAVA_LIBRARIES := libgoogleclient
-
-LOCAL_MODULE := xmlwriter
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java b/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java
new file mode 100644
index 0000000..258261b
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.ImmutableMap.Builder;
+
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+
+import org.python.core.ArgParser;
+import org.python.core.Py;
+import org.python.core.PyDictionary;
+import org.python.core.PyFloat;
+import org.python.core.PyInteger;
+import org.python.core.PyList;
+import org.python.core.PyNone;
+import org.python.core.PyObject;
+import org.python.core.PyString;
+import org.python.core.PyTuple;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Collection of useful utilities function for interacting with the Jython interpreter.
+ */
+public final class JythonUtils {
+    private static final Logger LOG = Logger.getLogger(JythonUtils.class.getCanonicalName());
+    private JythonUtils() { }
+
+    /**
+     * Mapping of PyObject classes to the java class we want to convert them to.
+     */
+    private static final Map<Class<? extends PyObject>, Class<?>> PYOBJECT_TO_JAVA_OBJECT_MAP;
+    static {
+        Builder<Class<? extends PyObject>, Class<?>> builder = ImmutableMap.builder();
+
+        builder.put(PyString.class, String.class);
+        // What python calls float, most people call double
+        builder.put(PyFloat.class, Double.class);
+        builder.put(PyInteger.class, Integer.class);
+
+        PYOBJECT_TO_JAVA_OBJECT_MAP = builder.build();
+    }
+
+    /**
+     * Utility method to be called from Jython bindings to give proper handling of keyword and
+     * positional arguments.
+     *
+     * @param args the PyObject arguments from the binding
+     * @param kws the keyword arguments from the binding
+     * @return an ArgParser for this binding, or null on error
+     */
+    public static ArgParser createArgParser(PyObject[] args, String[] kws) {
+        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+        // Up 2 levels in the current stack to give us the calling function
+        StackTraceElement element = stackTrace[2];
+
+        String methodName = element.getMethodName();
+        String className = element.getClassName();
+
+        Class<?> clz;
+        try {
+            clz = Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            LOG.log(Level.SEVERE, "Got exception: ", e);
+            return null;
+        }
+
+        Method m;
+
+        try {
+            m = clz.getMethod(methodName, PyObject[].class, String[].class);
+        } catch (SecurityException e) {
+            LOG.log(Level.SEVERE, "Got exception: ", e);
+            return null;
+        } catch (NoSuchMethodException e) {
+            LOG.log(Level.SEVERE, "Got exception: ", e);
+            return null;
+        }
+
+        MonkeyRunnerExported annotation = m.getAnnotation(MonkeyRunnerExported.class);
+        return new ArgParser(methodName, args, kws,
+                annotation.args());
+    }
+
+    /**
+     * Get a python floating point value from an ArgParser.
+     *
+     * @param ap the ArgParser to get the value from.
+     * @param position the position in the parser
+     * @return the double value
+     */
+    public static double getFloat(ArgParser ap, int position) {
+        PyObject arg = ap.getPyObject(position);
+
+        if (Py.isInstance(arg, PyFloat.TYPE)) {
+            return ((PyFloat) arg).asDouble();
+        }
+        if (Py.isInstance(arg, PyInteger.TYPE)) {
+            return ((PyInteger) arg).asDouble();
+        }
+        throw Py.TypeError("Unable to parse argument: " + position);
+    }
+
+    /**
+     * Get a python floating point value from an ArgParser.
+     *
+     * @param ap the ArgParser to get the value from.
+     * @param position the position in the parser
+     * @param defaultValue the default value to return if the arg isn't specified.
+     * @return the double value
+     */
+    public static double getFloat(ArgParser ap, int position, double defaultValue) {
+        PyObject arg = ap.getPyObject(position, new PyFloat(defaultValue));
+
+        if (Py.isInstance(arg, PyFloat.TYPE)) {
+            return ((PyFloat) arg).asDouble();
+        }
+        if (Py.isInstance(arg, PyInteger.TYPE)) {
+            return ((PyInteger) arg).asDouble();
+        }
+        throw Py.TypeError("Unable to parse argument: " + position);
+    }
+
+    /**
+     * Get a list of arguments from an ArgParser.
+     *
+     * @param ap the ArgParser
+     * @param position the position in the parser to get the argument from
+     * @return a list of those items
+     */
+    @SuppressWarnings("unchecked")
+    public static List<Object> getList(ArgParser ap, int position) {
+        PyObject arg = ap.getPyObject(position, Py.None);
+        if (Py.isInstance(arg, PyNone.TYPE)) {
+            return Collections.emptyList();
+        }
+
+        List<Object> ret = Lists.newArrayList();
+        PyList array = (PyList) arg;
+        for (int x = 0; x < array.__len__(); x++) {
+            PyObject item = array.__getitem__(x);
+
+            Class<?> javaClass = PYOBJECT_TO_JAVA_OBJECT_MAP.get(item.getClass());
+            if (javaClass != null) {
+                ret.add(item.__tojava__(javaClass));
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Get a dictionary from an ArgParser.  For ease of use, key types are always coerced to
+     * strings.  If key type cannot be coeraced to string, an exception is raised.
+     *
+     * @param ap the ArgParser to work with
+     * @param position the position in the parser to get.
+     * @return a Map mapping the String key to the value
+     */
+    public static Map<String, Object> getMap(ArgParser ap, int position) {
+        PyObject arg = ap.getPyObject(position, Py.None);
+        if (Py.isInstance(arg, PyNone.TYPE)) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> ret = Maps.newHashMap();
+        // cast is safe as getPyObjectbyType ensures it
+        PyDictionary dict = (PyDictionary) arg;
+        PyList items = dict.items();
+        for (int x = 0; x < items.__len__(); x++) {
+            // It's a list of tuples
+            PyTuple item = (PyTuple) items.__getitem__(x);
+            // We call str(key) on the key to get the string and then convert it to the java string.
+            String key = (String) item.__getitem__(0).__str__().__tojava__(String.class);
+            PyObject value = item.__getitem__(1);
+
+            // Look up the conversion type and convert the value
+            Class<?> javaClass = PYOBJECT_TO_JAVA_OBJECT_MAP.get(value.getClass());
+            if (javaClass != null) {
+                ret.put(key, value.__tojava__(javaClass));
+            }
+        }
+        return ret;
+    }
+
+    private static PyObject convertObject(Object o) {
+        if (o instanceof String) {
+            return new PyString((String) o);
+        } else if (o instanceof Double) {
+            return new PyFloat((Double) o);
+        } else if (o instanceof Integer) {
+            return new PyInteger((Integer) o);
+        } else if (o instanceof Float) {
+            float f = (Float) o;
+            return new PyFloat(f);
+        }
+        return Py.None;
+    }
+
+    /**
+     * Convert the given Java Map into a PyDictionary.
+     *
+     * @param map the map to convert
+     * @return the python dictionary
+     */
+    public static PyDictionary convertMapToDict(Map<String, Object> map) {
+        Map<PyObject, PyObject> resultMap = Maps.newHashMap();
+
+        for (Entry<String, Object> entry : map.entrySet()) {
+            resultMap.put(new PyString(entry.getKey()),
+                    convertObject(entry.getValue()));
+        }
+        return new PyDictionary(resultMap);
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
new file mode 100644
index 0000000..87c54c2
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+
+import org.python.core.ArgParser;
+import org.python.core.Py;
+import org.python.core.PyDictionary;
+import org.python.core.PyException;
+import org.python.core.PyObject;
+import org.python.core.PyTuple;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/*
+ * Abstract base class that represents a single connected Android
+ * Device and provides MonkeyRunner API methods for interacting with
+ * that device.  Each backend will need to create a concrete
+ * implementation of this class.
+ */
+public abstract class MonkeyDevice {
+    /**
+     * Create a MonkeyMananger for talking to this device.
+     *
+     * NOTE: This is not part of the jython API.
+     *
+     * @return the MonkeyManager
+     */
+    public abstract MonkeyManager getManager();
+
+    /**
+     * Dispose of any native resoureces this device may have taken hold of.
+     *
+     *  NOTE: This is not part of the jython API.
+     */
+    public abstract void dispose();
+
+    @MonkeyRunnerExported(doc = "Fetch the screenbuffer from the device and return it.",
+            returns = "The captured snapshot.")
+    public abstract MonkeyImage takeSnapshot();
+
+    @MonkeyRunnerExported(doc = "Get a MonkeyRunner property (like build.fingerprint)",
+            args = {"key"},
+            argDocs = {"The key of the property to return"},
+            returns = "The value of the property")
+    public String getProperty(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        return getProperty(ap.getString(0));
+    }
+
+    @MonkeyRunnerExported(doc = "Get a system property (returns the same value as getprop).",
+            args = {"key"},
+            argDocs = {"The key of the property to return"},
+            returns = "The value of the property")
+    public String getSystemProperty(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+        return getSystemProperty(ap.getString(0));
+    }
+
+    @MonkeyRunnerExported(doc = "Enumeration of possible touch and press event types.  This gets " +
+            "passed into a press or touch call to specify the event type.",
+            argDocs = {"Indicates the down part of a touch/press event",
+            "Indicates the up part of a touch/press event.",
+            "Indicates that the monkey should send a down event immediately " +
+                "followed by an up event"})
+    public enum TouchPressType {
+        DOWN, UP, DOWN_AND_UP
+    }
+
+    @MonkeyRunnerExported(doc = "Send a touch event at the specified location",
+            args = { "x", "y", "type" },
+            argDocs = { "x coordinate", "y coordinate", "the type of touch event to send"})
+    public void touch(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        int x = ap.getInt(0);
+        int y = ap.getInt(1);
+
+        // Default
+        MonkeyDevice.TouchPressType type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+        try {
+            PyObject pyObject = ap.getPyObject(2);
+            type = (TouchPressType) pyObject.__tojava__(MonkeyDevice.TouchPressType.class);
+        } catch (PyException e) {
+            // bad stuff was passed in, just use the already specified default value
+            type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+        }
+        touch(x, y, type);
+    }
+
+    @MonkeyRunnerExported(doc = "Simulate a drag on the screen.",
+            args = { "start", "end", "duration", "steps"},
+            argDocs = { "The starting point for the drag (a tuple of x,y)",
+            "The end point for the drag (a tuple of x,y)",
+            "How long (in seconds) should the drag take (default is 1.0 seconds)",
+            "The number of steps to take when interpolating points. (default is 10)"})
+    public void drag(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        PyObject startObject = ap.getPyObject(0);
+        if (!(startObject instanceof PyTuple)) {
+            throw Py.TypeError("Agrument 0 is not a tuple");
+        }
+        PyObject endObject = ap.getPyObject(1);
+        if (!(endObject instanceof PyTuple)) {
+            throw Py.TypeError("Agrument 1 is not a tuple");
+        }
+
+        PyTuple start = (PyTuple) startObject;
+        PyTuple end = (PyTuple) endObject;
+
+        int startx = (Integer) start.__getitem__(0).__tojava__(Integer.class);
+        int starty = (Integer) start.__getitem__(1).__tojava__(Integer.class);
+        int endx = (Integer) end.__getitem__(0).__tojava__(Integer.class);
+        int endy = (Integer) end.__getitem__(1).__tojava__(Integer.class);
+
+        double seconds = JythonUtils.getFloat(ap, 2, 1.0);
+        long ms = (long) (seconds * 1000.0);
+
+        int steps = ap.getInt(3, 10);
+
+        drag(startx, starty, endx, endy, steps, ms);
+    }
+
+    @MonkeyRunnerExported(doc = "Send a key press event to the specified button",
+            args = { "name", "type" },
+            argDocs = { "the name of the key to press", "the type of touch event to send"})
+    public void press(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String name = ap.getString(0);
+
+        // Default
+        MonkeyDevice.TouchPressType type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+        try {
+            PyObject pyObject = ap.getPyObject(1);
+            type = (TouchPressType) pyObject.__tojava__(MonkeyDevice.TouchPressType.class);
+        } catch (PyException e) {
+            // bad stuff was passed in, just use the already specified default value
+            type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+        }
+        press(name, type);
+    }
+
+    @MonkeyRunnerExported(doc = "Type the specified string on the keyboard.",
+            args = { "message" },
+            argDocs = { "the message to type." })
+    public void type(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String message = ap.getString(0);
+        type(message);
+    }
+
+    @MonkeyRunnerExported(doc = "Execute the given command on the shell.",
+            args = { "cmd"},
+            argDocs = { "The command to execute" },
+            returns = "The output of the command")
+    public String shell(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String cmd = ap.getString(0);
+        return shell(cmd);
+    }
+
+    @MonkeyRunnerExported(doc = "Reboot the specified device",
+            args = { "into" },
+            argDocs = { "the bootloader to reboot into (bootloader, recovery, or None)"})
+    public void reboot(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String into = ap.getString(0, null);
+
+        reboot(into);
+    }
+
+    @MonkeyRunnerExported(doc = "Install the specified apk onto the device.",
+            args = { "path" },
+            argDocs = { "The path on the host filesystem to the APK to install." },
+            returns = "True if install succeeded")
+    public boolean installPackage(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String path = ap.getString(0);
+        return installPackage(path);
+    }
+
+    @MonkeyRunnerExported(doc = "Remove the specified package from the device.",
+            args = { "package"},
+            argDocs = { "The name of the package to uninstall"},
+            returns = "'True if remove succeeded")
+    public boolean removePackage(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String packageName = ap.getString(0);
+        return removePackage(packageName);
+    }
+
+    @MonkeyRunnerExported(doc = "Start the Activity specified by the intent.",
+            args = { "uri", "action", "data", "mimetype", "categories", "extras",
+                     "component", "flags" },
+            argDocs = { "The URI for the intent",
+                        "The action for the intent",
+                        "The data URI for the intent",
+                        "The mime type for the intent",
+                        "The list of category names for the intent",
+                        "A dictionary of extras to add to the intent.  Types of these extras " +
+                            "are inferred from the python types of the values",
+                        "The component of the intent",
+                        "A list of flags for the intent" })
+    public void startActivity(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String uri = ap.getString(0, null);
+        String action = ap.getString(1, null);
+        String data = ap.getString(2, null);
+        String mimetype = ap.getString(3, null);
+        Collection<String> categories = Collections2.transform(JythonUtils.getList(ap, 4),
+                Functions.toStringFunction());
+        Map<String, Object> extras = JythonUtils.getMap(ap, 5);
+        String component = ap.getString(6, null);
+        int flags = ap.getInt(7, 0);
+
+        startActivity(uri, action, data, mimetype, categories, extras, component, flags);
+    }
+
+    @MonkeyRunnerExported(doc = "Start the specified broadcast intent on the device.",
+            args = { "uri", "action", "data", "mimetype", "categories", "extras",
+                     "component", "flags" },
+            argDocs = { "The URI for the intent",
+                        "The action for the intent",
+                        "The data URI for the intent",
+                        "The mime type for the intent",
+                        "The list of category names for the intent",
+                        "A dictionary of extras to add to the intent.  Types of these extras " +
+                            "are inferred from the python types of the values",
+                        "The component of the intent",
+                        "A list of flags for the intent" })
+    public void broadcastIntent(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String uri = ap.getString(0, null);
+        String action = ap.getString(1, null);
+        String data = ap.getString(2, null);
+        String mimetype = ap.getString(3, null);
+        Collection<String> categories = Collections2.transform(JythonUtils.getList(ap, 4),
+                Functions.toStringFunction());
+        Map<String, Object> extras = JythonUtils.getMap(ap, 5);
+        String component = ap.getString(6, null);
+        int flags = ap.getInt(7, 0);
+
+        broadcastIntent(uri, action, data, mimetype, categories, extras, component, flags);
+    }
+
+    @MonkeyRunnerExported(doc = "Instrument the specified package and return the results from it.",
+            args = { "className", "args" },
+            argDocs = { "The class name to instrument (like com.android.test/.TestInstrument)",
+                        "A Map of String to Objects for the aruments to pass to this " +
+                        "instrumentation (default value is None)" },
+            returns = "A map of string to objects for the results this instrumentation returned")
+    public PyDictionary instrument(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String packageName = ap.getString(0);
+        Map<String, Object> instrumentArgs = JythonUtils.getMap(ap, 1);
+        if (instrumentArgs == null) {
+            instrumentArgs = Collections.emptyMap();
+        }
+
+        Map<String, Object> result = instrument(packageName, instrumentArgs);
+        return JythonUtils.convertMapToDict(result);
+    }
+
+    @MonkeyRunnerExported(doc = "Wake up the screen on the device")
+    public void wake(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        wake();
+    }
+
+    /**
+     * Reboot the device.
+     *
+     * @param into which bootloader to boot into.  Null means default reboot.
+     */
+    protected abstract void reboot(@Nullable String into);
+
+    protected abstract String getProperty(String key);
+    protected abstract String getSystemProperty(String key);
+    protected abstract void touch(int x, int y, TouchPressType type);
+    protected abstract void press(String keyName, TouchPressType type);
+    protected abstract void drag(int startx, int starty, int endx, int endy, int steps, long ms);
+    protected abstract void type(String string);
+    protected abstract String shell(String cmd);
+    protected abstract boolean installPackage(String path);
+    protected abstract boolean removePackage(String packageName);
+    protected abstract void startActivity(@Nullable String uri, @Nullable String action,
+            @Nullable String data, @Nullable String mimetype,
+            Collection<String> categories, Map<String, Object> extras, @Nullable String component,
+            int flags);
+    protected abstract void broadcastIntent(@Nullable String uri, @Nullable String action,
+            @Nullable String data, @Nullable String mimetype,
+            Collection<String> categories, Map<String, Object> extras, @Nullable String component,
+            int flags);
+    protected abstract Map<String, Object> instrument(String packageName,
+            Map<String, Object> args);
+    protected abstract void wake();
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyFormatter.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyFormatter.java
new file mode 100644
index 0000000..c4a5362
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyFormatter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.collect.Maps;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.logging.Formatter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/*
+ * Custom Logging Formatter for MonkeyRunner that generates all log
+ * messages on a single line.
+ */
+public class MonkeyFormatter extends Formatter {
+    public static final Formatter DEFAULT_INSTANCE = new MonkeyFormatter();
+
+    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyMMdd HH:mm:ss.SSS");
+
+    private static Map<Level, String> LEVEL_TO_STRING_CACHE = Maps.newHashMap();
+
+    private static final String levelToString(Level level) {
+        String levelName = LEVEL_TO_STRING_CACHE.get(level);
+        if (levelName == null) {
+            levelName = level.getName().substring(0, 1);
+            LEVEL_TO_STRING_CACHE.put(level, levelName);
+        }
+        return levelName;
+    }
+
+    private static String getHeader(LogRecord record) {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(FORMAT.format(new Date(record.getMillis()))).append(":");
+        sb.append(levelToString(record.getLevel())).append(" ");
+
+        sb.append("[").append(Thread.currentThread().getName()).append("] ");
+
+        String loggerName = record.getLoggerName();
+        if (loggerName != null) {
+            sb.append("[").append(loggerName).append("]");
+        }
+        return sb.toString();
+    }
+
+    private class PrintWriterWithHeader extends PrintWriter {
+        private final ByteArrayOutputStream out;
+        private final String header;
+
+        public PrintWriterWithHeader(String header) {
+            this(header, new ByteArrayOutputStream());
+        }
+
+        public PrintWriterWithHeader(String header, ByteArrayOutputStream out) {
+            super(out, true);
+            this.header = header;
+            this.out = out;
+        }
+
+        @Override
+        public void println(Object x) {
+            print(header);
+            super.println(x);
+        }
+
+        @Override
+        public void println(String x) {
+            print(header);
+            super.println(x);
+        }
+
+        @Override
+        public String toString() {
+            return out.toString();
+        }
+    }
+
+    @Override
+    public String format(LogRecord record) {
+        Throwable thrown = record.getThrown();
+        String header = getHeader(record);
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(header);
+        sb.append(" ").append(formatMessage(record));
+        sb.append("\n");
+
+        // Print the exception here if we caught it
+        if (thrown != null) {
+
+            PrintWriter pw = new PrintWriterWithHeader(header);
+            thrown.printStackTrace(pw);
+            sb.append(pw.toString());
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
new file mode 100644
index 0000000..7cff67f
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.base.Preconditions;
+
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+
+import org.python.core.ArgParser;
+import org.python.core.PyInteger;
+import org.python.core.PyObject;
+import org.python.core.PyTuple;
+
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageOutputStream;
+
+/**
+ * Jython object to encapsulate images that have been taken.
+ */
+public abstract class MonkeyImage {
+    /**
+     * Convert the MonkeyImage into a BufferedImage.
+     *
+     * @return a BufferedImage for this MonkeyImage.
+     */
+    public abstract BufferedImage createBufferedImage();
+
+    // Cache the BufferedImage so we don't have to generate it every time.
+    private WeakReference<BufferedImage> cachedBufferedImage = null;
+
+    /**
+     * Utility method to handle getting the BufferedImage and managing the cache.
+     *
+     * @return the BufferedImage for this image.
+     */
+    private BufferedImage getBufferedImage() {
+        // Check the cache first
+        if (cachedBufferedImage != null) {
+            BufferedImage img = cachedBufferedImage.get();
+            if (img != null) {
+                return img;
+            }
+        }
+
+        // Not in the cache, so create it and cache it.
+        BufferedImage img = createBufferedImage();
+        cachedBufferedImage = new WeakReference<BufferedImage>(img);
+        return img;
+    }
+
+    @MonkeyRunnerExported(doc = "Encode the image into a format and return the bytes.",
+        args = {"format"},
+        argDocs = { "The (optional) format in which to encode the image (PNG for example)" },
+        returns = "A String containing the bytes.")
+    public byte[] convertToBytes(PyObject[] args, String[] kws) {
+      ArgParser ap = JythonUtils.createArgParser(args, kws);
+      Preconditions.checkNotNull(ap);
+
+      String format = ap.getString(0, "png");
+
+      BufferedImage argb = convertSnapshot();
+
+      ByteArrayOutputStream os = new ByteArrayOutputStream();
+      try {
+          ImageIO.write(argb, format, os);
+      } catch (IOException e) {
+          return new byte[0];
+      }
+      return os.toByteArray();
+    }
+
+    @MonkeyRunnerExported(doc = "Write out the file to the specified location.  If no " +
+            "format is specified, this function tries to guess at the output format " +
+            "depending on the file extension given.  If unable to determine, it uses PNG.",
+            args = {"path", "format"},
+            argDocs = {"Where to write out the file",
+                       "The format in which to encode the image (PNG for example)"},
+            returns = "True if writing succeeded.")
+    public boolean writeToFile(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String path = ap.getString(0);
+        String format = ap.getString(1, null);
+
+        if (format != null) {
+            return writeToFile(path, format);
+        }
+        int offset = path.lastIndexOf('.');
+        if (offset < 0) {
+            return writeToFile(path, "png");
+        }
+        String ext = path.substring(offset + 1);
+        Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix(ext);
+        if (!writers.hasNext()) {
+            return writeToFile(path, "png");
+        }
+        ImageWriter writer = writers.next();
+        BufferedImage image = getBufferedImage();
+        try {
+            File f = new File(path);
+            f.delete();
+
+            ImageOutputStream outputStream = ImageIO.createImageOutputStream(f);
+            writer.setOutput(outputStream);
+
+            try {
+                writer.write(image);
+            } finally {
+                writer.dispose();
+                outputStream.flush();
+            }
+        } catch (IOException e) {
+            return false;
+        }
+        return true;
+    }
+
+    @MonkeyRunnerExported(doc = "Get a single ARGB pixel from the image",
+            args = { "x", "y" },
+            argDocs = { "the x offset of the pixel", "the y offset of the pixel" },
+            returns = "A tuple of (A, R, G, B) for the pixel")
+    public PyObject getRawPixel(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        int x = ap.getInt(0);
+        int y = ap.getInt(1);
+        int pixel = getPixel(x, y);
+        PyInteger a = new PyInteger((pixel & 0xFF000000) >> 24);
+        PyInteger r = new PyInteger((pixel & 0x00FF0000) >> 16);
+        PyInteger g = new PyInteger((pixel & 0x0000FF00) >> 8);
+        PyInteger b = new PyInteger((pixel & 0x000000FF) >> 0);
+        return new PyTuple(a, r, g ,b);
+    }
+
+    @MonkeyRunnerExported(doc = "Get a single ARGB pixel from the image",
+            args = { "x", "y" },
+            argDocs = { "the x offset of the pixel", "the y offset of the pixel" },
+            returns = "An integer for the ARGB pixel")
+    public int getRawPixelInt(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        int x = ap.getInt(0);
+        int y = ap.getInt(1);
+        return getPixel(x, y);
+    }
+
+    private int getPixel(int x, int y) {
+        BufferedImage image = getBufferedImage();
+        return image.getRGB(x, y);
+    }
+
+    private BufferedImage convertSnapshot() {
+        BufferedImage image = getBufferedImage();
+
+        // Convert the image to ARGB so ImageIO writes it out nicely
+        BufferedImage argb = new BufferedImage(image.getWidth(), image.getHeight(),
+                BufferedImage.TYPE_INT_ARGB);
+        Graphics g = argb.createGraphics();
+        g.drawImage(image, 0, 0, null);
+        g.dispose();
+        return argb;
+    }
+
+    public boolean writeToFile(String path, String format) {
+        BufferedImage argb = convertSnapshot();
+
+        try {
+            ImageIO.write(argb, format, new File(path));
+        } catch (IOException e) {
+            return false;
+        }
+        return true;
+    }
+
+    @MonkeyRunnerExported(doc = "Compare this image to the other image.",
+            args = {"other", "percent"},
+            argDocs = {"The other image.",
+                       "A float from 0.0 to 1.0 indicating the percentage " +
+                           "of pixels that need to be the same.  Defaults to 1.0"},
+            returns = "True if they are the same image.")
+    public boolean sameAs(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        PyObject otherObject = ap.getPyObject(0);
+        MonkeyImage other = (MonkeyImage) otherObject.__tojava__(MonkeyImage.class);
+
+        double percent = JythonUtils.getFloat(ap, 1, 1.0);
+
+        BufferedImage otherImage = other.getBufferedImage();
+        BufferedImage myImage = getBufferedImage();
+
+        // Easy size check
+        if (otherImage.getWidth() != myImage.getWidth()) {
+            return false;
+        }
+        if (otherImage.getHeight() != myImage.getHeight()) {
+            return false;
+        }
+
+        int[] otherPixel = new int[1];
+        int[] myPixel = new int[1];
+
+        int width = myImage.getWidth();
+        int height = myImage.getHeight();
+
+        int numDiffPixels = 0;
+        // Now, go through pixel-by-pixel and check that the images are the same;
+        for (int y = 0; y < height; y++) {
+            for (int x = 0; x < width; x++) {
+                if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) {
+                    numDiffPixels++;
+                }
+            }
+        }
+        double numberPixels = (height * width);
+        double diffPercent = numDiffPixels / numberPixels;
+        return percent <= 1.0 - diffPercent;
+    }
+
+    private static class BufferedImageMonkeyImage extends MonkeyImage {
+        private final BufferedImage image;
+
+        public BufferedImageMonkeyImage(BufferedImage image) {
+            this.image = image;
+        }
+
+        @Override
+        public BufferedImage createBufferedImage() {
+            return image;
+        }
+
+    }
+
+    @MonkeyRunnerExported(doc = "Get a sub-image of this image.",
+            args = {"rect"},
+            argDocs = {"A Tuple of (x, y, w, h) representing the area of the image to extract."},
+            returns = "The newly extracted image.")
+    public MonkeyImage getSubImage(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        PyTuple rect = (PyTuple) ap.getPyObjectByType(0, PyTuple.TYPE);
+        int x = rect.__getitem__(0).asInt();
+        int y = rect.__getitem__(1).asInt();
+        int w = rect.__getitem__(2).asInt();
+        int h = rect.__getitem__(3).asInt();
+
+        BufferedImage image = getBufferedImage();
+        return new BufferedImageMonkeyImage(image.getSubimage(x, y, w, h));
+    }
+}
\ No newline at end of file
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java
new file mode 100644
index 0000000..11a2dd4
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.collect.Lists;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.Socket;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Provides a nicer interface to interacting with the low-level network access protocol for talking
+ * to the monkey.
+ *
+ * This class is thread-safe and can handle being called from multiple threads.
+ */
+public class MonkeyManager {
+    private static Logger LOG = Logger.getLogger(MonkeyManager.class.getName());
+
+    private Socket monkeySocket;
+    private BufferedWriter monkeyWriter;
+    private BufferedReader monkeyReader;
+
+    /**
+     * Create a new MonkeyMananger to talk to the specified device.
+     *
+     * @param monkeySocket the already connected socket on which to send protocol messages.
+     */
+    public MonkeyManager(Socket monkeySocket) {
+        try {
+            this.monkeySocket = monkeySocket;
+            monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream()));
+            monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream()));
+        } catch(IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Send a touch down event at the specified location.
+     *
+     * @param x the x coordinate of where to click
+     * @param y the y coordinate of where to click
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean touchDown(int x, int y) throws IOException {
+        return sendMonkeyEvent("touch down " + x + " " + y);
+    }
+
+    /**
+     * Send a touch down event at the specified location.
+     *
+     * @param x the x coordinate of where to click
+     * @param y the y coordinate of where to click
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean touchUp(int x, int y) throws IOException {
+        return sendMonkeyEvent("touch up " + x + " " + y);
+    }
+
+    /**
+     * Send a touch move event at the specified location.
+     *
+     * @param x the x coordinate of where to click
+     * @param y the y coordinate of where to click
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean touchMove(int x, int y) throws IOException {
+        return sendMonkeyEvent("touch move " + x + " " + y);
+    }
+
+    /**
+     * Send a touch (down and then up) event at the specified location.
+     *
+     * @param x the x coordinate of where to click
+     * @param y the y coordinate of where to click
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean touch(int x, int y) throws IOException {
+        return sendMonkeyEvent("tap " + x + " " + y);
+    }
+
+    /**
+     * Press a physical button on the device.
+     *
+     * @param name the name of the button (As specified in the protocol)
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean press(String name) throws IOException {
+        return sendMonkeyEvent("press " + name);
+    }
+
+    /**
+     * Send a Key Down event for the specified button.
+     *
+     * @param name the name of the button (As specified in the protocol)
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean keyDown(String name) throws IOException {
+        return sendMonkeyEvent("key down " + name);
+    }
+
+    /**
+     * Send a Key Up event for the specified button.
+     *
+     * @param name the name of the button (As specified in the protocol)
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean keyUp(String name) throws IOException {
+        return sendMonkeyEvent("key up " + name);
+    }
+
+    /**
+     * Press a physical button on the device.
+     *
+     * @param button the button to press
+     * @return success or not
+     * @throws IOException on error communicating with the device
+     */
+    public boolean press(PhysicalButton button) throws IOException {
+        return press(button.getKeyName());
+    }
+
+    /**
+     * This function allows the communication bridge between the host and the device
+     * to be invisible to the script for internal needs.
+     * It splits a command into monkey events and waits for responses for each over an adb tcp socket.
+     * Returns on an error, else continues and sets up last response.
+     *
+     * @param command the monkey command to send to the device
+     * @return the (unparsed) response returned from the monkey.
+     */
+    private String sendMonkeyEventAndGetResponse(String command) throws IOException {
+        command = command.trim();
+        LOG.info("Monkey Command: " + command + ".");
+
+        // send a single command and get the response
+        monkeyWriter.write(command + "\n");
+        monkeyWriter.flush();
+        return monkeyReader.readLine();
+    }
+
+    /**
+     * Parse a monkey response string to see if the command succeeded or not.
+     *
+     * @param monkeyResponse the response
+     * @return true if response code indicated success.
+     */
+    private boolean parseResponseForSuccess(String monkeyResponse) {
+        if (monkeyResponse == null) {
+            return false;
+        }
+        // return on ok
+        if(monkeyResponse.startsWith("OK")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Parse a monkey response string to get the extra data returned.
+     *
+     * @param monkeyResponse the response
+     * @return any extra data that was returned, or empty string if there was nothing.
+     */
+    private String parseResponseForExtra(String monkeyResponse) {
+        int offset = monkeyResponse.indexOf(':');
+        if (offset < 0) {
+            return "";
+        }
+        return monkeyResponse.substring(offset + 1);
+    }
+
+    /**
+     * This function allows the communication bridge between the host and the device
+     * to be invisible to the script for internal needs.
+     * It splits a command into monkey events and waits for responses for each over an
+     * adb tcp socket.
+     *
+     * @param command the monkey command to send to the device
+     * @return true on success.
+     */
+    private boolean sendMonkeyEvent(String command) throws IOException {
+        synchronized (this) {
+            String monkeyResponse = sendMonkeyEventAndGetResponse(command);
+            return parseResponseForSuccess(monkeyResponse);
+        }
+    }
+
+    /**
+     * Close all open resources related to this device.
+     */
+    public void close() {
+        try {
+            monkeySocket.close();
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to close monkeySocket", e);
+        }
+        try {
+            monkeyReader.close();
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to close monkeyReader", e);
+        }
+        try {
+            monkeyWriter.close();
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to close monkeyWriter", e);
+        }
+    }
+
+    /**
+     * Function to get a static variable from the device.
+     *
+     * @param name name of static variable to get
+     * @return the value of the variable, or null if there was an error
+     */
+    public String getVariable(String name) throws IOException {
+        synchronized (this) {
+            String response = sendMonkeyEventAndGetResponse("getvar " + name);
+            if (!parseResponseForSuccess(response)) {
+                return null;
+            }
+            return parseResponseForExtra(response);
+        }
+    }
+
+    /**
+     * Function to get the list of static variables from the device.
+     */
+    public Collection<String> listVariable() throws IOException {
+        synchronized (this) {
+            String response = sendMonkeyEventAndGetResponse("listvar");
+            if (!parseResponseForSuccess(response)) {
+                Collections.emptyList();
+            }
+            String extras = parseResponseForExtra(response);
+            return Lists.newArrayList(extras.split(" "));
+        }
+    }
+
+    /**
+     * Tells the monkey that we are done for this session.
+     * @throws IOException
+     */
+    public void done() throws IOException {
+        // this command just drops the connection, so handle it here
+        synchronized (this) {
+            sendMonkeyEventAndGetResponse("done");
+        }
+    }
+
+    /**
+     * Tells the monkey that we are done forever.
+     * @throws IOException
+     */
+    public void quit() throws IOException {
+        // this command drops the connection, so handle it here
+        synchronized (this) {
+            sendMonkeyEventAndGetResponse("quit");
+        }
+    }
+
+    /**
+     * Send a tap event at the specified location.
+     *
+     * @param x the x coordinate of where to click
+     * @param y the y coordinate of where to click
+     * @return success or not
+     * @throws IOException
+     * @throws IOException on error communicating with the device
+     */
+    public boolean tap(int x, int y) throws IOException {
+        return sendMonkeyEvent("tap " + x + " " + y);
+    }
+
+    /**
+     * Type the following string to the monkey.
+     *
+     * @param text the string to type
+     * @return success
+     * @throws IOException
+     */
+    public boolean type(String text) throws IOException {
+        // The network protocol can't handle embedded line breaks, so we have to handle it
+        // here instead
+        StringTokenizer tok = new StringTokenizer(text, "\n", true);
+        while (tok.hasMoreTokens()) {
+            String line = tok.nextToken();
+            if ("\n".equals(line)) {
+                boolean success = press(PhysicalButton.ENTER);
+                if (!success) {
+                    return false;
+                }
+            } else {
+                boolean success = sendMonkeyEvent("type " + line);
+                if (!success) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Type the character to the monkey.
+     *
+     * @param keyChar the character to type.
+     * @return success
+     * @throws IOException
+     */
+    public boolean type(char keyChar) throws IOException {
+        return type(Character.toString(keyChar));
+    }
+
+    /**
+     * Wake the device up from sleep.
+     * @throws IOException
+     */
+    public void wake() throws IOException {
+        sendMonkeyEvent("wake");
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRecorder.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRecorder.java
deleted file mode 100644
index f06eafd..0000000
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRecorder.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * 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.android.monkeyrunner;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-import org.jheer.XMLWriter;
-
-/**
- *  MonkeyRecorder is a host side class that records the output of scripts that are run. 
- *  It creates a unique directory, puts in an xml file that records each cmd and result.
- *  It stores every screenshot in this directory.
- *  When finished, it zips this all up.
- *
- *  Calling Sequence:
- *    mr = new MonkeyRecorder(scriptName);
- *    mr.startCommand();
- *    [mr.addAttribute(name, value);]
- *    ...
- *    [mr.addInput(cmd);]
- *    [mr.addResults(result, filename);]   // filename = "" if no screenshot
- *    mr.endCommand();
- *    mr.addComment(comment);
- *    mr.startCommand();
- *    ...
- *    mr.endCommand();
- *    ...
- *    mr.close();
- *
- *  With MonkeyRunner this should output an xml file, <script_name>-yyyyMMdd-HH:mm:ss.xml, into the
- *  directory out/<script_name>-yyyyMMdd-HH:mm:ss with the contents like:
- *
- *  <?xml version="1.0" encoding='UTF-8'?>
- *  <!-- Monkey Script Results -->
- *  <script_run script_name="filename" monkeyRunnerVersion="0.2">
- *    <!-- Device specific variables -->
- *    <device_var var_name="name" var_value="value" />
- *    <device_var name="build.display" value="opal-userdebug 1.6 DRC79 14207 test-keys"/>
- *    ...
- *    <!-- Script commands -->
- *    <command>
- *      dateTime="20090921-17:08:43"
- *      <input cmd="Pressing: menu"/>
- *      <response result="OK" dateTime="20090921-17:08:43"/>
- *    </command>
- *    ...
- *    <command>
- *      dateTime="20090921-17:09:44"
- *      <input cmd="grabscreen"/>
- *      <response result="OK" dateTime="20090921-17:09:45" screenshot="home_screen-20090921-17:09:45.png"/>
- *    </command>
- *    ...
- *  </script_run>
- *  
- *  And then zip it up with all the screenshots in the file: <script_name>-yyyyMMdd-HH:mm:ss.zip.
- */
- 
-public class MonkeyRecorder {
-
-  // xml file to store output results in
-  private static String mXmlFilename;
-  private static FileWriter mXmlFile;
-  private static XMLWriter mXmlWriter;
-  
-  // unique subdirectory to put results in (screenshots and xml file)
-  private static String mDirname;
-  private static List<String> mScreenShotNames = new ArrayList<String>();
-  
-  // where we store all the results for all the script runs
-  private static final String ROOT_DIR = "out";
-  
-  // for getting the date and time in now()
-  private static final SimpleDateFormat SIMPLE_DATE_TIME_FORMAT =
-      new SimpleDateFormat("yyyyMMdd-HH:mm:ss");
-  
-  /**
-   * Create a new MonkeyRecorder that records commands and zips up screenshots for submittal
-   * 
-   * @param scriptName filepath of the monkey script we are running
-   */
-  public MonkeyRecorder(String scriptName, String version) throws IOException {
-    // Create directory structure to store xml file, images and zips
-    File scriptFile = new File(scriptName);
-    scriptName = scriptFile.getName();  // Get rid of path
-    mDirname = ROOT_DIR + "/" + stripType(scriptName) + "-" + now();
-    new File(mDirname).mkdirs();
-    
-    // Initialize xml file
-    mXmlFilename = stampFilename(stripType(scriptName) + ".xml");
-    initXmlFile(scriptName, version);
-  }
-
-  // Get the current date and time in a simple string format (used for timestamping filenames)
-  private static String now() {
-    return SIMPLE_DATE_TIME_FORMAT.format(Calendar.getInstance().getTime());     
-  }
-  
-  /**
-   * Initialize the xml file writer
-   * 
-   * @param scriptName filename (not path) of the monkey script, stored as attribute in the xml file
-   * @param version of the monkey runner test system
-   */
-  private static void initXmlFile(String scriptName, String version) throws IOException {
-    String[] names = new String[] { "script_name", "monkeyRunnerVersion" };
-    String[] values = new String[] { scriptName, version };
-    mXmlFile = new FileWriter(mDirname + "/" + mXmlFilename);
-    mXmlWriter = new XMLWriter(mXmlFile);
-    mXmlWriter.begin();
-    mXmlWriter.comment("Monkey Script Results");
-    mXmlWriter.start("script_run", names, values, names.length);
-  }
-  
-  /**
-   * Add a comment to the xml file.
-   * 
-   * @param comment comment to add to the xml file
-   */
-  public static void addComment(String comment) throws IOException {
-    mXmlWriter.comment(comment);
-  }
-    
-  /**
-   * Begin writing a command xml element
-   */
-  public static void startCommand() throws IOException {
-    mXmlWriter.start("command", "dateTime", now());
-  }
-  
-  /**
-   * Write a command name attribute in a command xml element.  
-   * It's add as a sinlge script command could be multiple monkey commands.
-   * 
-   * @param cmd command sent to the monkey
-   */
-  public static void addInput(String cmd)  throws IOException {
-    String name = "cmd";
-    String value = cmd;
-    mXmlWriter.tag("input", name, value);
-  }
-  
-  /**
-   * Write a response xml element in a command.  
-   * Attributes include the monkey result, datetime, and possibly screenshot filename
-   * 
-   * @param result response of the monkey to the command
-   * @param filename filename of the screen shot (or other file to be included)
-   */
-  public static void addResult(String result, String filename) throws IOException {
-    int num_args = 2;
-    String[] names = new String[3];
-    String[] values = new String[3];
-    names[0] = "result";
-    values[0] = result;
-    names[1] = "dateTime";
-    values[1] = now();
-    if (filename.length() != 0) {
-      names[2] = "screenshot";
-      values[2] = stampFilename(filename); 
-      addScreenShot(filename);
-      num_args = 3;
-    }
-    mXmlWriter.tag("response", names, values, num_args); 
-  }
-  
-  /**
-   * Add an attribut to an open xml element. name="escaped_value"
-   * 
-   * @param name name of the attribute
-   * @param value value of the attribute
-   */
-  public static void addAttribute(String name, String value) throws IOException {
-    mXmlWriter.addAttribute(name, value);
-  }
-
-   /**
-   * Add an xml device variable element. name="escaped_value"
-   * 
-   * @param name name of the variable
-   * @param value value of the variable
-   */
-  public static void addDeviceVar(String name, String value) throws IOException {
-    String[] names = {"name", "value"};
-    String[] values = {name, value};
-    mXmlWriter.tag("device_var", names, values, names.length);
-  }
- 
-  /**
-   * Move the screenshot to storage and remember you did it so it can be zipped up later.
-   * 
-   * @param filename file name of the screenshot to be stored (Not path name)
-   */
-  private static void addScreenShot(String filename) {
-    File file = new File(filename);
-    String screenShotName = stampFilename(filename);
-    file.renameTo(new File(mDirname, screenShotName));
-    mScreenShotNames.add(screenShotName);
-  }
-
-  /**
-   * Finish writing a command xml element
-   */
-  public static void endCommand() throws IOException {
-    mXmlWriter.end();
-  }
-  
-  /**
-   * Add datetime in front of filetype (the stuff after and including the last infamous '.')
-   *
-   * @param filename path of file to be stamped
-   */
-  private static String stampFilename(String filename) {
-    // 
-    int typeIndex = filename.lastIndexOf('.');
-    if (typeIndex == -1) {
-      return filename + "-" + now();
-    }  
-    return filename.substring(0, typeIndex) + "-" + now() + filename.substring(typeIndex);
-  }
-  
-  /**
-   * Strip out the file type (the stuff after and including the last infamous '.')
-   *
-   * @param filename path of file to be stripped of type information
-   */
-   private static String stripType(String filename) {
-    // 
-    int typeIndex = filename.lastIndexOf('.');
-    if (typeIndex == -1)
-      return filename;
-    return filename.substring(0, typeIndex);
-  }
-
-  /**
-   * Close the monkeyRecorder by closing the xml file and zipping it up with the screenshots.
-   *
-   * @param filename path of file to be stripped of type information
-   */ 
-  public static void close() throws IOException {
-    // zip up xml file and screenshots into ROOT_DIR.
-    byte[] buf = new byte[1024];
-    String zipFileName = mXmlFilename + ".zip";
-    endCommand();
-    mXmlFile.close();
-    FileOutputStream zipFile = new FileOutputStream(ROOT_DIR + "/" + zipFileName);
-    ZipOutputStream out = new ZipOutputStream(zipFile);
-
-    // add the xml file
-    addFileToZip(out, mDirname + "/" + mXmlFilename, buf);
-    
-    // Add the screenshots
-    for (String filename : mScreenShotNames) {
-      addFileToZip(out, mDirname + "/" + filename, buf);
-    }
-    out.close();
-  }
-  
-  /**
-   * Helper function to zip up a file into an open zip archive.
-   *
-   * @param zip the stream of the zip archive
-   * @param filepath the filepath of the file to be added to the zip archive
-   * @param buf storage place to stage reads of file before zipping
-   */ 
-  private static void addFileToZip(ZipOutputStream zip, String filepath, byte[] buf) throws IOException {
-    FileInputStream in = new FileInputStream(filepath);
-    zip.putNextEntry(new ZipEntry(filepath));
-    int len;
-    while ((len = in.read(buf)) > 0) {
-      zip.write(buf, 0, len);
-    }
-    zip.closeEntry();
-    in.close();
-  }
-}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
index 4734ba1..cdab926 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,644 +13,202 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.monkeyrunner;
 
-import com.android.ddmlib.AndroidDebugBridge;
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.Log;
-import com.android.ddmlib.NullOutputReceiver;
-import com.android.ddmlib.RawImage;
-import com.android.ddmlib.Log.ILogOutput;
-import com.android.ddmlib.Log.LogLevel;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
 
-import java.awt.image.BufferedImage;
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
 
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
+import org.python.core.ArgParser;
+import org.python.core.PyException;
+import org.python.core.PyObject;
+
+import java.util.Collection;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.imageio.ImageIO;
+import javax.swing.JOptionPane;
 
 /**
- *  MonkeyRunner is a host side application to control a monkey instance on a
- *  device. MonkeyRunner provides some useful helper functions to control the
- *  device as well as various other methods to help script tests. 
+ * This is the main interface class into the jython bindings.
  */
 public class MonkeyRunner {
+    private static final Logger LOG = Logger.getLogger(MonkeyRunner.class.getCanonicalName());
+    private static MonkeyRunnerBackend backend;
 
-  static String monkeyServer = "127.0.0.1";
-  static int monkeyPort = 1080;
-  static Socket monkeySocket = null;
+    /**
+     * Set the backend MonkeyRunner is using.
+     *
+     * @param backend the backend to use.
+     */
+    /* package */ static void setBackend(MonkeyRunnerBackend backend) {
+        MonkeyRunner.backend = backend;
+    }
 
-  static IDevice monkeyDevice;
+    @MonkeyRunnerExported(doc = "Wait for the specified device to connect.",
+            args = {"timeout", "deviceId"},
+            argDocs = {"The timeout in seconds to wait for the device to connect. (default " +
+                "is to wait forever)",
+            "A regular expression that specifies the device of for valid devices" +
+                " to wait for."},
+    returns = "A MonkeyDevice representing the connected device.")
+    public static MonkeyDevice waitForConnection(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
 
-  static BufferedReader monkeyReader;
-  static BufferedWriter monkeyWriter;
-  static String monkeyResponse;
-
-  static MonkeyRecorder monkeyRecorder;
-
-  static String scriptName = null;
-  
-  // Obtain a suitable logger.
-  private static Logger logger = Logger.getLogger("com.android.monkeyrunner");
-
-  // delay between key events
-  final static int KEY_INPUT_DELAY = 1000;
-  
-  // version of monkey runner
-  final static String monkeyRunnerVersion = "0.4";
-
-  // TODO: interface cmd; class xml tags; fix logger; test class/script
-
-  public static void main(String[] args) throws IOException {
-
-    // haven't figure out how to get below INFO...bad parent.  Pass -v INFO to turn on logging 
-    logger.setLevel(Level.parse("WARNING"));  
-    processOptions(args);
-    
-    logger.info("initAdb");
-    initAdbConnection();
-    logger.info("openMonkeyConnection");
-    openMonkeyConnection();
-
-    logger.info("start_script");
-    start_script();
-    
-    logger.info("ScriptRunner.run");
-    ScriptRunner.run(scriptName);
-   
-    logger.info("end_script");
-    end_script();
-    logger.info("closeMonkeyConnection");
-    closeMonkeyConnection();  
-  }
-
-  /**
-   *  Initialize an adb session with a device connected to the host
-   * 
-   */
-  public static void initAdbConnection() {
-    String adbLocation = "adb";
-    boolean device = false;
-    boolean emulator = false;
-    String serial = null;
-
-    AndroidDebugBridge.init(false /* debugger support */);
-
-    try {
-      AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
-          adbLocation, true /* forceNewBridge */);
-
-      // we can't just ask for the device list right away, as the internal thread getting
-      // them from ADB may not be done getting the first list.
-      // Since we don't really want getDevices() to be blocking, we wait here manually.
-      int count = 0;
-      while (bridge.hasInitialDeviceList() == false) {
+        long timeoutMs;
         try {
-          Thread.sleep(100);
-          count++;
+            double timeoutInSecs = JythonUtils.getFloat(ap, 0);
+            timeoutMs = (long) (timeoutInSecs * 1000.0);
+        } catch (PyException e) {
+            timeoutMs = Long.MAX_VALUE;
+        }
+
+        return backend.waitForConnection(timeoutMs,
+                ap.getString(1, ".*"));
+    }
+
+    @MonkeyRunnerExported(doc = "Pause script processing for the specified number of seconds",
+            args = {"seconds"},
+            argDocs = {"The number of seconds to pause processing"})
+            public static void sleep(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        double seconds = JythonUtils.getFloat(ap, 0);
+
+        long ms = (long) (seconds * 1000.0);
+
+        try {
+            Thread.sleep(ms);
         } catch (InterruptedException e) {
-          // pass
+            LOG.log(Level.SEVERE, "Error sleeping", e);
         }
+    }
 
-        // let's not wait > 10 sec.
-        if (count > 100) {
-          System.err.println("Timeout getting device list!");
-          return;
-        }
-      }
+    @MonkeyRunnerExported(doc = "Simple help command to dump the MonkeyRunner supported " +
+            "commands",
+            args = { "format" },
+            argDocs = {"The format to return the help text in. (default is text)"},
+            returns = "The help text")
+    public static String help(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
 
-      // now get the devices
-      IDevice[] devices = bridge.getDevices();
+        String format = ap.getString(0, "text");
 
-      if (devices.length == 0) {
-        printAndExit("No devices found!", true /* terminate */);
-      }
+        return MonkeyRunnerHelp.helpString(format);
+    }
 
-      monkeyDevice = null;
+    @MonkeyRunnerExported(doc = "Put up an alert dialog to inform the user of something that " +
+            "happened.  This is modal dialog and will stop processing of " +
+            "the script until the user acknowledges the alert message",
+            args = { "message", "title", "okTitle" },
+            argDocs = {
+            "The contents of the message of the dialog box",
+            "The title to display for the dialog box.  (default value is \"Alert\")",
+            "The title to use for the acknowledgement button (default value is \"OK\")"
+    })
+    public static void alert(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
 
-      if (emulator || device) {
-        for (IDevice d : devices) {
-          // this test works because emulator and device can't both be true at the same
-          // time.
-          if (d.isEmulator() == emulator) {
-            // if we already found a valid target, we print an error and return.
-            if (monkeyDevice != null) {
-              if (emulator) {
-                printAndExit("Error: more than one emulator launched!",
-                    true /* terminate */);
-              } else {
-                printAndExit("Error: more than one device connected!",true /* terminate */);
-              }
+        String message = ap.getString(0);
+        String title = ap.getString(1, "Alert");
+        String buttonTitle = ap.getString(2, "OK");
+
+        alert(message, title, buttonTitle);
+    }
+
+    @MonkeyRunnerExported(doc = "Put up an input dialog that allows the user to input a string." +
+            "  This is a modal dialog that will stop processing of the script until the user " +
+            "inputs the requested information.",
+            args = {"message", "initialValue", "title", "okTitle", "cancelTitle"},
+            argDocs = {
+            "The message to display for the input.",
+            "The initial value to supply the user (default is empty string)",
+            "The title of the dialog box to display. (default is \"Input\")"
+    },
+    returns = "The test entered by the user, or None if the user canceled the input;"
+    )
+    public static String input(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String message = ap.getString(0);
+        String initialValue = ap.getString(1, "");
+        String title = ap.getString(2, "Input");
+
+        return input(message, initialValue, title);
+    }
+
+    @MonkeyRunnerExported(doc = "Put up a choice dialog that allows the user to select a single " +
+            "item from a list of items that were presented.",
+            args = {"message", "choices", "title"},
+            argDocs = {
+            "The message to display for the input.",
+            "The list of choices to display.",
+            "The title of the dialog box to display. (default is \"Input\")" },
+            returns = "The numeric offset of the choice selected.")
+    public static int choice(PyObject[] args, String kws[]) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+
+        String message = ap.getString(0);
+        Collection<String> choices = Collections2.transform(JythonUtils.getList(ap, 1),
+                Functions.toStringFunction());
+        String title = ap.getString(2, "Input");
+
+        return choice(message, title, choices);
+    }
+
+    /**
+     * Display an alert dialog.
+     *
+     * @param message the message to show.
+     * @param title the title of the dialog box.
+     * @param okTitle the title of the button.
+     */
+    private static void alert(String message, String title, String okTitle) {
+        Object[] options = { okTitle };
+        JOptionPane.showOptionDialog(null, message, title, JOptionPane.DEFAULT_OPTION,
+                JOptionPane.INFORMATION_MESSAGE, null, options, options[0]);
+    }
+
+    /**
+     * Display a dialog allow the user to pick a choice from a list of choices.
+     *
+     * @param message the message to show.
+     * @param title the title of the dialog box.
+     * @param choices the list of the choices to display.
+     * @return the index of the selected choice, or -1 if nothing was chosen.
+     */
+    private static int choice(String message, String title, Collection<String> choices) {
+        Object[] possibleValues = choices.toArray();
+        Object selectedValue = JOptionPane.showInputDialog(null, message, title,
+                JOptionPane.QUESTION_MESSAGE, null, possibleValues, possibleValues[0]);
+
+        for (int x = 0; x < possibleValues.length; x++) {
+            if (possibleValues[x].equals(selectedValue)) {
+                return x;
             }
-            monkeyDevice = d;
-          }
         }
-      } else if (serial != null) {
-        for (IDevice d : devices) {
-          if (serial.equals(d.getSerialNumber())) {
-            monkeyDevice = d;
-            break;
-          }
-        }
-      } else {
-        if (devices.length > 1) {
-          printAndExit("Error: more than one emulator or device available!",
-              true /* terminate */);
-        }
-        monkeyDevice = devices[0];
-      }
-
-      monkeyDevice.createForward(monkeyPort, monkeyPort);
-      String command = "monkey --port " + monkeyPort;
-      monkeyDevice.executeShellCommand(command, new NullOutputReceiver());
-
-    } catch(IOException e) {
-      e.printStackTrace();
-    }
-  }
-
-  /**
-   * Open a tcp session over adb with the device to communicate monkey commands
-   */
-  public static void openMonkeyConnection() {
-    try {
-      InetAddress addr = InetAddress.getByName(monkeyServer);
-      monkeySocket = new Socket(addr, monkeyPort);
-      monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream()));
-      monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream()));
-    } catch (UnknownHostException e) {
-      e.printStackTrace();
-    } catch(IOException e) {
-      e.printStackTrace();
-    }
-  }
-  
-  /** 
-   * Close tcp session with the monkey on the device
-   * 
-   */
-  public static void closeMonkeyConnection() {
-    try {
-      monkeyReader.close();
-      monkeyWriter.close();
-      monkeySocket.close();
-      AndroidDebugBridge.terminate();
-    } catch(IOException e) {
-      e.printStackTrace();
-    }
-  }
-
-  /** 
-   * This is a house cleaning routine to run before starting a script. Puts
-   * the device in a known state and starts recording interesting info.
-   */
-  public static void start_script() throws IOException {
-    press("menu", false);
-    press("menu", false);
-    press("home", false);
-    
-    // Start recording the script output, might want md5 signature of file for completeness
-    monkeyRecorder = new MonkeyRecorder(scriptName, monkeyRunnerVersion);
-
-    // Record what device we are running on
-    addDeviceVars();
-    monkeyRecorder.addComment("Script commands");
-  }
-
-  /** 
-   * This is a house cleaning routine to run after finishing a script.
-   * Puts the monkey server in a known state and closes the recording.
-   */
-  public static void end_script() throws IOException {
-    String command = "done";
-    sendMonkeyEvent(command, false, false);
-    
-    // Stop the recording and zip up the results
-    monkeyRecorder.close();
-  }
-
-  /** This is a method for scripts to launch an activity on the device
-   * 
-   * @param name The name of the activity to launch 
-   */
-  public static void launch_activity(String name) throws IOException {
-    System.out.println("Launching: " + name);
-    recordCommand("Launching: " + name);
-    monkeyDevice.executeShellCommand("am start -a android.intent.action.MAIN -n " 
-        + name, new NullOutputReceiver());
-    // void return, so no response given, just close the command element in the xml file.
-    monkeyRecorder.endCommand();
-   }
-
-  /**
-   * Grabs the current state of the screen stores it as a png
-   * 
-   * @param tag filename or tag descriptor of the screenshot
-   */
-  public static void grabscreen(String tag) throws IOException {
-    tag += ".png";
-
-    try {
-      Thread.sleep(1000);
-      getDeviceImage(monkeyDevice, tag, false);
-    } catch (InterruptedException e) {
-    }
-  }
-
-  /**
-   * Sleeper method for script to call
-   * 
-   * @param msec msecs to sleep for
-   */
-  public static void sleep(int msec) throws IOException {
-    try {
-      recordCommand("sleep: " + msec);
-      Thread.sleep(msec);
-      recordResponse("OK");
-    } catch (InterruptedException e) {
-      e.printStackTrace();
-    }
-  }
-
-  /**
-   * Tap function for scripts to call at a particular x and y location
-   * 
-   * @param x x-coordinate
-   * @param y y-coordinate
-   */
-  public static boolean tap(int x, int y) throws IOException {
-    String command = "tap " + x + " " + y;
-    boolean result = sendMonkeyEvent(command);
-    return result;
-  }
-
-  /** 
-   * Press function for scripts to call on a particular button or key
-   * 
-   * @param key key to press
-   */
-  public static boolean press(String key) throws IOException {
-    return press(key, true);
-  }
-
-  /** 
-   * Press function for scripts to call on a particular button or key
-   * 
-   * @param key key to press
-   * @param print whether to send output to user
-   */
-  private static boolean press(String key, boolean print) throws IOException {
-    String command = "press " + key;
-    boolean result = sendMonkeyEvent(command, print, true);
-    return result;
-  }
-
-  /**
-   * dpad down function
-   */
-  public static boolean down() throws IOException {
-    return press("dpad_down");
-  }
-
-  /**
-   * dpad up function
-   */
-  public static boolean up() throws IOException {
-    return press("dpad_up");
-  }
-
-  /**
-   * Function to type text on the device
-   * 
-   * @param text text to type
-   */
-  public static boolean type(String text) throws IOException {
-    boolean result = false;
-    // text might have line ends, which signal new monkey command, so we have to eat and reissue
-    String[] lines = text.split("[\\r\\n]+");
-    for (String line: lines) {
-      result = sendMonkeyEvent("type " + line + "\n");
-    }
-    // return last result.  Should never fail..?
-    return result;
-  }
-  
-  /**
-   * Function to get a static variable from the device
-   * 
-   * @param name name of static variable to get
-   */
-  public static boolean getvar(String name) throws IOException {
-    return sendMonkeyEvent("getvar " + name + "\n");
-  }
-
-  /**
-   * Function to get the list of static variables from the device
-   */
-  public static boolean listvar() throws IOException {
-    return sendMonkeyEvent("listvar \n");
-  }
-
-  /**
-   * This function is the communication bridge between the host and the device.
-   * It sends monkey events and waits for responses over the adb tcp socket.
-   * This version if for all scripted events so that they get recorded and reported to user.
-   * 
-   * @param command the monkey command to send to the device
-   */
-  private static boolean sendMonkeyEvent(String command) throws IOException {
-    return sendMonkeyEvent(command, true, true);
-  }
-
-  /**
-   * This function allows the communication bridge between the host and the device
-   * to be invisible to the script for internal needs.
-   * It splits a command into monkey events and waits for responses for each over an adb tcp socket.
-   * Returns on an error, else continues and sets up last response.
-   * 
-   * @param command the monkey command to send to the device
-   * @param print whether to print out the responses to the user
-   * @param record whether to put the command in the xml file that stores test outputs
-   */
-  private static boolean sendMonkeyEvent(String command, Boolean print, Boolean record) throws IOException {
-    command = command.trim();
-    if (print)
-      System.out.println("MonkeyCommand: " + command);
-    if (record)
-      recordCommand(command);
-    logger.info("Monkey Command: " + command + ".");
-      
-    // send a single command and get the response
-    monkeyWriter.write(command + "\n");
-    monkeyWriter.flush();
-    monkeyResponse = monkeyReader.readLine();
-
-    if(monkeyResponse != null) {
-      // if a command returns with a response
-      if (print)
-        System.out.println("MonkeyServer: " + monkeyResponse);
-      if (record)
-        recordResponse(monkeyResponse);
-      logger.info("Monkey Response: " + monkeyResponse + ".");
-
-      // return on error
-      if (monkeyResponse.startsWith("ERROR"))
-        return false;
-
-      // return on ok
-      if(monkeyResponse.startsWith("OK"))
-        return true;
-
-      // return on something else?
-      return false;
-    }
-    // didn't get a response...
-    if (print)
-      System.out.println("MonkeyServer: ??no response");
-    if (record)
-      recordResponse("??no response");
-    logger.info("Monkey Response: ??no response.");
-
-    //return on no response
-    return false;
-  }
-
-  /**
-   * Record the command in the xml file
-   *
-   * @param command the command sent to the monkey server
-   */
-  private static void recordCommand(String command) throws IOException {
-    if (monkeyRecorder != null) {                       // don't record setup junk
-      monkeyRecorder.startCommand();
-      monkeyRecorder.addInput(command);
-    }
-  }
-  
-  /**
-   * Record the response in the xml file
-   *
-   * @param response the response sent by the monkey server
-   */
-  private static void recordResponse(String response) throws IOException {
-    recordResponse(response, "");
-  } 
-  
-  /**
-   * Record the response and the filename in the xml file, store the file (to be zipped up later)
-   *
-   * @param response the response sent by the monkey server
-   * @param filename the filename of a file to be time stamped, recorded in the xml file and stored
-   */
-  private static void recordResponse(String response, String filename) throws IOException {
-    if (monkeyRecorder != null) {                    // don't record setup junk
-      monkeyRecorder.addResult(response, filename);  // ignores file if filename empty
-      monkeyRecorder.endCommand();
-    }
-  }
-    
-  /**
-   * Add the device variables to the xml file in monkeyRecorder.
-   * The results get added as device_var tags in the script_run tag
-   */
-  private static void addDeviceVars() throws IOException {
-    monkeyRecorder.addComment("Device specific variables");
-    sendMonkeyEvent("listvar \n", false, false);
-    if (monkeyResponse.startsWith("OK:")) {
-      // peel off "OK:" string and get the individual var names  
-      String[] varNames = monkeyResponse.substring(3).split("\\s+");
-      // grab all the individual var values
-      for (String name: varNames) {
-        sendMonkeyEvent("getvar " + name, false, false);
-        if(monkeyResponse != null) {
-          if (monkeyResponse.startsWith("OK") ) {
-            if (monkeyResponse.length() > 2) {
-              monkeyRecorder.addDeviceVar(name, monkeyResponse.substring(3));
-            } else { 
-              // only got OK - good variable but no value
-              monkeyRecorder.addDeviceVar(name, "null");
-            }
-          } else { 
-            // error returned - couldn't get var value for name... include error return
-            monkeyRecorder.addDeviceVar(name, monkeyResponse);
-          }
-        } else { 
-          // no monkeyResponse - bad variable with no value
-          monkeyRecorder.addDeviceVar(name, "null");
-        }
-      }
-    } else {
-      // it's an error, can't find variable names...
-      monkeyRecorder.addAttribute("listvar", monkeyResponse);
-    }
-  }
-  
-  /**
-   * Process the command-line options
-   *
-   * @return Returns true if options were parsed with no apparent errors.
-   */
-  private static void processOptions(String[] args) {
-    // parse command line parameters.
-    int index = 0;
-
-    do {
-      String argument = args[index++];
-
-      if ("-s".equals(argument)) {
-        if(index == args.length) {
-          printUsageAndQuit("Missing Server after -s");
-        }
-
-        monkeyServer = args[index++];
-
-      } else if ("-p".equals(argument)) {
-        // quick check on the next argument.
-        if (index == args.length) {
-          printUsageAndQuit("Missing Server port after -p");
-        }
-
-        monkeyPort = Integer.parseInt(args[index++]);
-
-      } else if ("-v".equals(argument)) {
-        // quick check on the next argument.
-        if (index == args.length) {
-          printUsageAndQuit("Missing Log Level after -v");
-        }
-
-        Level level = Level.parse(args[index++]);
-        logger.setLevel(level);
-        level = logger.getLevel();
-        System.out.println("Log level set to: " + level + "(" + level.intValue() + ").");
-        System.out.println("Warning: Log levels below INFO(800) not working currently... parent issues");
-        
-      } else if (argument.startsWith("-")) {
-        // we have an unrecognized argument.
-        printUsageAndQuit("Unrecognized argument: " + argument + ".");
-
-        monkeyPort = Integer.parseInt(args[index++]);
-
-      } else {
-        // get the filepath of the script to run.  This will be the last undashed argument.
-        scriptName = argument;
-      }
-    } while (index < args.length);
-  }
-
-  /*
-   * Grab an image from an ADB-connected device.
-   */
-  private static void getDeviceImage(IDevice device, String filepath, boolean landscape)
-  throws IOException {
-    RawImage rawImage;
-    recordCommand("grabscreen");
-    System.out.println("Grabbing Screeshot: " + filepath + ".");
-
-    try {
-      rawImage = device.getScreenshot();
-    }
-    catch (IOException ioe) {
-      recordResponse("No frame buffer", "");
-      printAndExit("Unable to get frame buffer: " + ioe.getMessage(), true /* terminate */);
-      return;
+        // Error
+        return -1;
     }
 
-    // device/adb not available?
-    if (rawImage == null) {
-      recordResponse("No image", "");
-      return;
+    /**
+     * Display a dialog that allows the user to input a text string.
+     *
+     * @param message the message to show.
+     * @param initialValue the initial value to display in the dialog
+     * @param title the title of the dialog box.
+     * @return the entered string, or null if cancelled
+     */
+    private static String input(String message, String initialValue, String title) {
+        return (String) JOptionPane.showInputDialog(null, message, title,
+                JOptionPane.QUESTION_MESSAGE, null, null, initialValue);
     }
-    
-    assert rawImage.bpp == 16;
-
-    BufferedImage image;
-    
-    logger.info("Raw Image - height: " + rawImage.height + ", width: " + rawImage.width);
-
-    if (landscape) {
-      // convert raw data to an Image
-      image = new BufferedImage(rawImage.height, rawImage.width,
-          BufferedImage.TYPE_INT_ARGB);
-
-      byte[] buffer = rawImage.data;
-      int index = 0;
-      for (int y = 0 ; y < rawImage.height ; y++) {
-        for (int x = 0 ; x < rawImage.width ; x++) {
-
-          int value = buffer[index++] & 0x00FF;
-          value |= (buffer[index++] << 8) & 0x0FF00;
-
-          int r = ((value >> 11) & 0x01F) << 3;
-          int g = ((value >> 5) & 0x03F) << 2;
-          int b = ((value >> 0) & 0x01F) << 3;
-
-          value = 0xFF << 24 | r << 16 | g << 8 | b;
-
-          image.setRGB(y, rawImage.width - x - 1, value);
-        }
-      }
-    } else {
-      // convert raw data to an Image
-      image = new BufferedImage(rawImage.width, rawImage.height,
-          BufferedImage.TYPE_INT_ARGB);
-
-      byte[] buffer = rawImage.data;
-      int index = 0;
-      for (int y = 0 ; y < rawImage.height ; y++) {
-        for (int x = 0 ; x < rawImage.width ; x++) {
-
-          int value = buffer[index++] & 0x00FF;
-          value |= (buffer[index++] << 8) & 0x0FF00;
-
-          int r = ((value >> 11) & 0x01F) << 3;
-          int g = ((value >> 5) & 0x03F) << 2;
-          int b = ((value >> 0) & 0x01F) << 3;
-
-          value = 0xFF << 24 | r << 16 | g << 8 | b;
-
-          image.setRGB(x, y, value);
-        }
-      }
-    }
-
-    if (!ImageIO.write(image, "png", new File(filepath))) {
-      recordResponse("No png writer", "");
-      throw new IOException("Failed to find png writer");
-    }
-    recordResponse("OK", filepath);
-  }
-
-  private static void printUsageAndQuit(String message) {
-    // 80 cols marker:  01234567890123456789012345678901234567890123456789012345678901234567890123456789
-    System.out.println(message);
-    System.out.println("Usage: monkeyrunner [options] SCRIPT_FILE");
-    System.out.println("");
-    System.out.println("    -s      MonkeyServer IP Address.");
-    System.out.println("    -p      MonkeyServer TCP Port.");
-    System.out.println("    -v      MonkeyServer Logging level (ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF)");
-    System.out.println("");
-    System.out.println("");
-
-    System.exit(1);
-  }
-
-  private static void printAndExit(String message, boolean terminate) {
-    System.out.println(message);
-    if (terminate) {
-      AndroidDebugBridge.terminate();
-    }
-    System.exit(1);
-  }
 }
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerBackend.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerBackend.java
new file mode 100644
index 0000000..216d214
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerBackend.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+/**
+ * Interface between MonkeyRunner common code and the MonkeyRunner backend.  The backend is
+ * responsible for communicating between the host and the device.
+ */
+public interface MonkeyRunnerBackend {
+    /**
+     * Wait for a device to connect to the backend.
+     *
+     * @param timeoutMs how long (in ms) to wait
+     * @param deviceIdRegex the regular expression to specify which device to wait for.
+     * @return the connected device (or null if timeout);
+     */
+    MonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex);
+
+    /**
+     * Shutdown the backend and cleanup any resources it was using.
+     */
+    void shutdown();
+}
\ No newline at end of file
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java
new file mode 100644
index 0000000..8dbe85b
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Resources;
+
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+
+import org.clearsilver.CS;
+import org.clearsilver.CSFileLoader;
+import org.clearsilver.HDF;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility class for generating inline help documentation
+ */
+public final class MonkeyRunnerHelp {
+    private MonkeyRunnerHelp() { }
+
+    private static final String HELP = "help";
+    private static final String NAME = "name";
+    private static final String DOC = "doc";
+    private static final String ARGUMENT = "argument";
+    private static final String RETURNS = "returns";
+    private static final String TYPE = "type";
+
+    // Enum used to describe documented types.
+    private enum Type {
+        ENUM, FIELD, METHOD
+    }
+
+    private static void getAllExportedClasses(Set<Field> fields,
+            Set<Method> methods,
+            Set<Constructor<?>> constructors,
+            Set<Class<?>> enums) {
+        final Set<Class<?>> classesVisited = Sets.newHashSet();
+        Set<Class<?>> classesToVisit = Sets.newHashSet();
+        classesToVisit.add(MonkeyRunner.class);
+
+        Predicate<Class<?>> haventSeen = new Predicate<Class<?>>() {
+            public boolean apply(Class<?> clz) {
+                return !classesVisited.contains(clz);
+            }
+        };
+
+        while (!classesToVisit.isEmpty()) {
+            classesVisited.addAll(classesToVisit);
+
+            List<Class<?>> newClasses = Lists.newArrayList();
+            for (Class<?> clz : classesToVisit) {
+                // See if the class itself is annotated and is an enum
+                if (clz.isEnum() && clz.isAnnotationPresent(MonkeyRunnerExported.class)) {
+                    enums.add(clz);
+                }
+
+                // Constructors
+                for (Constructor<?> c : clz.getConstructors()) {
+                    newClasses.addAll(Collections2.filter(Arrays.asList(c.getParameterTypes()),
+                            haventSeen));
+                    if (c.isAnnotationPresent(MonkeyRunnerExported.class)) {
+                        constructors.add(c);
+                    }
+                }
+
+                // Fields
+                for (Field f : clz.getFields()) {
+                    if (haventSeen.apply(f.getClass())) {
+                        newClasses.add(f.getClass());
+                    }
+                    if (f.isAnnotationPresent(MonkeyRunnerExported.class)) {
+                        fields.add(f);
+                    }
+                }
+
+                // Methods
+                for (Method m : clz.getMethods()) {
+                    newClasses.addAll(Collections2.filter(Arrays.asList(m.getParameterTypes()),
+                            haventSeen));
+                    if (haventSeen.apply(m.getReturnType())) {
+                        newClasses.add(m.getReturnType());
+                    }
+
+                    if (m.isAnnotationPresent(MonkeyRunnerExported.class)) {
+                        methods.add(m);
+                    }
+                }
+
+                // Containing classes
+                for (Class<?> toAdd : clz.getClasses()) {
+                    if (haventSeen.apply(toAdd)) {
+                        newClasses.add(toAdd);
+                    }
+                }
+            }
+
+            classesToVisit.clear();
+            classesToVisit.addAll(newClasses);
+        }
+    }
+
+    private static Comparator<Member> MEMBER_SORTER = new Comparator<Member>() {
+        public int compare(Member o1, Member o2) {
+            return o1.getName().compareTo(o2.getName());
+        }
+    };
+
+    private static Comparator<Class<?>> CLASS_SORTER = new Comparator<Class<?>>() {
+        public int compare(Class<?> o1, Class<?> o2) {
+            return o1.getName().compareTo(o2.getName());
+        }
+    };
+
+    public static String helpString(String format) {
+        // Quick check for support formats
+        if ("html".equals(format) || "text".equals(format)) {
+            HDF hdf = buildHelpHdf();
+            CS clearsilver = new CS(hdf);
+            // Set a custom file loader to load requested files from resources relative to this class.
+            clearsilver.setFileLoader(new CSFileLoader() {
+                public String load(HDF hdf, String filename) throws IOException {
+                    return Resources.toString(Resources.getResource(MonkeyRunnerHelp.class, filename),
+                            Charset.defaultCharset());
+                }
+            });
+
+            // Load up the CS template file
+            clearsilver.parseFile(format.toLowerCase() + ".cs");
+            // And render the output
+            return clearsilver.render();
+        } else if ("hdf".equals(format)) {
+            HDF hdf = buildHelpHdf();
+            return hdf.writeString();
+        }
+        return "";
+    }
+
+    private static HDF buildHelpHdf() {
+        HDF hdf = new HDF();
+
+        int outputItemCount = 0;
+
+        Set<Field> fields = Sets.newTreeSet(MEMBER_SORTER);
+        Set<Method> methods = Sets.newTreeSet(MEMBER_SORTER);
+        Set<Constructor<?>> constructors = Sets.newTreeSet(MEMBER_SORTER);
+        Set<Class<?>> classes = Sets.newTreeSet(CLASS_SORTER);
+        getAllExportedClasses(fields, methods, constructors, classes);
+
+        for (Class<?> clz : classes) {
+            String prefix = HELP + "." + outputItemCount + ".";
+
+            hdf.setValue(prefix + NAME, clz.getCanonicalName());
+            MonkeyRunnerExported annotation = clz.getAnnotation(MonkeyRunnerExported.class);
+            hdf.setValue(prefix + DOC, annotation.doc());
+            hdf.setValue(prefix + TYPE, Type.ENUM.name());
+
+            // Now go through the enumeration constants
+            Object[] constants = clz.getEnumConstants();
+            String[] argDocs = annotation.argDocs();
+            if (constants.length > 0) {
+                for (int x = 0; x < constants.length; x++) {
+                    String argPrefix = prefix + ARGUMENT + "." + x + ".";
+                    hdf.setValue(argPrefix + NAME, constants[x].toString());
+                    if (argDocs.length > x) {
+                        hdf.setValue(argPrefix + DOC, argDocs[x]);
+                    }
+                }
+            }
+            outputItemCount++;
+        }
+
+        for (Method m : methods) {
+            String prefix = HELP + "." + outputItemCount + ".";
+
+            MonkeyRunnerExported annotation = m.getAnnotation(MonkeyRunnerExported.class);
+            String className = m.getDeclaringClass().getCanonicalName();
+            String methodName = className + "." + m.getName();
+            hdf.setValue(prefix + NAME, methodName);
+            hdf.setValue(prefix + DOC, annotation.doc());
+            if (annotation.args().length > 0) {
+                String[] argDocs = annotation.argDocs();
+                String[] aargs = annotation.args();
+                for (int x = 0; x < aargs.length; x++) {
+                    String argPrefix = prefix + ARGUMENT + "." + x + ".";
+
+                    hdf.setValue(argPrefix + NAME, aargs[x]);
+                    if (argDocs.length > x) {
+                        hdf.setValue(argPrefix + DOC, argDocs[x]);
+                    }
+                }
+            }
+            if (!"".equals(annotation.returns())) {
+                hdf.setValue(prefix + RETURNS, annotation.returns());
+            }
+            outputItemCount++;
+        }
+
+        return hdf;
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
new file mode 100644
index 0000000..cf193c2
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class MonkeyRunnerOptions {
+    private static final Logger LOG = Logger.getLogger(MonkeyRunnerOptions.class.getName());
+    private static String DEFAULT_MONKEY_SERVER_ADDRESS = "127.0.0.1";
+    private static int DEFAULT_MONKEY_PORT = 12345;
+
+    private final int port;
+    private final String hostname;
+    private final File scriptFile;
+    private final String backend;
+    private final Collection<File> plugins;
+    private final Collection<String> arguments;
+
+    private MonkeyRunnerOptions(String hostname, int port, File scriptFile, String backend,
+            Collection<File> plugins, Collection<String> arguments) {
+        this.hostname = hostname;
+        this.port = port;
+        this.scriptFile = scriptFile;
+        this.backend = backend;
+        this.plugins = plugins;
+        this.arguments = arguments;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public File getScriptFile() {
+        return scriptFile;
+    }
+
+    public String getBackendName() {
+        return backend;
+    }
+
+    public Collection<File> getPlugins() {
+        return plugins;
+    }
+
+    public Collection<String> getArguments() {
+        return arguments;
+    }
+
+    private static void printUsage(String message) {
+        System.out.println(message);
+        System.out.println("Usage: monkeyrunner [options] SCRIPT_FILE");
+        System.out.println("");
+        System.out.println("    -s      MonkeyServer IP Address.");
+        System.out.println("    -p      MonkeyServer TCP Port.");
+        System.out.println("    -v      MonkeyServer Logging level (ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF)");
+        System.out.println("");
+        System.out.println("");
+    }
+
+    /**
+     * Process the command-line options
+     *
+     * @return the parsed options, or null if there was an error.
+     */
+    public static MonkeyRunnerOptions processOptions(String[] args) {
+        // parse command line parameters.
+        int index = 0;
+
+        String hostname = DEFAULT_MONKEY_SERVER_ADDRESS;
+        File scriptFile = null;
+        int port = DEFAULT_MONKEY_PORT;
+        String backend = "adb";
+
+        ImmutableList.Builder<File> pluginListBuilder = ImmutableList.builder();
+        ImmutableList.Builder<String> argumentBuilder = ImmutableList.builder();
+        while (index < args.length) {
+            String argument = args[index++];
+
+            if ("-s".equals(argument)) {
+                if (index == args.length) {
+                    printUsage("Missing Server after -s");
+                    return null;
+                }
+                hostname = args[index++];
+
+            } else if ("-p".equals(argument)) {
+                // quick check on the next argument.
+                if (index == args.length) {
+                    printUsage("Missing Server port after -p");
+                    return null;
+                }
+                port = Integer.parseInt(args[index++]);
+
+            } else if ("-v".equals(argument)) {
+                // quick check on the next argument.
+                if (index == args.length) {
+                    printUsage("Missing Log Level after -v");
+                    return null;
+                }
+
+                Level level = Level.parse(args[index++]);
+                LOG.setLevel(level);
+                level = LOG.getLevel();
+                System.out.println("Log level set to: " + level + "(" + level.intValue() + ").");
+                System.out.println("Warning: Log levels below INFO(800) not working currently... parent issues");
+            } else if ("-be".equals(argument)) {
+                // quick check on the next argument.
+                if (index == args.length) {
+                    printUsage("Missing backend name after -be");
+                    return null;
+                }
+                backend = args[index++];
+            } else if ("-plugin".equals(argument)) {
+                // quick check on the next argument.
+                if (index == args.length) {
+                    printUsage("Missing plugin path after -plugin");
+                    return null;
+                }
+                File plugin = new File(args[index++]);
+                if (!plugin.exists()) {
+                    printUsage("Plugin file doesn't exist");
+                    return null;
+                }
+
+                if (!plugin.canRead()) {
+                    printUsage("Can't read plugin file");
+                    return null;
+                }
+
+                pluginListBuilder.add(plugin);
+            } else if (argument.startsWith("-") &&
+                // Once we have the scriptfile, the rest of the arguments go to jython.
+                scriptFile == null) {
+                // we have an unrecognized argument.
+                printUsage("Unrecognized argument: " + argument + ".");
+                return null;
+            } else {
+                if (scriptFile == null) {
+                    // get the filepath of the script to run.  This will be the last undashed argument.
+                    scriptFile = new File(argument);
+                    if (!scriptFile.exists()) {
+                        printUsage("Can't open specified script file");
+                        return null;
+                    }
+                    if (!scriptFile.canRead()) {
+                        printUsage("Can't open specified script file");
+                        return null;
+                    }
+                } else {
+                    argumentBuilder.add(argument);
+                }
+            }
+        };
+
+        return new MonkeyRunnerOptions(hostname, port, scriptFile, backend,
+                pluginListBuilder.build(), argumentBuilder.build());
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
new file mode 100644
index 0000000..1f539ba
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+
+import com.android.monkeyrunner.adb.AdbBackend;
+import com.android.monkeyrunner.stub.StubBackend;
+
+import org.python.util.PythonInterpreter;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+/**
+ *  MonkeyRunner is a host side application to control a monkey instance on a
+ *  device. MonkeyRunner provides some useful helper functions to control the
+ *  device as well as various other methods to help script tests.  This class bootstraps
+ *  MonkeyRunner.
+ */
+public class MonkeyRunnerStarter {
+    private static final Logger LOG = Logger.getLogger(MonkeyRunnerStarter.class.getName());
+    private static final String MONKEY_RUNNER_MAIN_MANIFEST_NAME = "MonkeyRunnerStartupRunner";
+
+    private final MonkeyRunnerBackend backend;
+    private final MonkeyRunnerOptions options;
+
+    public MonkeyRunnerStarter(MonkeyRunnerOptions options) {
+        this.options = options;
+        this.backend = MonkeyRunnerStarter.createBackendByName(options.getBackendName());
+        if (this.backend == null) {
+           throw new RuntimeException("Unknown backend");
+        }
+    }
+
+
+    /**
+     * Creates a specific backend by name.
+     *
+     * @param backendName the name of the backend to create
+     * @return the new backend, or null if none were found.
+     */
+    public static MonkeyRunnerBackend createBackendByName(String backendName) {
+        if ("adb".equals(backendName)) {
+            return new AdbBackend();
+        } else if ("stub".equals(backendName)) {
+            return new StubBackend();
+        } else {
+            return null;
+        }
+    }
+
+    private int run() {
+        MonkeyRunner.setBackend(backend);
+        Map<String, Predicate<PythonInterpreter>> plugins = handlePlugins();
+        if (options.getScriptFile() == null) {
+            ScriptRunner.console();
+            return 0;
+        } else {
+            int error = ScriptRunner.run(options.getScriptFile().getAbsolutePath(),
+                    options.getArguments(), plugins);
+            backend.shutdown();
+            MonkeyRunner.setBackend(null);
+            return error;
+        }
+    }
+
+    private Predicate<PythonInterpreter> handlePlugin(File f) {
+        JarFile jarFile;
+        try {
+            jarFile = new JarFile(f);
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to open plugin file.  Is it a jar file? " +
+                    f.getAbsolutePath(), e);
+            return Predicates.alwaysFalse();
+        }
+        Manifest manifest;
+        try {
+            manifest = jarFile.getManifest();
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to get manifest file from jar: " +
+                    f.getAbsolutePath(), e);
+            return Predicates.alwaysFalse();
+        }
+        Attributes mainAttributes = manifest.getMainAttributes();
+        String pluginClass = mainAttributes.getValue(MONKEY_RUNNER_MAIN_MANIFEST_NAME);
+        if (pluginClass == null) {
+            // No main in this plugin, so it always succeeds.
+            return Predicates.alwaysTrue();
+        }
+        URL url;
+        try {
+            url =  f.toURI().toURL();
+        } catch (MalformedURLException e) {
+            LOG.log(Level.SEVERE, "Unable to convert file to url " + f.getAbsolutePath(),
+                    e);
+            return Predicates.alwaysFalse();
+        }
+        URLClassLoader classLoader = new URLClassLoader(new URL[] { url },
+                ClassLoader.getSystemClassLoader());
+        Class<?> clz;
+        try {
+            clz = Class.forName(pluginClass, true, classLoader);
+        } catch (ClassNotFoundException e) {
+            LOG.log(Level.SEVERE, "Unable to load the specified plugin: " + pluginClass, e);
+            return Predicates.alwaysFalse();
+        }
+        Object loadedObject;
+        try {
+            loadedObject = clz.newInstance();
+        } catch (InstantiationException e) {
+            LOG.log(Level.SEVERE, "Unable to load the specified plugin: " + pluginClass, e);
+            return Predicates.alwaysFalse();
+        } catch (IllegalAccessException e) {
+            LOG.log(Level.SEVERE, "Unable to load the specified plugin " +
+                    "(did you make it public?): " + pluginClass, e);
+            return Predicates.alwaysFalse();
+        }
+        // Cast it to the right type
+        if (loadedObject instanceof Runnable) {
+            final Runnable run = (Runnable) loadedObject;
+            return new Predicate<PythonInterpreter>() {
+                public boolean apply(PythonInterpreter i) {
+                    run.run();
+                    return true;
+                }
+            };
+        } else if (loadedObject instanceof Predicate<?>) {
+            return (Predicate<PythonInterpreter>) loadedObject;
+        } else {
+            LOG.severe("Unable to coerce object into correct type: " + pluginClass);
+            return Predicates.alwaysFalse();
+        }
+    }
+
+    private Map<String, Predicate<PythonInterpreter>> handlePlugins() {
+        ImmutableMap.Builder<String, Predicate<PythonInterpreter>> builder = ImmutableMap.builder();
+        for (File f : options.getPlugins()) {
+            builder.put(f.getAbsolutePath(), handlePlugin(f));
+        }
+        return builder.build();
+    }
+
+
+
+    private static final void replaceAllLogFormatters(Formatter form) {
+        LogManager mgr = LogManager.getLogManager();
+        Enumeration<String> loggerNames = mgr.getLoggerNames();
+        while (loggerNames.hasMoreElements()) {
+            String loggerName = loggerNames.nextElement();
+            Logger logger = mgr.getLogger(loggerName);
+            for (Handler handler : logger.getHandlers()) {
+                handler.setFormatter(form);
+                handler.setLevel(Level.INFO);
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        MonkeyRunnerOptions options = MonkeyRunnerOptions.processOptions(args);
+
+        // logging property files are difficult
+        replaceAllLogFormatters(MonkeyFormatter.DEFAULT_INSTANCE);
+
+        if (options == null) {
+            return;
+        }
+
+        MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options);
+        int error = runner.run();
+
+        // This will kill any background threads as well.
+        System.exit(error);
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/PhysicalButton.java b/tools/monkeyrunner/src/com/android/monkeyrunner/PhysicalButton.java
new file mode 100644
index 0000000..f0525a0
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/PhysicalButton.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+public enum PhysicalButton {
+    HOME("home"),
+    SEARCH("search"),
+    MENU("menu"),
+    BACK("back"),
+    DPAD_UP("DPAD_UP"),
+    DPAD_DOWN("DPAD_DOWN"),
+    DPAD_LEFT("DPAD_LEFT"),
+    DPAD_RIGHT("DPAD_RIGHT"),
+    DPAD_CENTER("DPAD_CENTER"),
+    ENTER("enter");
+
+    private String keyName;
+
+    private PhysicalButton(String keyName) {
+        this.keyName = keyName;
+    }
+
+    public String getKeyName() {
+        return keyName;
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
index 6a4405b..c247a5f 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
@@ -1,96 +1,177 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableMap.Builder;
+
 import org.python.core.Py;
+import org.python.core.PyException;
 import org.python.core.PyObject;
-import org.python.util.PythonInterpreter;
 import org.python.util.InteractiveConsole;
+import org.python.util.JLineConsole;
+import org.python.util.PythonInterpreter;
 
 import java.io.File;
-import java.io.IOException;
-import java.io.FileInputStream;
-import java.lang.RuntimeException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 
 /**
  * Runs Jython based scripts.
  */
 public class ScriptRunner {
+    private static final Logger LOG = Logger.getLogger(MonkeyRunnerOptions.class.getName());
 
-  /** The "this" scope object for scripts. */
-  private final Object scope;
-  private final String variable;
-  
-  /** Private constructor. */
-  private ScriptRunner(Object scope, String variable) {
-    this.scope = scope;
-    this.variable = variable;
-  }
-  
-  /** Creates a new instance for the given scope object. */
-  public static ScriptRunner newInstance(Object scope, String variable) {
-    return new ScriptRunner(scope, variable);
-  }
+    /** The "this" scope object for scripts. */
+    private final Object scope;
+    private final String variable;
 
-  /**
-   * Runs the specified Jython script. First runs the initialization script to
-   * preload the appropriate client library version.
-   */
-  public static void run(String scriptfilename) {
-    try {
-      initPython();
-      PythonInterpreter python = new PythonInterpreter();
-      
-      python.execfile(scriptfilename);
-    } catch(Exception e) {
-      e.printStackTrace();
+    /** Private constructor. */
+    private ScriptRunner(Object scope, String variable) {
+        this.scope = scope;
+        this.variable = variable;
     }
-  }
-  
 
-  /** Initialize the python interpreter. */
-  private static void initPython() {
-    Properties props = new Properties();
-    // Default is 'message' which displays sys-package-mgr bloat
-    // Choose one of error,warning,message,comment,debug
-    props.setProperty("python.verbose", "error");
-    props.setProperty("python.path", System.getProperty("java.class.path"));
-    PythonInterpreter.initialize(System.getProperties(), props, new String[] {""});
-  }
+    /** Creates a new instance for the given scope object. */
+    public static ScriptRunner newInstance(Object scope, String variable) {
+        return new ScriptRunner(scope, variable);
+    }
 
-  /**
-   * Create and run a console using a new python interpreter for the test
-   * associated with this instance.
-   */
-  public void console() throws IOException {
-    initPython();
-    InteractiveConsole python = new InteractiveConsole();
-    initInterpreter(python, scope, variable);
-    python.interact();
-  }
+    /**
+     * Runs the specified Jython script. First runs the initialization script to
+     * preload the appropriate client library version.
+     *
+     * @param scriptfilename the name of the file to run.
+     * @param args the arguments passed in (excluding the filename).
+     * @param plugins a list of plugins to load.
+     * @return the error code from running the script.
+     */
+    public static int run(String scriptfilename, Collection<String> args,
+            Map<String, Predicate<PythonInterpreter>> plugins) {
+        // Add the current directory of the script to the python.path search path.
+        File f = new File(scriptfilename);
 
-  /**
-   * Start an interactive python interpreter using the specified set of local
-   * variables. Use this to interrupt a running test script with a prompt:
-   * 
-   * @param locals
-   */
-  public static void console(PyObject locals) {
-    initPython();
-    InteractiveConsole python = new InteractiveConsole(locals);
-    python.interact();
-  }
+        // Adjust the classpath so jython can access the classes in the specified classpath.
+        Collection<String> classpath = Lists.newArrayList(f.getParent());
+        classpath.addAll(plugins.keySet());
 
-  /**
-   * Initialize a python interpreter.
-   * 
-   * @param python
-   * @param scope
-   * @throws IOException
-   */
-  public static void initInterpreter(PythonInterpreter python, Object scope, String variable) 
-      throws IOException {
-    // Store the current test case as the this variable
-    python.set(variable, scope);
-  }
+        String[] argv = new String[args.size() + 1];
+        argv[0] = f.getAbsolutePath();
+        int x = 1;
+        for (String arg : args) {
+            argv[x++] = arg;
+        }
+
+        initPython(classpath, argv);
+
+        PythonInterpreter python = new PythonInterpreter();
+
+        // Now let the mains run.
+        for (Map.Entry<String, Predicate<PythonInterpreter>> entry : plugins.entrySet()) {
+            boolean success;
+            try {
+                success = entry.getValue().apply(python);
+            } catch (Exception e) {
+                LOG.log(Level.SEVERE, "Plugin Main through an exception.", e);
+                continue;
+            }
+            if (!success) {
+                LOG.severe("Plugin Main returned error for: " + entry.getKey());
+            }
+        }
+
+        // Bind __name__ to __main__ so mains will run
+        python.set("__name__", "__main__");
+
+        try {
+          python.execfile(scriptfilename);
+        } catch (PyException e) {
+          if (Py.SystemExit.equals(e.type)) {
+            // Then recover the error code so we can pass it on
+            return (Integer) e.value.__tojava__(Integer.class);
+          }
+          // Then some other kind of exception was thrown.  Log it and return error;
+          LOG.log(Level.SEVERE, "Script terminated due to an exception", e);
+          return 1;
+        }
+        return 0;
+    }
+
+    public static void runString(String script) {
+        initPython();
+        PythonInterpreter python = new PythonInterpreter();
+        python.exec(script);
+    }
+
+    public static Map<String, PyObject> runStringAndGet(String script, String... names) {
+        return runStringAndGet(script, Arrays.asList(names));
+    }
+
+    public static Map<String, PyObject> runStringAndGet(String script, Collection<String> names) {
+        initPython();
+        final PythonInterpreter python = new PythonInterpreter();
+        python.exec(script);
+
+        Builder<String, PyObject> builder = ImmutableMap.builder();
+        for (String name : names) {
+            builder.put(name, python.get(name));
+        }
+        return builder.build();
+    }
+
+    private static void initPython() {
+        List<String> arg = Collections.emptyList();
+        initPython(arg, new String[] {""});
+    }
+
+    private static void initPython(Collection<String> pythonPath,
+            String[] argv) {
+        Properties props = new Properties();
+
+        // Build up the python.path
+        StringBuilder sb = new StringBuilder();
+        sb.append(System.getProperty("java.class.path"));
+        for (String p : pythonPath) {
+            sb.append(":").append(p);
+        }
+        props.setProperty("python.path", sb.toString());
+
+        /** Initialize the python interpreter. */
+        // Default is 'message' which displays sys-package-mgr bloat
+        // Choose one of error,warning,message,comment,debug
+        props.setProperty("python.verbose", "error");
+
+        PythonInterpreter.initialize(System.getProperties(), props, argv);
+    }
+
+    /**
+     * Start an interactive python interpreter.
+     */
+    public static void console() {
+        initPython();
+        InteractiveConsole python = new JLineConsole();
+        python.interact();
+    }
 }
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java
new file mode 100644
index 0000000..63badf5
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb;
+
+import com.google.common.collect.Lists;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
+import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.MonkeyRunnerBackend;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+/**
+ * Backend implementation that works over ADB to talk to the device.
+ */
+public class AdbBackend implements MonkeyRunnerBackend {
+    private static Logger LOG = Logger.getLogger(AdbBackend.class.getCanonicalName());
+    // How long to wait each time we check for the device to be connected.
+    private static final int CONNECTION_ITERATION_TIMEOUT_MS = 200;
+    private final List<AdbMonkeyDevice> devices = Lists.newArrayList();
+
+    private final AndroidDebugBridge bridge;
+
+    public AdbBackend() {
+        AndroidDebugBridge.init(false /* debugger support */);
+
+        bridge = AndroidDebugBridge.createBridge(
+                "adb", true /* forceNewBridge */);
+    }
+
+    /**
+     * Checks the attached devices looking for one whose device id matches the specified regex.
+     *
+     * @param deviceIdRegex the regular expression to match against
+     * @return the Device (if found), or null (if not found).
+     */
+    private IDevice findAttacedDevice(String deviceIdRegex) {
+        Pattern pattern = Pattern.compile(deviceIdRegex);
+        for (IDevice device : bridge.getDevices()) {
+            String serialNumber = device.getSerialNumber();
+            if (pattern.matcher(serialNumber).matches()) {
+                return device;
+            }
+        }
+        return null;
+    }
+
+    public MonkeyDevice waitForConnection() {
+        return waitForConnection(Integer.MAX_VALUE, ".*");
+    }
+
+    public MonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex) {
+        do {
+            IDevice device = findAttacedDevice(deviceIdRegex);
+            if (device != null) {
+                AdbMonkeyDevice amd = new AdbMonkeyDevice(device);
+                devices.add(amd);
+                return amd;
+            }
+
+            try {
+                Thread.sleep(CONNECTION_ITERATION_TIMEOUT_MS);
+            } catch (InterruptedException e) {
+                LOG.log(Level.SEVERE, "Error sleeping", e);
+            }
+            timeoutMs -= CONNECTION_ITERATION_TIMEOUT_MS;
+        } while (timeoutMs > 0);
+
+        // Timeout.  Give up.
+        return null;
+    }
+
+    public void shutdown() {
+        for (AdbMonkeyDevice device : devices) {
+            device.dispose();
+        }
+        AndroidDebugBridge.terminate();
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java
new file mode 100644
index 0000000..d8fc07e
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import com.android.ddmlib.IDevice;
+import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.MonkeyImage;
+import com.android.monkeyrunner.MonkeyManager;
+import com.android.monkeyrunner.adb.LinearInterpolator.Point;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+public class AdbMonkeyDevice extends MonkeyDevice {
+    private static final Logger LOG = Logger.getLogger(AdbMonkeyDevice.class.getName());
+
+    private static final String[] ZERO_LENGTH_STRING_ARRAY = new String[0];
+    private static final long MANAGER_CREATE_TIMEOUT_MS = 5 * 1000; // 5 seconds
+
+    private final ExecutorService executor = Executors.newCachedThreadPool();
+
+    private final IDevice device;
+    private MonkeyManager manager;
+
+    public AdbMonkeyDevice(IDevice device) {
+        this.device = device;
+        this.manager = createManager("127.0.0.1", 12345);
+
+        Preconditions.checkNotNull(this.manager);
+    }
+
+    @Override
+    public MonkeyManager getManager() {
+        return manager;
+    }
+
+    @Override
+    public void dispose() {
+        try {
+            manager.quit();
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Error getting the manager to quit", e);
+        }
+        manager = null;
+    }
+
+    private void executeAsyncCommand(final String command,
+            final LoggingOutputReceiver logger) {
+        executor.submit(new Runnable() {
+            public void run() {
+                try {
+                    device.executeShellCommand(command, logger);
+                } catch (IOException e) {
+                    LOG.log(Level.SEVERE, "Error starting command: " + command, e);
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+    }
+
+    private MonkeyManager createManager(String address, int port) {
+        device.createForward(port, port);
+        String command = "monkey --port " + port;
+        executeAsyncCommand(command, new LoggingOutputReceiver(LOG, Level.FINE));
+
+        // Sleep for a second to give the command time to execute.
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            LOG.log(Level.SEVERE, "Unable to sleep", e);
+        }
+
+        InetAddress addr;
+        try {
+            addr = InetAddress.getByName(address);
+        } catch (UnknownHostException e) {
+            LOG.log(Level.SEVERE, "Unable to convert address into InetAddress: " + address, e);
+            return null;
+        }
+
+        // We have a tough problem to solve here.  "monkey" on the device gives us no indication
+        // when it has started up and is ready to serve traffic.  If you try too soon, commands
+        // will fail.  To remedy this, we will keep trying until a single command (in this case,
+        // wake) succeeds.
+        boolean success = false;
+        MonkeyManager mm = null;
+        long start = System.currentTimeMillis();
+
+        while (!success) {
+            long now = System.currentTimeMillis();
+            long diff = now - start;
+            if (diff > MANAGER_CREATE_TIMEOUT_MS) {
+                LOG.severe("Timeout while trying to create monkey mananger");
+                return null;
+            }
+
+            Socket monkeySocket;
+            try {
+                monkeySocket = new Socket(addr, port);
+            } catch (IOException e) {
+                LOG.log(Level.FINE, "Unable to connect socket", e);
+                success = false;
+                continue;
+            }
+
+            mm = new MonkeyManager(monkeySocket);
+
+            try {
+                mm.wake();
+            } catch (IOException e) {
+                LOG.log(Level.FINE, "Unable to wake up device", e);
+                success = false;
+                continue;
+            }
+            success = true;
+        }
+
+        return mm;
+    }
+
+    @Override
+    public MonkeyImage takeSnapshot() {
+        try {
+            return new AdbMonkeyImage(device.getScreenshot());
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to take snapshot", e);
+            return null;
+        }
+    }
+
+    @Override
+    protected String getSystemProperty(String key) {
+        return device.getProperty(key);
+    }
+
+    @Override
+    protected String getProperty(String key) {
+        try {
+            return manager.getVariable(key);
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to get variable: " + key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected void wake() {
+        try {
+            manager.wake();
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to wake device (too sleepy?)", e);
+        }
+    }
+
+    private String shell(String... args) {
+        StringBuilder cmd = new StringBuilder();
+        for (String arg : args) {
+            cmd.append(arg).append(" ");
+        }
+        return shell(cmd.toString());
+    }
+
+    @Override
+    protected String shell(String cmd) {
+        CommandOutputCapture capture = new CommandOutputCapture();
+        try {
+            device.executeShellCommand(cmd, capture);
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Error executing command: " + cmd, e);
+            return null;
+        }
+        return capture.toString();
+    }
+
+    @Override
+    protected boolean installPackage(String path) {
+        try {
+            String result = device.installPackage(path, true);
+            if (result != null) {
+                LOG.log(Level.SEVERE, "Got error installing package: "+ result);
+                return false;
+            }
+            return true;
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Error installing package: " + path, e);
+            return false;
+        }
+    }
+
+    @Override
+    protected boolean removePackage(String packageName) {
+        try {
+            String result = device.uninstallPackage(packageName);
+            if (result != null) {
+                LOG.log(Level.SEVERE, "Got error uninstalling package "+ packageName + ": " +
+                        result);
+                return false;
+            }
+            return true;
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Error installing package: " + packageName, e);
+            return false;
+        }
+    }
+
+    @Override
+    protected void press(String keyName, TouchPressType type) {
+        try {
+            switch (type) {
+                case DOWN_AND_UP:
+                    manager.press(keyName);
+                    break;
+                case DOWN:
+                    manager.keyDown(keyName);
+                    break;
+                case UP:
+                    manager.keyUp(keyName);
+                    break;
+            }
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Error sending press event: " + keyName + " " + type, e);
+        }
+    }
+
+    @Override
+    protected void type(String string) {
+        try {
+            manager.type(string);
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Error Typing: " + string, e);
+        }
+    }
+
+    @Override
+    protected void touch(int x, int y, TouchPressType type) {
+        try {
+            switch (type) {
+                case DOWN:
+                    manager.touchDown(x, y);
+                    break;
+                case UP:
+                    manager.touchUp(x, y);
+                    break;
+                case DOWN_AND_UP:
+                    manager.tap(x, y);
+                    break;
+            }
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Error sending touch event: " + x + " " + y + " " + type, e);
+        }
+    }
+
+    @Override
+    protected void reboot(String into) {
+        try {
+            device.reboot(into);
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "Unable to reboot device", e);
+        }
+    }
+
+    @Override
+    protected void startActivity(String uri, String action, String data, String mimetype,
+            Collection<String> categories, Map<String, Object> extras, String component,
+            int flags) {
+        List<String> intentArgs = buildIntentArgString(uri, action, data, mimetype, categories,
+                extras, component, flags);
+        shell(Lists.asList("am", "start",
+                intentArgs.toArray(ZERO_LENGTH_STRING_ARRAY)).toArray(ZERO_LENGTH_STRING_ARRAY));
+    }
+
+    @Override
+    protected void broadcastIntent(String uri, String action, String data, String mimetype,
+            Collection<String> categories, Map<String, Object> extras, String component,
+            int flags) {
+        List<String> intentArgs = buildIntentArgString(uri, action, data, mimetype, categories,
+                extras, component, flags);
+        shell(Lists.asList("am", "broadcast",
+                intentArgs.toArray(ZERO_LENGTH_STRING_ARRAY)).toArray(ZERO_LENGTH_STRING_ARRAY));
+    }
+
+    private static boolean isNullOrEmpty(@Nullable String string) {
+        return string == null || string.length() == 0;
+    }
+
+    private List<String> buildIntentArgString(String uri, String action, String data, String mimetype,
+            Collection<String> categories, Map<String, Object> extras, String component,
+            int flags) {
+        List<String> parts = Lists.newArrayList();
+
+        // from adb docs:
+        //<INTENT> specifications include these flags:
+        //    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]
+        //    [-c <CATEGORY> [-c <CATEGORY>] ...]
+        //    [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]
+        //    [--esn <EXTRA_KEY> ...]
+        //    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]
+        //    [-e|--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]
+        //    [-n <COMPONENT>] [-f <FLAGS>]
+        //    [<URI>]
+
+        if (!isNullOrEmpty(action)) {
+            parts.add("-a");
+            parts.add(action);
+        }
+
+        if (!isNullOrEmpty(data)) {
+            parts.add("-d");
+            parts.add(data);
+        }
+
+        if (!isNullOrEmpty(mimetype)) {
+            parts.add("-t");
+            parts.add(mimetype);
+        }
+
+        // Handle categories
+        for (String category : categories) {
+            parts.add("-c");
+            parts.add(category);
+        }
+
+        // Handle extras
+        for (Entry<String, Object> entry : extras.entrySet()) {
+            // Extras are either boolean, string, or int.  See which we have
+            Object value = entry.getValue();
+            String valueString;
+            String arg;
+            if (value instanceof Integer) {
+                valueString = Integer.toString((Integer) value);
+                arg = "--ei";
+            } else if (value instanceof Boolean) {
+                valueString = Boolean.toString((Boolean) value);
+                arg = "--ez";
+            } else {
+                // treat is as a string.
+                valueString = value.toString();
+                arg = "--esmake";
+            }
+            parts.add(arg);
+            parts.add(valueString);
+        }
+
+        if (!isNullOrEmpty(component)) {
+            parts.add("-n");
+            parts.add(component);
+        }
+
+        if (flags != 0) {
+            parts.add("-f");
+            parts.add(Integer.toString(flags));
+        }
+
+        if (!isNullOrEmpty(uri)) {
+            parts.add(uri);
+        }
+
+        return parts;
+    }
+
+    @Override
+    protected Map<String, Object> instrument(String packageName, Map<String, Object> args) {
+        List<String> shellCmd = Lists.newArrayList("am", "instrument", "-w", "-r", packageName);
+        String result = shell(shellCmd.toArray(ZERO_LENGTH_STRING_ARRAY));
+        return convertInstrumentResult(result);
+    }
+
+    /**
+     * Convert the instrumentation result into it's Map representation.
+     *
+     * @param result the result string
+     * @return the new map
+     */
+    private Map<String, Object> convertInstrumentResult(String result) {
+        Map<String, Object> map = Maps.newHashMap();
+        for (String line : result.split("\r\n")) {
+            if (line.startsWith("INSTRUMENTATION_RESULT")) {
+                int colonOffset = line.indexOf(':');
+                int equalsOffset = line.indexOf('=');
+
+                if (colonOffset == -1 || equalsOffset == -1) {
+                    LOG.severe("Unable to parse instrumentaton result: " + line);
+                    return Collections.emptyMap();
+                }
+
+                // +2 eats of up space after the : too
+                String key = line.substring(colonOffset + 2, equalsOffset);
+                String value = line.substring(equalsOffset + 1);
+                map.put(key, value);
+            }
+        }
+        return map;
+    }
+
+    @Override
+    protected void drag(int startx, int starty, int endx, int endy, int steps, long ms) {
+        final long iterationTime = ms / steps;
+
+        LinearInterpolator lerp = new LinearInterpolator(steps);
+        LinearInterpolator.Point start = new LinearInterpolator.Point(startx, starty);
+        LinearInterpolator.Point end = new LinearInterpolator.Point(endx, endy);
+        lerp.interpolate(start, end, new LinearInterpolator.Callback() {
+            public void step(Point point) {
+                try {
+                    manager.touchMove(point.getX(), point.getY());
+                } catch (IOException e) {
+                    LOG.log(Level.SEVERE, "Error sending drag start event", e);
+                }
+
+                try {
+                    Thread.sleep(iterationTime);
+                } catch (InterruptedException e) {
+                    LOG.log(Level.SEVERE, "Error sleeping", e);
+                }
+            }
+
+            public void start(Point point) {
+                try {
+                    manager.touchDown(point.getX(), point.getY());
+                } catch (IOException e) {
+                    LOG.log(Level.SEVERE, "Error sending drag start event", e);
+                }
+
+                try {
+                    Thread.sleep(iterationTime);
+                } catch (InterruptedException e) {
+                    LOG.log(Level.SEVERE, "Error sleeping", e);
+                }
+            }
+
+            public void end(Point point) {
+                try {
+                    manager.touchUp(point.getX(), point.getY());
+                } catch (IOException e) {
+                    LOG.log(Level.SEVERE, "Error sending drag end event", e);
+                }
+            }
+        });
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java
new file mode 100644
index 0000000..fc32600
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb;
+
+import com.android.ddmlib.RawImage;
+import com.android.monkeyrunner.MonkeyImage;
+import com.android.monkeyrunner.adb.image.ImageUtils;
+
+import java.awt.image.BufferedImage;
+
+/**
+ * ADB implementation of the MonkeyImage class.
+ */
+public class AdbMonkeyImage extends MonkeyImage {
+    private final RawImage image;
+
+    /**
+     * Create a new AdbMonkeyImage.
+     *
+     * @param image the image from adb.
+     */
+    AdbMonkeyImage(RawImage image) {
+        this.image = image;
+    }
+
+    @Override
+    public BufferedImage createBufferedImage() {
+        return ImageUtils.convertImage(image);
+    }
+
+    public RawImage getRawImage() {
+        return image;
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/CommandOutputCapture.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/CommandOutputCapture.java
new file mode 100644
index 0000000..9f99a7a
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/CommandOutputCapture.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb;
+
+import com.android.ddmlib.IShellOutputReceiver;
+
+/**
+ * Shell Output Receiver that captures shell output into a String for
+ * later retrieval.
+ */
+public class CommandOutputCapture implements IShellOutputReceiver {
+    private final StringBuilder builder = new StringBuilder();
+
+    public void flush() { }
+
+    public boolean isCancelled() {
+        return false;
+    }
+
+    public void addOutput(byte[] data, int offset, int length) {
+        String message = new String(data, offset, length);
+        builder.append(message);
+    }
+
+    @Override
+    public String toString() {
+        return builder.toString();
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java
new file mode 100644
index 0000000..e39fefd
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb;
+
+
+
+/**
+ * Linear Interpolation class.
+ */
+public class LinearInterpolator {
+    private final int steps;
+
+    /**
+     * Use our own Point class so we don't pull in java.awt.* just for this simple class.
+     */
+    public static class Point {
+        private final int x;
+        private final int y;
+
+        public Point(int x, int y) {
+            this.x = x;
+            this.y = y;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder().
+                append("(").
+                append(x).
+                append(",").
+                append(y).
+                append(")").toString();
+        }
+
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof Point) {
+                Point that = (Point) obj;
+                return this.x == that.x && this.y == that.y;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return 0x43125315 + x + y;
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public int getY() {
+            return y;
+        }
+    }
+
+    /**
+     * Callback interface to recieve interpolated points.
+     */
+    public interface Callback {
+        /**
+         * Called once to inform of the start point.
+         */
+        void start(Point point);
+        /**
+         * Called once to inform of the end point.
+         */
+        void end(Point point);
+        /**
+         * Called at every step in-between start and end.
+         */
+        void step(Point point);
+    }
+
+    /**
+     * Create a new linear Interpolator.
+     *
+     * @param steps How many steps should be in a single run.  This counts the intervals
+     *              in-between points, so the actual number of points generated will be steps + 1.
+     */
+    public LinearInterpolator(int steps) {
+        this.steps = steps;
+    }
+
+    // Copied from android.util.MathUtils since we couldn't link it in on the host.
+    private static float lerp(float start, float stop, float amount) {
+        return start + (stop - start) * amount;
+    }
+
+    /**
+     * Calculate the interpolated points.
+     *
+     * @param start The starting point
+     * @param end The ending point
+     * @param callback the callback to call with each calculated points.
+     */
+    public void interpolate(Point start, Point end, Callback callback) {
+        int xDistance = Math.abs(end.getX() - start.getX());
+        int yDistance = Math.abs(end.getY() - start.getY());
+        float amount = (float) (1.0 / steps);
+
+
+        callback.start(start);
+        for (int i = 1; i < steps; i++) {
+            float newX = lerp(start.getX(), end.getX(), amount * i);
+            float newY = lerp(start.getY(), end.getY(), amount * i);
+
+            callback.step(new Point(Math.round(newX), Math.round(newY)));
+        }
+        // Generate final point
+        callback.end(end);
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LoggingOutputReceiver.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LoggingOutputReceiver.java
new file mode 100644
index 0000000..b78aff3
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LoggingOutputReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb;
+
+import com.android.ddmlib.IShellOutputReceiver;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Shell Output Receiver that sends shell output to a Logger.
+ */
+public class LoggingOutputReceiver implements IShellOutputReceiver {
+    private final Logger log;
+    private final Level level;
+
+    public LoggingOutputReceiver(Logger log, Level level) {
+        this.log = log;
+        this.level = level;
+    }
+
+    public void addOutput(byte[] data, int offset, int length) {
+        String message = new String(data, offset, length);
+        for (String line : message.split("\n")) {
+            log.log(level, line);
+        }
+    }
+
+    public void flush() { }
+
+    public boolean isCancelled() {
+        return false;
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java
new file mode 100644
index 0000000..7e31ea5
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb.image;
+
+import com.android.ddmlib.RawImage;
+import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.adb.AdbBackend;
+import com.android.monkeyrunner.adb.AdbMonkeyImage;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/**
+ * Utility program to capture raw and converted images from a device and write them to a file.
+ * This is used to generate the test data for ImageUtilsTest.
+ */
+public class CaptureRawAndConvertedImage {
+    public static class MonkeyRunnerRawImage implements Serializable {
+        public int version;
+        public int bpp;
+        public int size;
+        public int width;
+        public int height;
+        public int red_offset;
+        public int red_length;
+        public int blue_offset;
+        public int blue_length;
+        public int green_offset;
+        public int green_length;
+        public int alpha_offset;
+        public int alpha_length;
+
+        public byte[] data;
+
+        public MonkeyRunnerRawImage(RawImage rawImage) {
+            version = rawImage.version;
+            bpp = rawImage.bpp;
+            size = rawImage.size;
+            width = rawImage.width;
+            height = rawImage.height;
+            red_offset = rawImage.red_offset;
+            red_length = rawImage.red_length;
+            blue_offset = rawImage.blue_offset;
+            blue_length = rawImage.blue_length;
+            green_offset = rawImage.green_offset;
+            green_length = rawImage.green_length;
+            alpha_offset = rawImage.alpha_offset;
+            alpha_length = rawImage.alpha_length;
+
+            data = rawImage.data;
+        }
+
+        public RawImage toRawImage() {
+            RawImage rawImage = new RawImage();
+
+            rawImage.version = version;
+            rawImage.bpp = bpp;
+            rawImage.size = size;
+            rawImage.width = width;
+            rawImage.height = height;
+            rawImage.red_offset = red_offset;
+            rawImage.red_length = red_length;
+            rawImage.blue_offset = blue_offset;
+            rawImage.blue_length = blue_length;
+            rawImage.green_offset = green_offset;
+            rawImage.green_length = green_length;
+            rawImage.alpha_offset = alpha_offset;
+            rawImage.alpha_length = alpha_length;
+
+            rawImage.data = data;
+            return rawImage;
+        }
+    }
+
+    private static void writeOutImage(RawImage screenshot, String name) throws IOException {
+        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(name));
+        out.writeObject(new MonkeyRunnerRawImage(screenshot));
+        out.close();
+    }
+
+    public static void main(String[] args) throws IOException {
+        AdbBackend backend = new AdbBackend();
+        MonkeyDevice device = backend.waitForConnection();
+        AdbMonkeyImage snapshot = (AdbMonkeyImage) device.takeSnapshot();
+
+        // write out to a file
+        snapshot.writeToFile("output.png", "png");
+        writeOutImage(snapshot.getRawImage(), "output.raw");
+        System.exit(0);
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/ImageUtils.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/ImageUtils.java
new file mode 100644
index 0000000..c3eaf01
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/ImageUtils.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb.image;
+
+import com.android.ddmlib.RawImage;
+
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.util.Hashtable;
+/**
+ * Useful image related functions.
+ */
+public class ImageUtils {
+    // Utility class
+    private ImageUtils() { }
+
+    private static Hashtable<?,?> EMPTY_HASH = new Hashtable();
+    private static int[] BAND_OFFSETS_32 = { 0, 1, 2, 3 };
+    private static int[] BAND_OFFSETS_16 = { 0, 1 };
+
+    /**
+     * Convert a raw image into a buffered image.
+     *
+     * @param rawImage the raw image to convert
+     * @param image the old image to (possibly) recycle
+     * @return the converted image
+     */
+    public static BufferedImage convertImage(RawImage rawImage, BufferedImage image) {
+        switch (rawImage.bpp) {
+            case 16:
+                return rawImage16toARGB(image, rawImage);
+            case 32:
+                return rawImage32toARGB(rawImage);
+        }
+        return null;
+    }
+
+    /**
+     * Convert a raw image into a buffered image.
+     *
+     * @param rawImage the image to convert.
+     * @return the converted image.
+     */
+    public static BufferedImage convertImage(RawImage rawImage) {
+        return convertImage(rawImage, null);
+    }
+
+    static int getMask(int length) {
+        int res = 0;
+        for (int i = 0 ; i < length ; i++) {
+            res = (res << 1) + 1;
+        }
+
+        return res;
+    }
+
+    private static BufferedImage rawImage32toARGB(RawImage rawImage) {
+        // Do as much as we can to not make an extra copy of the data.  This is just a bunch of
+        // classes that wrap's the raw byte array of the image data.
+        DataBufferByte dataBuffer = new DataBufferByte(rawImage.data, rawImage.size);
+
+        PixelInterleavedSampleModel sampleModel =
+            new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, rawImage.width, rawImage.height,
+                    4, rawImage.width * 4, BAND_OFFSETS_32);
+        WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer,
+                new Point(0, 0));
+        return new BufferedImage(new ThirtyTwoBitColorModel(rawImage), raster, false, EMPTY_HASH);
+    }
+
+    private static BufferedImage rawImage16toARGB(BufferedImage image, RawImage rawImage) {
+        // Do as much as we can to not make an extra copy of the data.  This is just a bunch of
+        // classes that wrap's the raw byte array of the image data.
+        DataBufferByte dataBuffer = new DataBufferByte(rawImage.data, rawImage.size);
+
+        PixelInterleavedSampleModel sampleModel =
+            new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, rawImage.width, rawImage.height,
+                    2, rawImage.width * 2, BAND_OFFSETS_16);
+        WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer,
+                new Point(0, 0));
+        return new BufferedImage(new SixteenBitColorModel(rawImage), raster, false, EMPTY_HASH);
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/SixteenBitColorModel.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/SixteenBitColorModel.java
new file mode 100644
index 0000000..06ab939
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/SixteenBitColorModel.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb.image;
+
+import com.android.ddmlib.RawImage;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+
+/**
+ * Internal color model used to do conversion of 16bpp RawImages.
+ */
+class SixteenBitColorModel extends ColorModel {
+    private static final int[] BITS = {
+        8, 8, 8, 8
+    };
+    public SixteenBitColorModel(RawImage rawImage) {
+        super(32
+                , BITS, ColorSpace.getInstance(ColorSpace.CS_sRGB),
+                true, false, Transparency.TRANSLUCENT,
+                DataBuffer.TYPE_BYTE);
+    }
+
+    @Override
+    public boolean isCompatibleRaster(Raster raster) {
+        return true;
+    }
+
+    private int getPixel(Object inData) {
+        byte[] data = (byte[]) inData;
+        int value = data[0] & 0x00FF;
+        value |= (data[1] << 8) & 0x0FF00;
+
+        return value;
+    }
+
+    @Override
+    public int getAlpha(Object inData) {
+        return 0xff;
+    }
+
+    @Override
+    public int getBlue(Object inData) {
+        int pixel = getPixel(inData);
+        return ((pixel >> 0) & 0x01F) << 3;
+    }
+
+    @Override
+    public int getGreen(Object inData) {
+        int pixel = getPixel(inData);
+        return ((pixel >> 5) & 0x03F) << 2;
+    }
+
+    @Override
+    public int getRed(Object inData) {
+        int pixel = getPixel(inData);
+        return ((pixel >> 11) & 0x01F) << 3;
+    }
+
+    @Override
+    public int getAlpha(int pixel) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getBlue(int pixel) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getGreen(int pixel) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getRed(int pixel) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/ThirtyTwoBitColorModel.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/ThirtyTwoBitColorModel.java
new file mode 100644
index 0000000..d4e47ea
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/image/ThirtyTwoBitColorModel.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb.image;
+
+import com.android.ddmlib.RawImage;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+
+/**
+ * Internal color model used to do conversion of 32bpp RawImages.
+ */
+class ThirtyTwoBitColorModel extends ColorModel {
+    private static final int[] BITS = {
+        8, 8, 8, 8,
+    };
+    private final int alphaLength;
+    private final int alphaMask;
+    private final int alphaOffset;
+    private final int blueMask;
+    private final int blueLength;
+    private final int blueOffset;
+    private final int greenMask;
+    private final int greenLength;
+    private final int greenOffset;
+    private final int redMask;
+    private final int redLength;
+    private final int redOffset;
+
+    public ThirtyTwoBitColorModel(RawImage rawImage) {
+        super(32, BITS, ColorSpace.getInstance(ColorSpace.CS_sRGB),
+                true, false, Transparency.TRANSLUCENT,
+                DataBuffer.TYPE_BYTE);
+
+        redOffset = rawImage.red_offset;
+        redLength = rawImage.red_length;
+        redMask = ImageUtils.getMask(redLength);
+        greenOffset = rawImage.green_offset;
+        greenLength = rawImage.green_length;
+        greenMask = ImageUtils.getMask(greenLength);
+        blueOffset = rawImage.blue_offset;
+        blueLength = rawImage.blue_length;
+        blueMask = ImageUtils.getMask(blueLength);
+        alphaLength = rawImage.alpha_length;
+        alphaOffset = rawImage.alpha_offset;
+        alphaMask = ImageUtils.getMask(alphaLength);
+    }
+
+    @Override
+    public boolean isCompatibleRaster(Raster raster) {
+        return true;
+    }
+
+    private int getPixel(Object inData) {
+        byte[] data = (byte[]) inData;
+        int value = data[0] & 0x00FF;
+        value |= (data[1] & 0x00FF) << 8;
+        value |= (data[2] & 0x00FF) << 16;
+        value |= (data[3] & 0x00FF) << 24;
+
+        return value;
+    }
+
+    @Override
+    public int getAlpha(Object inData) {
+        int pixel = getPixel(inData);
+        if(alphaLength == 0) {
+            return 0xff;
+        }
+        return ((pixel >>> alphaOffset) & alphaMask) << (8 - alphaLength);
+    }
+
+    @Override
+    public int getBlue(Object inData) {
+        int pixel = getPixel(inData);
+        return ((pixel >>> blueOffset) & blueMask) << (8 - blueLength);
+    }
+
+    @Override
+    public int getGreen(Object inData) {
+        int pixel = getPixel(inData);
+        return ((pixel >>> greenOffset) & greenMask) << (8 - greenLength);
+    }
+
+    @Override
+    public int getRed(Object inData) {
+        int pixel = getPixel(inData);
+        return ((pixel >>> redOffset) & redMask) << (8 - redLength);
+    }
+
+    @Override
+    public int getAlpha(int pixel) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getBlue(int pixel) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getGreen(int pixel) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getRed(int pixel) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java b/tools/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java
new file mode 100644
index 0000000..e199a75
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.controller;
+
+import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.adb.AdbBackend;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.logging.Logger;
+
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+
+/**
+ * Application that can control an attached device using the network monkey.  It has a window
+ * that shows what the current screen looks like and allows the user to click in it.  Clicking in
+ * the window sends touch events to the attached device.  It also supports keyboard input for
+ * typing and has buttons to press to simulate physical buttons on the device.
+ */
+public class MonkeyController extends JFrame {
+    private static final Logger LOG = Logger.getLogger(MonkeyController.class.getName());
+
+    public static void main(String[] args) {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                AdbBackend adb = new AdbBackend();
+                final MonkeyDevice device = adb.waitForConnection();
+                MonkeyControllerFrame mf = new MonkeyControllerFrame(device);
+                mf.setVisible(true);
+                mf.addWindowListener(new WindowAdapter() {
+                    @Override
+                    public void windowClosed(WindowEvent e) {
+                        device.dispose();
+                    }
+                });
+            }
+        });
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java b/tools/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java
new file mode 100644
index 0000000..7f5a7d8
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.controller;
+
+import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.MonkeyImage;
+import com.android.monkeyrunner.MonkeyManager;
+import com.android.monkeyrunner.PhysicalButton;
+
+import java.awt.KeyEventDispatcher;
+import java.awt.KeyboardFocusManager;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.swing.AbstractAction;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JToolBar;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+
+/**
+ * Main window for MonkeyController.
+ */
+public class MonkeyControllerFrame extends JFrame {
+    private static final Logger LOG = Logger.getLogger(MonkeyControllerFrame.class.getName());
+
+    private final JButton refreshButton = new JButton("Refresh");
+    private final JButton variablesButton = new JButton("Variable");
+    private final JLabel imageLabel = new JLabel();
+    private final VariableFrame variableFrame;
+
+    private MonkeyManager monkeyManager;
+    private BufferedImage currentImage;
+
+    private final Timer timer = new Timer(1000, new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+            updateScreen();
+        }
+    });
+
+    private final MonkeyDevice device;
+
+    private class PressAction extends AbstractAction {
+        private final PhysicalButton button;
+
+        public PressAction(PhysicalButton button) {
+            this.button = button;
+        }
+        public void actionPerformed(ActionEvent event) {
+            try {
+                monkeyManager.press(button);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            updateScreen();
+        }
+    }
+
+    private JButton createToolbarButton(PhysicalButton hardButton) {
+        JButton button = new JButton(new PressAction(hardButton));
+        button.setText(hardButton.getKeyName());
+        return button;
+    }
+
+    public MonkeyControllerFrame(MonkeyDevice device) {
+        super("MonkeyController");
+        this.device = device;
+
+        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
+        JToolBar toolbar = new JToolBar();
+
+        toolbar.add(createToolbarButton(PhysicalButton.HOME));
+        toolbar.add(createToolbarButton(PhysicalButton.BACK));
+        toolbar.add(createToolbarButton(PhysicalButton.SEARCH));
+        toolbar.add(createToolbarButton(PhysicalButton.MENU));
+
+        add(toolbar);
+        add(refreshButton);
+        add(variablesButton);
+        add(imageLabel);
+
+        refreshButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                updateScreen();
+            }
+        });
+
+        variableFrame = new VariableFrame();
+        variablesButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                variableFrame.setVisible(true);
+            }
+        });
+
+        imageLabel.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent event) {
+                try {
+                    monkeyManager.touch(event.getX(), event.getY());
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+                updateScreen();
+            }
+
+        });
+
+        KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
+        focusManager.addKeyEventDispatcher(new KeyEventDispatcher() {
+            public boolean dispatchKeyEvent(KeyEvent event) {
+                if (KeyEvent.KEY_TYPED == event.getID()) {
+                    try {
+                        monkeyManager.type(event.getKeyChar());
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                return false;
+            }
+        });
+
+        SwingUtilities.invokeLater(new Runnable(){
+            public void run() {
+                init();
+                variableFrame.init(monkeyManager);
+            }
+        });
+
+        pack();
+    }
+
+    private void updateScreen() {
+        MonkeyImage snapshot = device.takeSnapshot();
+        currentImage = snapshot.createBufferedImage();
+        imageLabel.setIcon(new ImageIcon(currentImage));
+
+        pack();
+    }
+
+    private void init() {
+        monkeyManager = device.getManager();
+        if (monkeyManager == null) {
+            throw new RuntimeException("Unable to create monkey manager");
+        }
+        updateScreen();
+        timer.start();
+    }
+
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/controller/VariableFrame.java b/tools/monkeyrunner/src/com/android/monkeyrunner/controller/VariableFrame.java
new file mode 100644
index 0000000..9015b5d
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/controller/VariableFrame.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.controller;
+
+import com.google.common.collect.Sets;
+
+import com.android.monkeyrunner.MonkeyManager;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JTable;
+import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Swing Frame that displays all the variables that the monkey exposes on the device.
+ */
+public class VariableFrame extends JFrame {
+    private static final Logger LOG = Logger.getLogger(VariableFrame.class.getName());
+    private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();
+    private MonkeyManager monkeyManager;
+
+    private static class VariableHolder implements Comparable<VariableHolder> {
+        private final String key;
+        private final String value;
+
+        public VariableHolder(String key, String value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public int compareTo(VariableHolder o) {
+            return key.compareTo(o.key);
+        }
+    }
+
+    private static <E> E getNthElement(Set<E> set, int offset) {
+        int current = 0;
+        for (E elem : set) {
+            if (current == offset) {
+                return elem;
+            }
+            current++;
+        }
+        return null;
+    }
+
+    private class VariableTableModel extends AbstractTableModel {
+        private final TreeSet<VariableHolder> set = Sets.newTreeSet();
+
+        public void refresh() {
+            Collection<String> variables;
+            try {
+                variables = monkeyManager.listVariable();
+            } catch (IOException e) {
+                LOG.log(Level.SEVERE, "Error getting list of variables", e);
+                return;
+            }
+            for (final String variable : variables) {
+                EXECUTOR.execute(new Runnable() {
+                    public void run() {
+                        String value;
+                        try {
+                            value = monkeyManager.getVariable(variable);
+                        } catch (IOException e) {
+                            LOG.log(Level.SEVERE,
+                                    "Error getting variable value for " + variable, e);
+                            return;
+                        }
+                        if (value == null) {
+                            value = "";
+                        }
+                        synchronized (set) {
+                            set.add(new VariableHolder(variable, value));
+                            SwingUtilities.invokeLater(new Runnable() {
+                                public void run() {
+                                    VariableTableModel.this.fireTableDataChanged();
+                                }
+                            });
+
+                        }
+                    }
+                });
+            }
+        }
+
+        public int getColumnCount() {
+            return 2;
+        }
+
+        public int getRowCount() {
+            synchronized (set) {
+                return set.size();
+            }
+        }
+
+        public Object getValueAt(int rowIndex, int columnIndex) {
+            VariableHolder nthElement;
+            synchronized (set) {
+                nthElement = getNthElement(set, rowIndex);
+            }
+            if (columnIndex == 0) {
+                return nthElement.getKey();
+            }
+            return nthElement.getValue();
+        }
+    }
+
+    public VariableFrame() {
+        super("Variables");
+        setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
+        setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
+
+        final VariableTableModel tableModel = new VariableTableModel();
+
+        JButton refreshButton = new JButton("Refresh");
+        add(refreshButton);
+        refreshButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                tableModel.refresh();
+            }
+        });
+
+
+        JTable table = new JTable(tableModel);
+        add(table);
+
+        tableModel.addTableModelListener(new TableModelListener() {
+            public void tableChanged(TableModelEvent e) {
+                pack();
+            }
+        });
+
+        this.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowOpened(WindowEvent e) {
+                tableModel.refresh();
+            }
+        });
+
+        pack();
+    }
+
+    public void init(MonkeyManager monkeyManager) {
+        this.monkeyManager = monkeyManager;
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/doc/MonkeyRunnerExported.java b/tools/monkeyrunner/src/com/android/monkeyrunner/doc/MonkeyRunnerExported.java
new file mode 100644
index 0000000..dd3eb0b
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/doc/MonkeyRunnerExported.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.doc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that the annotated method is a public API to expose to the
+ * scripting interface.  Can be used to generate documentation of what
+ * methods are exposed and also can be used to enforce visibility of
+ * these methods in the scripting environment.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE })
+public @interface MonkeyRunnerExported {
+    /**
+     * A documentation string for this method.
+     */
+    String doc();
+
+    /**
+     * The list of names for the keywords in this method in their proper positional order.
+     *
+     * For example:
+     *
+     * @MonkeyRunnerExported(args={"one", "two"})
+     * public void foo();
+     *
+     * would allow calls like this:
+     *   foo(one=1, two=2)
+     *   foo(1, 2)
+     */
+    String[] args() default {};
+
+    /**
+     * The list of documentation for the arguments.
+     */
+    String[] argDocs() default {};
+
+    /**
+     * The documentation for the return type of this method.
+     */
+    String returns() default "returns nothing.";
+}
\ No newline at end of file
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/exceptions/MonkeyRunnerException.java b/tools/monkeyrunner/src/com/android/monkeyrunner/exceptions/MonkeyRunnerException.java
new file mode 100644
index 0000000..1d5f19b
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/exceptions/MonkeyRunnerException.java
@@ -0,0 +1,18 @@
+package com.android.monkeyrunner.exceptions;
+
+/**
+ * Base exception class for all MonkeyRunner Exceptions.
+ */
+public class MonkeyRunnerException extends Exception {
+    public MonkeyRunnerException(String message) {
+        super(message);
+    }
+
+    public MonkeyRunnerException(Throwable e) {
+        super(e);
+    }
+
+    public MonkeyRunnerException(String message, Throwable e) {
+        super(message, e);
+    }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java b/tools/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java
new file mode 100644
index 0000000..9b23bab
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java
@@ -0,0 +1,22 @@
+package com.android.monkeyrunner.stub;
+
+import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.MonkeyManager;
+import com.android.monkeyrunner.MonkeyRunnerBackend;
+
+public class StubBackend implements MonkeyRunnerBackend {
+
+    public MonkeyManager createManager(String address, int port) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public MonkeyDevice waitForConnection(long timeout, String deviceId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public void shutdown() {
+        // We're stub - we've got nothing to do.
+    }
+}
diff --git a/tools/monkeyrunner/src/resources/com/android/monkeyrunner/html.cs b/tools/monkeyrunner/src/resources/com/android/monkeyrunner/html.cs
new file mode 100644
index 0000000..7d2c93f
--- /dev/null
+++ b/tools/monkeyrunner/src/resources/com/android/monkeyrunner/html.cs
@@ -0,0 +1,25 @@
+<html>
+<body>
+<h1>MonkeyRunner Help<h1>
+<h2>Table of Contents</h2>
+<ul>
+<?cs each:item = help ?>
+<li><a href="#<?cs name:item ?>"><?cs var:item.name ?></a></li>
+<?cs /each ?>
+</ul>
+<?cs each:item = help ?>
+<h2><a name="<?cs name:item ?>"><?cs var:item.name ?></a></h2>
+  <p><?cs var:item.doc ?></p>
+    <?cs if:subcount(item.argument) ?>
+<h3>Args</h3>
+<ul>
+      <?cs each:arg = item.argument ?>
+        <li><?cs var:arg.name ?> - <?cs var:arg.doc ?></li>
+      <?cs /each ?>
+</ul>
+<h3>Returns</h3>
+<p><?cs var:item.returns ?></p>
+<?cs /if ?>
+<?cs /each ?>
+</body>
+</html>
diff --git a/tools/monkeyrunner/src/resources/com/android/monkeyrunner/text.cs b/tools/monkeyrunner/src/resources/com/android/monkeyrunner/text.cs
new file mode 100644
index 0000000..4a4af5f
--- /dev/null
+++ b/tools/monkeyrunner/src/resources/com/android/monkeyrunner/text.cs
@@ -0,0 +1,9 @@
+MonkeyRunner help
+<?cs each:item = help ?>
+<?cs var:item.name ?>
+  <?cs var:item.doc ?>
+
+<?cs if:subcount(item.argument) ?>  Args:<?cs each:arg = item.argument ?>
+    <?cs var:arg.name ?> - <?cs var:arg.doc ?><?cs /each ?>
+<?cs /if ?>  Returns: <?cs var:item.returns ?>
+<?cs /each ?>
diff --git a/tools/monkeyrunner/test/Android.mk b/tools/monkeyrunner/test/Android.mk
new file mode 100644
index 0000000..6e2233b
--- /dev/null
+++ b/tools/monkeyrunner/test/Android.mk
@@ -0,0 +1,23 @@
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_MODULE := MonkeyRunnerTest
+LOCAL_JAVA_LIBRARIES :=  junit monkeyrunner ddmlib guavalib jython
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java b/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java
new file mode 100644
index 0000000..f5c571d
--- /dev/null
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.android.monkeyrunner.adb.LinearInterpolatorTest;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+/**
+ * Test suite to run all the tests for MonkeyRunner.
+ */
+public class AllTests {
+    public static Test suite(Class<? extends TestCase>... classes) {
+        TestSuite suite = new TestSuite();
+        for (Class<? extends TestCase> clz : classes) {
+            suite.addTestSuite(clz);
+        }
+        return suite;
+    }
+
+    public static void main(String args[]) {
+        TestRunner tr = new TestRunner();
+        TestResult result = tr.doRun(AllTests.suite(ImageUtilsTest.class, JythonUtilsTest.class,
+            MonkeyRunnerOptionsTest.class, LinearInterpolatorTest.class));
+        if (result.wasSuccessful()) {
+            System.exit(0);
+        } else {
+            System.exit(1);
+        }
+    }
+}
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java
new file mode 100644
index 0000000..f07c2f3
--- /dev/null
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.android.ddmlib.RawImage;
+import com.android.monkeyrunner.adb.image.CaptureRawAndConvertedImage;
+import com.android.monkeyrunner.adb.image.ImageUtils;
+import com.android.monkeyrunner.adb.image.CaptureRawAndConvertedImage.MonkeyRunnerRawImage;
+
+import junit.framework.TestCase;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+
+import javax.imageio.ImageIO;
+
+public class ImageUtilsTest extends TestCase {
+    private static BufferedImage createBufferedImage(String name) throws IOException {
+        InputStream is = ImageUtilsTest.class.getResourceAsStream(name);
+        BufferedImage img =  ImageIO.read(is);
+        is.close();
+        return img;
+    }
+
+    private static RawImage createRawImage(String name) throws IOException, ClassNotFoundException {
+        ObjectInputStream is =
+            new ObjectInputStream(ImageUtilsTest.class.getResourceAsStream(name));
+        CaptureRawAndConvertedImage.MonkeyRunnerRawImage wrapper = (MonkeyRunnerRawImage) is.readObject();
+        is.close();
+        return wrapper.toRawImage();
+    }
+
+    /**
+     * Check that the two images will draw the same (ie. have the same pixels).  This is different
+     * that BufferedImage.equals(), which also wants to check that they have the same ColorModel
+     * and other parameters.
+     *
+     * @param i1 the first image
+     * @param i2 the second image
+     * @return true if both images will draw the same (ie. have same pixels).
+     */
+    private static boolean checkImagesHaveSamePixels(BufferedImage i1, BufferedImage i2) {
+        if (i1.getWidth() != i2.getWidth()) {
+            return false;
+        }
+        if (i1.getHeight() != i2.getHeight()) {
+            return false;
+        }
+
+        for (int y = 0; y < i1.getHeight(); y++) {
+            for (int x = 0; x < i1.getWidth(); x++) {
+                int p1 = i1.getRGB(x, y);
+                int p2 = i2.getRGB(x, y);
+                if (p1 != p2) {
+                    WritableRaster r1 = i1.getRaster();
+                    WritableRaster r2 = i2.getRaster();
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public void testImageConversionOld() throws IOException, ClassNotFoundException {
+        RawImage rawImage = createRawImage("image1.raw");
+        BufferedImage convertedImage = ImageUtils.convertImage(rawImage);
+        BufferedImage correctConvertedImage = createBufferedImage("image1.png");
+
+        assertTrue(checkImagesHaveSamePixels(convertedImage, correctConvertedImage));
+    }
+
+    public void testImageConversionNew() throws IOException, ClassNotFoundException {
+        RawImage rawImage = createRawImage("image2.raw");
+        BufferedImage convertedImage = ImageUtils.convertImage(rawImage);
+        BufferedImage correctConvertedImage = createBufferedImage("image2.png");
+
+        assertTrue(checkImagesHaveSamePixels(convertedImage, correctConvertedImage));
+    }
+}
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java
new file mode 100644
index 0000000..5b8c8f9
--- /dev/null
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+
+import junit.framework.TestCase;
+
+import org.python.core.ArgParser;
+import org.python.core.PyDictionary;
+import org.python.core.PyException;
+import org.python.core.PyObject;
+import org.python.core.PyString;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for the JythonUtils class.
+ */
+public class JythonUtilsTest extends TestCase {
+    private static final String PACKAGE_NAME = JythonUtilsTest.class.getPackage().getName();
+    private static final String CLASS_NAME = JythonUtilsTest.class.getSimpleName();
+
+    private static boolean called = false;
+    private static double floatValue = 0.0;
+    private static List<Object> listValue = null;
+    private static Map<String, Object> mapValue;
+
+    @MonkeyRunnerExported(doc = "", args = {"value"})
+    public static void floatTest(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+        called = true;
+
+        floatValue = JythonUtils.getFloat(ap, 0);
+    }
+
+    @MonkeyRunnerExported(doc = "", args = {"value"})
+    public static void listTest(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+        called = true;
+
+        listValue = JythonUtils.getList(ap, 0);
+    }
+
+    @MonkeyRunnerExported(doc = "", args = {"value"})
+    public static void mapTest(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+        called = true;
+
+        mapValue = JythonUtils.getMap(ap, 0);
+    }
+
+    @MonkeyRunnerExported(doc = "")
+    public static PyDictionary convertMapTest(PyObject[] args, String[] kws) {
+        Map<String, Object> map = Maps.newHashMap();
+        map.put("string", "value");
+        map.put("integer", 1);
+        map.put("double", 3.14);
+        return JythonUtils.convertMapToDict(map);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        called = false;
+        floatValue = 0.0;
+    }
+
+    private static PyObject call(String method) {
+        return call(method, new String[]{ });
+    }
+    private static PyObject call(String method, String... args) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("from ").append(PACKAGE_NAME);
+        sb.append(" import ").append(CLASS_NAME).append("\n");
+
+        // Exec line
+        sb.append("result = ");
+        sb.append(CLASS_NAME).append(".").append(method);
+        sb.append("(");
+        for (String arg : args) {
+            sb.append(arg).append(",");
+        }
+        sb.append(")");
+
+        return ScriptRunner.runStringAndGet(sb.toString(), "result").get("result");
+    }
+
+    public void testSimpleCall() {
+        call("floatTest", "0.0");
+        assertTrue(called);
+    }
+
+    public void testMissingFloatArg() {
+        try {
+            call("floatTest");
+        } catch(PyException e) {
+            return;
+        }
+        fail("Should have thrown exception");
+    }
+
+    public void testBadFloatArgType() {
+        try {
+            call("floatTest", "\'foo\'");
+        } catch(PyException e) {
+            return;
+        }
+        fail("Should have thrown exception");
+    }
+
+    public void testFloatParse() {
+        call("floatTest", "103.2");
+        assertTrue(called);
+        assertEquals(floatValue, 103.2);
+    }
+
+    public void testFloatParseInteger() {
+        call("floatTest", "103");
+        assertTrue(called);
+        assertEquals(floatValue, 103.0);
+    }
+
+    public void testParseStringList() {
+        call("listTest", "['a', 'b', 'c']");
+        assertTrue(called);
+        assertEquals(3, listValue.size());
+        assertEquals("a", listValue.get(0));
+        assertEquals("b", listValue.get(1));
+        assertEquals("c", listValue.get(2));
+    }
+
+    public void testParseIntList() {
+        call("listTest", "[1, 2, 3]");
+        assertTrue(called);
+        assertEquals(3, listValue.size());
+        assertEquals(new Integer(1), listValue.get(0));
+        assertEquals(new Integer(2), listValue.get(1));
+        assertEquals(new Integer(3), listValue.get(2));
+    }
+
+    public void testParseMixedList() {
+        call("listTest", "['a', 1, 3.14]");
+        assertTrue(called);
+        assertEquals(3, listValue.size());
+        assertEquals("a", listValue.get(0));
+        assertEquals(new Integer(1), listValue.get(1));
+        assertEquals(new Double(3.14), listValue.get(2));
+    }
+
+    public void testParseOptionalList() {
+        call("listTest");
+        assertTrue(called);
+        assertEquals(0, listValue.size());
+    }
+
+    public void testParsingNotAList() {
+        try {
+            call("listTest", "1.0");
+        } catch (PyException e) {
+            return;
+        }
+        fail("Should have thrown an exception");
+    }
+
+    public void testParseMap() {
+        call("mapTest", "{'a': 0, 'b': 'bee', 3: 'cee'}");
+        assertTrue(called);
+        assertEquals(3, mapValue.size());
+        assertEquals(new Integer(0), mapValue.get("a"));
+        assertEquals("bee", mapValue.get("b"));
+        // note: coerced key type
+        assertEquals("cee", mapValue.get("3"));
+    }
+
+    public void testParsingNotAMap() {
+        try {
+            call("mapTest", "1.0");
+        } catch (PyException e) {
+            return;
+        }
+        fail("Should have thrown an exception");
+    }
+
+    public void testParseOptionalMap() {
+        call("mapTest");
+        assertTrue(called);
+        assertEquals(0, mapValue.size());
+    }
+
+    public void testConvertMap() {
+        PyDictionary result = (PyDictionary) call("convertMapTest");
+        PyObject stringPyObject = result.__getitem__(new PyString("string"));
+        String string = (String) stringPyObject.__tojava__(String.class);
+        assertEquals("value", string);
+
+        PyObject intPyObject = result.__getitem__(new PyString("integer"));
+        int i = (Integer) intPyObject.__tojava__(Integer.class);
+        assertEquals(i, 1);
+
+        PyObject doublePyObject = result.__getitem__(new PyString("double"));
+        double d = (Double) doublePyObject.__tojava__(Double.class);
+        assertEquals(3.14, d);
+    }
+}
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/MonkeyRunnerOptionsTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/MonkeyRunnerOptionsTest.java
new file mode 100644
index 0000000..0852c0b
--- /dev/null
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/MonkeyRunnerOptionsTest.java
@@ -0,0 +1,56 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.monkeyrunner;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Iterator;
+
+/**
+ * Unit Tests to test command line argument parsing.
+ */
+public class MonkeyRunnerOptionsTest extends TestCase {
+  // We need to use a file that actually exists
+  private static final String FILENAME = "/etc/passwd";
+
+  public void testSimpleArgs() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { FILENAME });
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+  }
+
+  public void testParsingArgsBeforeScriptName() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { "-be", "stub", FILENAME});
+    assertEquals("stub", options.getBackendName());
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+  }
+
+  public void testParsingScriptArgument() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { FILENAME, "arg1", "arg2" });
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+    Iterator<String> i = options.getArguments().iterator();
+    assertEquals("arg1", i.next());
+    assertEquals("arg2", i.next());
+  }
+
+  public void testParsingScriptArgumentWithDashes() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { FILENAME, "--arg1" });
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+    assertEquals("--arg1", options.getArguments().iterator().next());
+  }
+
+  public void testMixedArgs() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { "-be", "stub", FILENAME,
+          "arg1", "--debug=True"});
+    assertEquals("stub", options.getBackendName());
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+    Iterator<String> i = options.getArguments().iterator();
+    assertEquals("arg1", i.next());
+    assertEquals("--debug=True", i.next());
+  }
+}
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java
new file mode 100644
index 0000000..00670ce
--- /dev/null
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.monkeyrunner.adb;
+
+import com.google.common.collect.Lists;
+
+import com.android.monkeyrunner.adb.LinearInterpolator.Point;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Unit tests for the LinerInterpolator class.S
+ */
+public class LinearInterpolatorTest extends TestCase {
+    private static class Collector implements LinearInterpolator.Callback {
+        private final List<LinearInterpolator.Point> points = Lists.newArrayList();
+
+        public List<LinearInterpolator.Point> getPoints() {
+            return points;
+        }
+
+        public void end(Point input) {
+            points.add(input);
+        }
+
+        public void start(Point input) {
+            points.add(input);
+        }
+
+        public void step(Point input) {
+            points.add(input);
+        }
+    }
+
+    List<Integer> STEP_POINTS = Lists.newArrayList(0, 100, 200, 300, 400, 500, 600, 700, 800, 900,
+            1000);
+    List<Integer> REVERSE_STEP_POINTS = Lists.newArrayList(1000, 900, 800, 700, 600, 500, 400, 300,
+            200, 100, 0);
+
+    public void testLerpRight() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(0, 100),
+                new LinearInterpolator.Point(1000, 100),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(STEP_POINTS.get(x), 100), points.get(x));
+        }
+    }
+
+    public void testLerpLeft() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(1000, 100),
+                new LinearInterpolator.Point(0, 100),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(REVERSE_STEP_POINTS.get(x), 100), points.get(x));
+        }
+    }
+
+    public void testLerpUp() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(100, 1000),
+                new LinearInterpolator.Point(100, 0),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(100, REVERSE_STEP_POINTS.get(x)), points.get(x));
+        }
+    }
+
+    public void testLerpDown() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(100, 0),
+                new LinearInterpolator.Point(100, 1000),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(100, STEP_POINTS.get(x)), points.get(x));
+        }
+    }
+
+    public void testLerpNW() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(0, 0),
+                new LinearInterpolator.Point(1000, 1000),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(STEP_POINTS.get(x), STEP_POINTS.get(x)), points.get(x));
+        }
+    }
+
+    public void testLerpNE() {
+        LinearInterpolator lerp = new LinearInterpolator(10);
+        Collector collector = new Collector();
+        lerp.interpolate(new LinearInterpolator.Point(1000, 1000),
+                new LinearInterpolator.Point(0, 0),
+                collector);
+
+        List<LinearInterpolator.Point> points = collector.getPoints();
+        assertEquals(11, points.size());
+        for (int x = 0; x < points.size(); x++) {
+            assertEquals(new Point(REVERSE_STEP_POINTS.get(x), REVERSE_STEP_POINTS.get(x)), points.get(x));
+        }
+    }
+}
diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png
new file mode 100644
index 0000000..9ef1800
--- /dev/null
+++ b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png
Binary files differ
diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw
new file mode 100644
index 0000000..99ec013
--- /dev/null
+++ b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw
Binary files differ
diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png
new file mode 100644
index 0000000..03ff0c1
--- /dev/null
+++ b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png
Binary files differ
diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw
new file mode 100644
index 0000000..06e5b47
--- /dev/null
+++ b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw
Binary files differ