am 8fc94823: am 42a6c754: am 3a38c110: Merge "Fix mac build"

* commit '8fc94823cfd7f9f2403f1ad553d84544252c7849':
  Fix mac build
diff --git a/apps/Development/src/com/android/development/AccountsTester.java b/apps/Development/src/com/android/development/AccountsTester.java
index e2a0789..b4155e7 100644
--- a/apps/Development/src/com/android/development/AccountsTester.java
+++ b/apps/Development/src/com/android/development/AccountsTester.java
@@ -21,6 +21,7 @@
 import android.app.AlertDialog;
 import android.content.*;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.accounts.*;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -32,6 +33,8 @@
 import android.text.TextUtils;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 public class AccountsTester extends Activity implements OnAccountsUpdateListener {
     private static final String TAG = "AccountsTester";
@@ -142,7 +145,7 @@
 
     private void initializeAuthenticatorsSpinner() {
         mAuthenticatorDescs = mAccountManager.getAuthenticatorTypes();
-        String[] names = new String[mAuthenticatorDescs.length];
+        List<String> names = new ArrayList(mAuthenticatorDescs.length);
         for (int i = 0; i < mAuthenticatorDescs.length; i++) {
             Context authContext;
             try {
@@ -150,12 +153,17 @@
             } catch (PackageManager.NameNotFoundException e) {
                 continue;
             }
-            names[i] = authContext.getString(mAuthenticatorDescs[i].labelId);
+            try  {
+                names.add(authContext.getString(mAuthenticatorDescs[i].labelId));
+            } catch (Resources.NotFoundException e) {
+                continue;
+            }
         }
 
+        String[] namesArray = names.toArray(new String[names.size()]);
         ArrayAdapter<String> adapter =
                 new ArrayAdapter<String>(AccountsTester.this,
-                android.R.layout.simple_spinner_item, names);
+                android.R.layout.simple_spinner_item, namesArray);
         mAccountTypesSpinner.setAdapter(adapter);
     }
 
diff --git a/apps/SpareParts/Android.mk b/apps/SpareParts/Android.mk
deleted file mode 100644
index 1a1ea0a..0000000
--- a/apps/SpareParts/Android.mk
+++ /dev/null
@@ -1,10 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := eng
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_PACKAGE_NAME := SpareParts
-
-include $(BUILD_PACKAGE)
diff --git a/apps/SpareParts/AndroidManifest.xml b/apps/SpareParts/AndroidManifest.xml
deleted file mode 100644
index 137f907..0000000
--- a/apps/SpareParts/AndroidManifest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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.android.spare_parts">
-    <uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />
-    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    
-    <application android:label="@string/app_label"
-            android:icon="@mipmap/app_icon">
-
-        <activity android:name="SpareParts">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-
-    </application>
-</manifest>
diff --git a/apps/SpareParts/NOTICE b/apps/SpareParts/NOTICE
deleted file mode 100644
index c5b1efa..0000000
--- a/apps/SpareParts/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
-   Copyright (c) 2005-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.
-
-   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.
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
diff --git a/apps/SpareParts/res/layout/spare_parts.xml b/apps/SpareParts/res/layout/spare_parts.xml
deleted file mode 100644
index c1cd3c8..0000000
--- a/apps/SpareParts/res/layout/spare_parts.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/Settings/assets/res/any/layout/keyboard_version.xml
-**
-** Copyright 2006, 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.
-*/
--->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <RelativeLayout 
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <Spinner android:id="@+id/window_animation_scale"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentLeft="true">
-        </Spinner>
-
-        <Spinner android:id="@+id/transition_animation_scale"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@id/window_animation_scale"
-            android:layout_alignParentLeft="true">
-        </Spinner>
-
-    </RelativeLayout>
-
-</ScrollView>
-
diff --git a/apps/SpareParts/res/mipmap-hdpi/app_icon.png b/apps/SpareParts/res/mipmap-hdpi/app_icon.png
deleted file mode 100755
index 60fbdf5..0000000
--- a/apps/SpareParts/res/mipmap-hdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/apps/SpareParts/res/mipmap-mdpi/app_icon.png b/apps/SpareParts/res/mipmap-mdpi/app_icon.png
deleted file mode 100644
index cb40a19..0000000
--- a/apps/SpareParts/res/mipmap-mdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/apps/SpareParts/res/values/arrays.xml b/apps/SpareParts/res/values/arrays.xml
deleted file mode 100644
index e6026da..0000000
--- a/apps/SpareParts/res/values/arrays.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 
- * Copyright (C) 2007 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.
- -->
-
-<resources>
-    <string-array name="entries_animations">
-        <item>Off</item>
-        <item>Fast</item>
-        <item>Normal</item>
-        <item>Slow</item>
-        <item>Very Slow</item>
-    </string-array>
-
-    <string-array name="entryvalues_animations">
-        <item>0.0</item>
-        <item>0.5</item>
-        <item>1.0</item>
-        <item>1.5</item>
-        <item>2.0</item>
-    </string-array>
-    
-    <string-array name="entries_font_size">
-        <item>Extremely Small</item>
-        <item>Extra Small</item>
-        <item>Small</item>
-        <item>Normal</item>
-        <item>Large</item>
-        <item>Extra Large</item>
-        <item>Extremely Large</item>
-    </string-array>
-
-    <string-array name="entryvalues_font_size">
-        <item>0.70</item>
-        <item>0.85</item>
-        <item>0.95</item>
-        <item>1.0</item>
-        <item>1.05</item>
-        <item>1.15</item>
-        <item>1.30</item>
-    </string-array>
-    
-    <string-array name="entries_end_button">
-        <item>Nothing</item>
-        <item>Go to home</item>
-        <item>Go to sleep</item>
-        <item>Home, then sleep</item>
-    </string-array>
-    
-    <string-array name="entryvalues_end_button">
-        <item>0</item>
-        <item>1</item>
-        <item>2</item>
-        <item>3</item>
-    </string-array>
-</resources>
diff --git a/apps/SpareParts/res/values/strings.xml b/apps/SpareParts/res/values/strings.xml
deleted file mode 100644
index 9e78be0..0000000
--- a/apps/SpareParts/res/values/strings.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/strings.xml
-**
-** Copyright 2006, 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.
-*/
--->
-<resources>
-    <string name="app_label">Spare Parts</string>
-
-    <string name="device_info_title">Device info</string>
-    
-    <string name="title_battery_history">Battery history</string>
-    <string name="summary_battery_history">Summary of how battery has been used</string>
-    
-    <string name="title_battery_information">Battery information</string>
-    <string name="summary_battery_information">Current battery status information</string>
-    
-    <string name="title_usage_statistics">Usage statistics</string>
-    <string name="summary_usage_statistics">Summary of application usage</string>
-    
-    <string name="general_title">General</string>
-    
-    <string name="title_window_animations">Window animations</string>
-    <string name="summary_window_animations">Speed of animations in individual windows</string>
-    <string name="dialog_title_window_animations">Select window speed</string>
-    
-    <string name="title_transition_animations">Transition animations</string>
-    <string name="summary_transition_animations">Speed of animations moving between screens</string>
-    <string name="dialog_title_transition_animations">Select transition speed</string>
-    
-    <string name="title_fancy_ime_animations">Fancy input animations</string>
-    <string name="summary_on_fancy_ime_animations">Use fancier animations for input method windows</string>
-    <string name="summary_off_fancy_ime_animations">Use normal animations for input method windows</string>
-    
-    <string name="title_haptic_feedback">Haptic feedback</string>
-    <string name="summary_on_haptic_feedback">Use haptic feedback with user interaction</string>
-    <string name="summary_off_haptic_feedback">Use haptic feedback with user interaction</string>
-    
-    <string name="title_font_size">Font size</string>
-    <string name="summary_font_size">Overall size of fonts</string>
-    <string name="dialog_title_font_size">Select font size</string>
-    
-    <string name="title_end_button">End button behavior</string>
-    <string name="summary_end_button">Select End (red) button action</string>
-    <string name="dialog_title_end_button">Select End button</string>
-    
-    <string name="applications_title">Applications</string>
-    
-    <!-- Sound & display settings screen, compatibility mode check box label -->
-    <string name="compatibility_mode_title">Compatibility Mode</string>
-    <!-- Sound & display settings screen, compatibility mode option summary text when check box is selected -->
-    <string name="compatibility_mode_summary_on">Run older apps in Compatibility mode. This requires rebooting. </string>
-    <!-- Sound & display settings screen, compatibility mode option summary text when check box is clear -->
-    <string name="compatibility_mode_summary_off">Run older apps in Compatibility mode. This requires rebooting. </string>
-</resources>
diff --git a/apps/SpareParts/res/xml/spare_parts.xml b/apps/SpareParts/res/xml/spare_parts.xml
deleted file mode 100644
index 0d06e26..0000000
--- a/apps/SpareParts/res/xml/spare_parts.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/*
- * Copyright 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.
- */
--->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <PreferenceCategory
-        android:title="@string/device_info_title">
-
-        <PreferenceScreen android:key="battery_history_settings"
-                android:title="@string/title_battery_history" 
-                android:summary="@string/summary_battery_history">
-            <intent android:action="android.intent.action.MAIN"
-                    android:targetPackage="com.android.settings"
-                    android:targetClass="com.android.settings.fuelgauge.PowerUsageSummary" />
-        </PreferenceScreen>
-        
-        <PreferenceScreen android:key="battery_information_settings"
-                android:title="@string/title_battery_information" 
-                android:summary="@string/summary_battery_information">
-            <intent android:action="android.intent.action.MAIN"
-                    android:targetPackage="com.android.settings"
-                    android:targetClass="com.android.settings.BatteryInfo" />
-        </PreferenceScreen>
-        
-        <PreferenceScreen android:key="usage_statistics_settings"
-                android:title="@string/title_usage_statistics" 
-                android:summary="@string/summary_usage_statistics">
-            <intent android:action="android.intent.action.MAIN"
-                    android:targetPackage="com.android.settings"
-                    android:targetClass="com.android.settings.UsageStats" />
-        </PreferenceScreen>
-        
-    </PreferenceCategory>
-            
-    <PreferenceCategory
-        android:title="@string/general_title">
-        
-        <ListPreference
-                android:key="window_animations"
-                android:title="@string/title_window_animations"
-                android:summary="@string/summary_window_animations"
-                android:entries="@array/entries_animations"
-                android:entryValues="@array/entryvalues_animations"
-                android:dialogTitle="@string/dialog_title_window_animations" />
-                
-        <ListPreference
-                android:key="transition_animations"
-                android:title="@string/title_transition_animations"
-                android:summary="@string/summary_transition_animations"
-                android:entries="@array/entries_animations"
-                android:entryValues="@array/entryvalues_animations"
-                android:dialogTitle="@string/dialog_title_transition_animations" />
-        
-        <CheckBoxPreference 
-            android:key="fancy_ime_animations" 
-            android:title="@string/title_fancy_ime_animations" 
-            android:summaryOn="@string/summary_on_fancy_ime_animations"
-            android:summaryOff="@string/summary_off_fancy_ime_animations"/>
-        
-        <ListPreference
-                android:key="font_size"
-                android:title="@string/title_font_size"
-                android:summary="@string/summary_font_size"
-                android:entries="@array/entries_font_size"
-                android:entryValues="@array/entryvalues_font_size"
-                android:dialogTitle="@string/dialog_title_font_size" />
-        
-        <ListPreference
-                android:key="end_button"
-                android:title="@string/title_end_button"
-                android:summary="@string/summary_end_button"
-                android:entries="@array/entries_end_button"
-                android:entryValues="@array/entryvalues_end_button"
-                android:dialogTitle="@string/dialog_title_end_button" />
-        
-        <CheckBoxPreference 
-            android:key="haptic_feedback" 
-            android:title="@string/title_haptic_feedback" 
-            android:summaryOn="@string/summary_on_haptic_feedback"
-            android:summaryOff="@string/summary_off_haptic_feedback"/>
-        
-
-        <CheckBoxPreference
-                android:key="compatibility_mode"
-                android:title="@string/compatibility_mode_title"
-                android:summaryOn="@string/compatibility_mode_summary_on"
-                android:summaryOff="@string/compatibility_mode_summary_off" />
-    </PreferenceCategory>
-
-</PreferenceScreen>
diff --git a/apps/SpareParts/src/com/android/spare_parts/SpareParts.java b/apps/SpareParts/src/com/android/spare_parts/SpareParts.java
deleted file mode 100644
index 099f27a..0000000
--- a/apps/SpareParts/src/com/android/spare_parts/SpareParts.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/* //device/apps/Settings/src/com/android/settings/Keyguard.java
-**
-** Copyright 2006, 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.spare_parts;
-
-import android.app.ActivityManagerNative;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceScreen;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.IWindowManager;
-
-import java.util.List;
-
-public class SpareParts extends PreferenceActivity
-        implements Preference.OnPreferenceChangeListener,
-        SharedPreferences.OnSharedPreferenceChangeListener {
-    private static final String TAG = "SpareParts";
-
-    private static final String BATTERY_HISTORY_PREF = "battery_history_settings";
-    private static final String BATTERY_INFORMATION_PREF = "battery_information_settings";
-    private static final String USAGE_STATISTICS_PREF = "usage_statistics_settings";
-    
-    private static final String WINDOW_ANIMATIONS_PREF = "window_animations";
-    private static final String TRANSITION_ANIMATIONS_PREF = "transition_animations";
-    private static final String FANCY_IME_ANIMATIONS_PREF = "fancy_ime_animations";
-    private static final String HAPTIC_FEEDBACK_PREF = "haptic_feedback";
-    private static final String FONT_SIZE_PREF = "font_size";
-    private static final String END_BUTTON_PREF = "end_button";
-    private static final String KEY_COMPATIBILITY_MODE = "compatibility_mode";
-
-    private final Configuration mCurConfig = new Configuration();
-    
-    private ListPreference mWindowAnimationsPref;
-    private ListPreference mTransitionAnimationsPref;
-    private CheckBoxPreference mFancyImeAnimationsPref;
-    private CheckBoxPreference mHapticFeedbackPref;
-    private ListPreference mFontSizePref;
-    private ListPreference mEndButtonPref;
-    private CheckBoxPreference mCompatibilityMode;
-
-    private IWindowManager mWindowManager;
-
-    public static boolean updatePreferenceToSpecificActivityOrRemove(Context context,
-            PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) {
-        
-        Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
-        if (preference == null) {
-            return false;
-        }
-        
-        Intent intent = preference.getIntent();
-        if (intent != null) {
-            // Find the activity that is in the system image
-            PackageManager pm = context.getPackageManager();
-            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
-            int listSize = list.size();
-            for (int i = 0; i < listSize; i++) {
-                ResolveInfo resolveInfo = list.get(i);
-                if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
-                        != 0) {
-                    
-                    // Replace the intent with this specific activity
-                    preference.setIntent(new Intent().setClassName(
-                            resolveInfo.activityInfo.packageName,
-                            resolveInfo.activityInfo.name));
-                    
-                    return true;
-                }
-            }
-        }
-
-        // Did not find a matching activity, so remove the preference
-        parentPreferenceGroup.removePreference(preference);
-        
-        return true;
-    }
-    
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        addPreferencesFromResource(R.xml.spare_parts);
-
-        PreferenceScreen prefSet = getPreferenceScreen();
-        
-        mWindowAnimationsPref = (ListPreference) prefSet.findPreference(WINDOW_ANIMATIONS_PREF);
-        mWindowAnimationsPref.setOnPreferenceChangeListener(this);
-        mTransitionAnimationsPref = (ListPreference) prefSet.findPreference(TRANSITION_ANIMATIONS_PREF);
-        mTransitionAnimationsPref.setOnPreferenceChangeListener(this);
-        mFancyImeAnimationsPref = (CheckBoxPreference) prefSet.findPreference(FANCY_IME_ANIMATIONS_PREF);
-        mHapticFeedbackPref = (CheckBoxPreference) prefSet.findPreference(HAPTIC_FEEDBACK_PREF);
-        mFontSizePref = (ListPreference) prefSet.findPreference(FONT_SIZE_PREF);
-        mFontSizePref.setOnPreferenceChangeListener(this);
-        mEndButtonPref = (ListPreference) prefSet.findPreference(END_BUTTON_PREF);
-        mEndButtonPref.setOnPreferenceChangeListener(this);
-        mCompatibilityMode = (CheckBoxPreference) findPreference(KEY_COMPATIBILITY_MODE);
-        mCompatibilityMode.setPersistent(false);
-        mCompatibilityMode.setChecked(Settings.System.getInt(getContentResolver(),
-                Settings.System.COMPATIBILITY_MODE, 1) != 0);
-
-        mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
-        
-        final PreferenceGroup parentPreference = getPreferenceScreen();
-        updatePreferenceToSpecificActivityOrRemove(this, parentPreference,
-                BATTERY_HISTORY_PREF, 0);
-        updatePreferenceToSpecificActivityOrRemove(this, parentPreference,
-                BATTERY_INFORMATION_PREF, 0);
-        updatePreferenceToSpecificActivityOrRemove(this, parentPreference,
-                USAGE_STATISTICS_PREF, 0);
-        
-        parentPreference.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
-    }
-
-    private void updateToggles() {
-        mFancyImeAnimationsPref.setChecked(Settings.System.getInt(
-                getContentResolver(), 
-                Settings.System.FANCY_IME_ANIMATIONS, 0) != 0);
-        mHapticFeedbackPref.setChecked(Settings.System.getInt(
-                getContentResolver(), 
-                Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0);
-    }
-    
-    public boolean onPreferenceChange(Preference preference, Object objValue) {
-        if (preference == mWindowAnimationsPref) {
-            writeAnimationPreference(0, objValue);
-        } else if (preference == mTransitionAnimationsPref) {
-            writeAnimationPreference(1, objValue);
-        } else if (preference == mFontSizePref) {
-            writeFontSizePreference(objValue);
-        } else if (preference == mEndButtonPref) {
-            writeEndButtonPreference(objValue);
-        }
-        // always let the preference setting proceed.
-        return true;
-    }
-
-    @Override
-    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
-        if (preference == mCompatibilityMode) {
-            Settings.System.putInt(getContentResolver(),
-                    Settings.System.COMPATIBILITY_MODE,
-                    mCompatibilityMode.isChecked() ? 1 : 0);
-            return true;
-        }
-        return false;
-    }
-
-    public void writeAnimationPreference(int which, Object objValue) {
-        try {
-            float val = Float.parseFloat(objValue.toString());
-            mWindowManager.setAnimationScale(which, val);
-        } catch (NumberFormatException e) {
-        } catch (RemoteException e) {
-        }
-    }
-    
-    public void writeFontSizePreference(Object objValue) {
-        try {
-            mCurConfig.fontScale = Float.parseFloat(objValue.toString());
-            ActivityManagerNative.getDefault().updateConfiguration(mCurConfig);
-        } catch (RemoteException e) {
-        }
-    }
-    
-    public void writeEndButtonPreference(Object objValue) {
-        try {
-            int val = Integer.parseInt(objValue.toString());
-            Settings.System.putInt(getContentResolver(),
-                    Settings.System.END_BUTTON_BEHAVIOR, val);
-        } catch (NumberFormatException e) {
-        }
-    }
-    
-    int floatToIndex(float val, int resid) {
-        String[] indices = getResources().getStringArray(resid);
-        float lastVal = Float.parseFloat(indices[0]);
-        for (int i=1; i<indices.length; i++) {
-            float thisVal = Float.parseFloat(indices[i]);
-            if (val < (lastVal + (thisVal-lastVal)*.5f)) {
-                return i-1;
-            }
-            lastVal = thisVal;
-        }
-        return indices.length-1;
-    }
-    
-    public void readAnimationPreference(int which, ListPreference pref) {
-        try {
-            float scale = mWindowManager.getAnimationScale(which);
-            pref.setValueIndex(floatToIndex(scale,
-                    R.array.entryvalues_animations));
-        } catch (RemoteException e) {
-        }
-    }
-    
-    public void readFontSizePreference(ListPreference pref) {
-        try {
-            mCurConfig.updateFrom(
-                ActivityManagerNative.getDefault().getConfiguration());
-        } catch (RemoteException e) {
-        }
-        pref.setValueIndex(floatToIndex(mCurConfig.fontScale,
-                R.array.entryvalues_font_size));
-    }
-    
-    public void readEndButtonPreference(ListPreference pref) {
-        try {
-            pref.setValueIndex(Settings.System.getInt(getContentResolver(),
-                    Settings.System.END_BUTTON_BEHAVIOR));
-        } catch (SettingNotFoundException e) {
-        }
-    }
-    
-    public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
-        if (FANCY_IME_ANIMATIONS_PREF.equals(key)) {
-            Settings.System.putInt(getContentResolver(),
-                    Settings.System.FANCY_IME_ANIMATIONS,
-                    mFancyImeAnimationsPref.isChecked() ? 1 : 0);
-        } else if (HAPTIC_FEEDBACK_PREF.equals(key)) {
-            Settings.System.putInt(getContentResolver(),
-                    Settings.System.HAPTIC_FEEDBACK_ENABLED,
-                    mHapticFeedbackPref.isChecked() ? 1 : 0);
-        }
-    }
-    
-    @Override
-    public void onResume() {
-        super.onResume();
-        readAnimationPreference(0, mWindowAnimationsPref);
-        readAnimationPreference(1, mTransitionAnimationsPref);
-        readFontSizePreference(mFontSizePref);
-        readEndButtonPreference(mEndButtonPref);
-        updateToggles();
-    }
-}
diff --git a/build/tools/mk_sdk_repo_xml.sh b/build/tools/mk_sdk_repo_xml.sh
index f7bbec1..56f426b 100755
--- a/build/tools/mk_sdk_repo_xml.sh
+++ b/build/tools/mk_sdk_repo_xml.sh
@@ -45,7 +45,7 @@
 shift
 
 # Get XML:NS for SDK from the schema
-XMLNS=$(sed -n '/xmlns:.*schemas.android.com\/sdk\/android\//s/.*"\(.*\)".*/\1/p' "$SCHEMA")
+XMLNS=$(sed -n '/xmlns:sdk="/s/.*"\(.*\)".*/\1/p' "$SCHEMA")
 [[ -z "$XMLNS" ]] && error "Failed to find xmlns:sdk in $SCHEMA."
 echo "## Using xmlns:sdk=$XMLNS"
 
diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath
index 1323287..47d94bb 100644
--- a/ide/eclipse/.classpath
+++ b/ide/eclipse/.classpath
@@ -11,7 +11,7 @@
 	<classpathentry kind="src" path="packages/apps/Email/src"/>
 	<classpathentry kind="src" path="packages/apps/Email/emailcommon/src"/>
 	<classpathentry kind="src" path="packages/apps/Exchange/src"/>
-	<classpathentry kind="src" path="packages/apps/Gallery3D/src"/>
+	<classpathentry kind="src" path="packages/apps/Gallery3D/new3d/src"/>
 	<classpathentry kind="src" path="packages/apps/HTMLViewer/src"/>
 	<classpathentry kind="src" path="packages/apps/Launcher2/src"/>
 	<classpathentry kind="src" path="packages/apps/Mms/src"/>
diff --git a/ide/eclipse/android-formatting-35.xml b/ide/eclipse/android-formatting-35.xml
new file mode 100644
index 0000000..b827782
--- /dev/null
+++ b/ide/eclipse/android-formatting-35.xml
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<profiles version="10">
+<profile name="Android" version="10">
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="100"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+</profile>
+</profiles>
diff --git a/ndk/platforms/android-9/include/EGL/eglext.h b/ndk/platforms/android-9/include/EGL/eglext.h
index 1ffcd56..1123e16 100644
--- a/ndk/platforms/android-9/include/EGL/eglext.h
+++ b/ndk/platforms/android-9/include/EGL/eglext.h
@@ -225,7 +225,7 @@
 
 #ifndef EGL_ANDROID_image_native_buffer
 #define EGL_ANDROID_image_native_buffer 1
-struct android_native_buffer_t;
+struct ANativeWindowBuffer;
 #define EGL_NATIVE_BUFFER_ANDROID       0x3140  /* eglCreateImageKHR target */
 #endif
 
diff --git a/ndk/platforms/android-9/samples/native-activity/Android.mk b/ndk/platforms/android-9/samples/native-activity/Android.mk
index ea961ca..73b3d87 100644
--- a/ndk/platforms/android-9/samples/native-activity/Android.mk
+++ b/ndk/platforms/android-9/samples/native-activity/Android.mk
@@ -44,7 +44,7 @@
 
 LOCAL_SHARED_LIBRARIES := liblog libandroid libEGL libGLESv1_CM
 
-LOCAL_PRELINK_MODULE := false
+
 
 LOCAL_MODULE := libnative-activity
 
diff --git a/pdk/docs/compatibility/downloads.jd b/pdk/docs/compatibility/downloads.jd
index 27a2109..79ad81b 100644
--- a/pdk/docs/compatibility/downloads.jd
+++ b/pdk/docs/compatibility/downloads.jd
@@ -11,7 +11,7 @@
 </p>
 <ul>
   <li><a href="{@docRoot}compatibility/android-2.3.3-cdd.pdf">Android 2.3.3 Compatibility Definition Document (CDD)</a></li>
-  <li><a href="http://dl.google.com/dl/android/cts/android-cts-2.3_r1-x86.zip">Android 2.3 R1 Compatibility Test Suite (CTS)</a></li>
+  <li><a href="http://dl.google.com/dl/android/cts/android-cts-2.3_r2-x86.zip">Android 2.3 R2 Compatibility Test Suite (CTS)</a></li>
 </ul>
 
 <h2>Android 2.2</h2>
@@ -21,7 +21,7 @@
 </p>
 <ul>
   <li><a href="{@docRoot}compatibility/android-2.2-cdd.pdf">Android 2.2 Compatibility Definition Document (CDD)</a></li>
-  <li><a href="http://dl.google.com/dl/android/cts/android-cts-2.2_r4-x86.zip">Android 2.2 R4 Compatibility Test Suite (CTS)</a></li>
+  <li><a href="http://dl.google.com/dl/android/cts/android-cts-2.2_r5-x86.zip">Android 2.2 R5 Compatibility Test Suite (CTS)</a></li>
 </ul>
 
 <h2>Android 2.1</h2>
@@ -41,6 +41,7 @@
 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>
+  <li><a href="http://dl.google.com/dl/android/cts/android-cts-1.6_r1-x86.zip">Android 1.6 R1 Compatibility Test Suite (CTS)</a></li>
 </ul>
 
 <h2>Compatibility Test Suite Manual</h2>
diff --git a/pdk/docs/porting/bluetooth.jd b/pdk/docs/porting/bluetooth.jd
index c792bd2..03b2d94 100755
--- a/pdk/docs/porting/bluetooth.jd
+++ b/pdk/docs/porting/bluetooth.jd
@@ -17,12 +17,15 @@
 </div>
 </div>
 
-<p>Android's Bluetooth stack uses BlueZ for GAP, SDP, and RFCOMM profiles, and
-is a SIG-qualified Bluetooth stack. </p>
+<p>Android's Bluetooth stack uses BlueZ as the host 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>
 
-<p>Headset and Handsfree (v1.5) profiles are implemented in the Android framework and are both tightly coupled with the Phone App. These profiles are also SIG qualified.</p>
+<p>The qualification notes mentioned below are example qualifications of the particular device in question. Each company has to re-qualify their product with Bluetooth SIG even if no changes are made to the Bluetooth stack.</p>
+
+<p>Headset and Handsfree (v1.5) profiles are implemented in the Android framework and are both tightly coupled with the Phone App. Profiles like OPP and PBAP are based on java obex. These profiles open a rfcomm socket connection into Bluez kernel bypassing the Bluez userspace stack.</p>
+
+<p> Profiles like A2DP, AVRCP, HID, PAN and other bluetooth functionality like pairing and scanning use the Bluez userspace stack.</p>
 
 <p>The diagram below offers a library-oriented view of the Bluetooth stack. Click <a href="bluetooth/bluetooth_process.html">Bluetooth Process Diagram</a> for a process-oriented view.</p>
 
@@ -79,7 +82,7 @@
 </pre>
 
 <p><strong>Deamon Logs</strong></p>
-<p>Deamon logs for <code>hcid</code> (<code>STDOUT</code>) and <code>hciattach</code> (<code>STDERR</code>) are sent to <code>/dev/null</code> by default. Edit <code>init.rc</code></span> and <code>init.PLATFORM.rc</code></span> to run these daemons under <code>logwrapper</code>, which redirects output to <code>logcat</code>.</p>
+<p>Deamon logs for <code>bluetoothd</code> (<code>STDOUT</code>) and <code>hciattach</code> (<code>STDERR</code>) are sent to <code>/dev/null</code> by default. Edit <code>init.rc</code></span> and <code>init.PLATFORM.rc</code></span> to run these daemons under <code>logwrapper</code>, which redirects output to <code>logcat</code>.</p>
 <p><strong>hciconfig -a and hcitool</strong></p>
 <p>If you compile your own system.img for Android, and <code>hciconfig -a</code> works but <code>hcitool</code> scan doesn't, try installing the firmware for the Bluetooth chipset. This firmware isn't yet available in the open source codebase, but you can <code>adb pull</code> and then <code>adb push</code>it from a stock T-Mobile G1 (located in <code>/etc/firmware/brf6300.bin</code>).<br />
   <a name="androidBluetoothTools"></a></p>
@@ -112,7 +115,7 @@
     </li>
   </ul>
 </ul>
-<h5>Qualifications</h5>
+<h5>Qualifications for HTC G1 product. Each company reusing this software version has to re-qualify with Bluetooth SIG</h5>
 <ul>
   <li>QDID B014524: Host stack (SDP, L2CAP, GAP, RFCOMM, SPP)</li>
   <li>QDID B014624: EPL for HTC Dream (HSP, HFP)</li>
@@ -154,7 +157,7 @@
     <li>play/pause/stop/prev/next</li>
   </ul>
 </ul>
-<h4>Qualifications</h4>
+<h5>Qualifications for HTC Sapphire product. Each company reusing this software version has to re-qualify with Bluetooth SIG</h5>
 <ul>
   <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>
@@ -224,6 +227,22 @@
   <li>Improved compatibility with headsets and car kits. </li>
 </ul>
 
+<h4>Android 2.3  release (Gingerbread)</h4>
+<h4>Platform features</h4>
+<ul>
+  <li>Based on Bluez 4.69 with Linux Kernel 2.6.35</li>
+  <li>No new profiles added.</li>
+  <li>Improved compatibility with headsets and car kits. </li>
+</ul>
+
+<h4>Android 3.0 release (Honeycomb)</h4>
+<h4>Platform features</h4>
+<ul>
+  <li>Based on Bluez 4.69 with Linux Kernel 2.6.36</li>
+  <li>HID and PAN (NAP and PANU role) are the new profiles added.
+  <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>
@@ -234,20 +253,3 @@
   <li>Bluetooth Low Energy </li>
 </ul>
 
-<p><strong>Development Notes</strong></p>
-<ul>
-  <li><strong>HID Support <br />
-  </strong>Cupcake features some early work&#151;Bluez has an HID plugin, <code>external/bluez/utils/input/Android.mk</code>, which gets compiled. <br />
-    <br />
-You can interact directly with this plugin using <code>dbus-send</code></span> and <code>dbus-monitor</code>. While not officially supported, you should be able to connect and use a HID keyboard and mouse using the Bluez HID plugin API. Next steps include plumbing the plugin API in the Android Java framework and offering better support for HID input methods (new keymaps and mouse support).<br />
-  <br />
-  </li>
-  <li>  <strong>Tethering - DUN and PAN Support</strong><br />
-    Cupcake features some early work&#151;Bluez has has DUN and PAN daemons which get compiled and <code>external/bluez/utils/dun/Android.mk
-  external/bluez/utils/pan/Android.mk
-BNEP</code> support is compiled into the kernel with cupcake. <br />
-<br />
-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>
-</ul>
diff --git a/pdk/docs/porting/images/androidBluetooth.gif b/pdk/docs/porting/images/androidBluetooth.gif
index e62f5a8..1c1aa3f 100755
--- a/pdk/docs/porting/images/androidBluetooth.gif
+++ b/pdk/docs/porting/images/androidBluetooth.gif
Binary files differ
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index c570322..d591e68 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -539,7 +539,7 @@
 
         <!-- Service Samples -->
 
-        <service android:name=".app.LocalService" />
+        <service android:name=".app.LocalService" android:stopWithTask="true" />
 
         <activity android:name=".app.LocalServiceActivities$Controller"
                 android:label="@string/activity_local_service_controller"
diff --git a/samples/ApiDemos/res/layout-h974dp/resources_height.xml b/samples/ApiDemos/res/layout-h974dp/resources_height.xml
new file mode 100644
index 0000000..5eb120d
--- /dev/null
+++ b/samples/ApiDemos/res/layout-h974dp/resources_height.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<!-- Part of resources_width_and_height that varies based on height. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"
+            android:orientation="vertical">
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#8000ff00">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#80ff0000">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#8000ff00">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#80ff0000">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#8000ff00">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#80ff0000">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#8000ff00">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#80ff0000">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#8000ff00">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#80ff0000">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#8000ff00">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+        <FrameLayout android:layout_width="match_parent" android:layout_height="0px"
+                android:layout_weight="1" android:padding="4dp"
+                android:background="#80ff0000">
+            <include layout="@layout/resources_width" />
+        </FrameLayout>
+    </LinearLayout>
+</merge>
diff --git a/samples/ApiDemos/res/layout-w1024dp/resources_width.xml b/samples/ApiDemos/res/layout-w1024dp/resources_width.xml
new file mode 100644
index 0000000..05ea7ba
--- /dev/null
+++ b/samples/ApiDemos/res/layout-w1024dp/resources_width.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<!-- Part of resources_width_and_height that varies based on width. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"
+            android:orientation="horizontal">
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#1">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#2">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#3">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#4">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#5">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#6">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#7">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#8">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#9">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#10">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#11">
+        </TextView>
+        <TextView android:layout_width="0px" android:layout_height="match_parent"
+                android:layout_weight="1" android:gravity="center"
+                android:layout_marginLeft="4dp" android:layout_marginRight="4dp"
+                android:background="#800000ff" android:text="w1024dp Width\n#12">
+        </TextView>
+    </LinearLayout>
+</merge>
diff --git a/samples/ApiDemos/res/layout/fragment_pager.xml b/samples/ApiDemos/res/layout/fragment_pager_support.xml
similarity index 65%
rename from samples/ApiDemos/res/layout/fragment_pager.xml
rename to samples/ApiDemos/res/layout/fragment_pager_support.xml
index 3867f46..a082e2e 100644
--- a/samples/ApiDemos/res/layout/fragment_pager.xml
+++ b/samples/ApiDemos/res/layout/fragment_pager_support.xml
@@ -28,10 +28,17 @@
             android:layout_weight="1">
     </android.support.v4.app.FragmentPager>
 
-    <Button android:id="@+id/new_fragment"
-        android:layout_width="wrap_content" android:layout_height="wrap_content"
-        android:layout_weight="0"
-        android:text="@string/new_fragment">
-        <requestFocus />
-    </Button>
+    <LinearLayout android:orientation="horizontal"
+            android:gravity="center" android:measureWithLargestChild="true"
+            android:layout_width="match_parent" android:layout_height="wrap_content"
+            android:layout_weight="0">
+        <Button android:id="@+id/goto_first"
+            android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="@string/first">
+        </Button>
+        <Button android:id="@+id/goto_last"
+            android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="@string/last">
+        </Button>
+    </LinearLayout>
 </LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_pager_list.xml b/samples/ApiDemos/res/layout/fragment_pager_support_list.xml
similarity index 100%
rename from samples/ApiDemos/res/layout/fragment_pager_list.xml
rename to samples/ApiDemos/res/layout/fragment_pager_support_list.xml
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index d7858f0..024975a 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -175,6 +175,8 @@
     <string name="fragment_stack_support">Support/App/Fragment/Stack</string>
 
     <string name="fragment_pager_support">Support/App/Fragment/Pager</string>
+    <string name="first">First</string>
+    <string name="last">Last</string>
 
     <string name="loader_cursor">App/Loader/Cursor</string>
 
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java b/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
index 9f84be1..123e369 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
@@ -66,7 +66,7 @@
     
     void invokeMethod(Method method, Object[] args) {
         try {
-            mStartForeground.invoke(this, mStartForegroundArgs);
+            method.invoke(this, args);
         } catch (InvocationTargetException e) {
             // Should not happen.
             Log.w("ApiDemos", "Unable to invoke method", e);
@@ -103,15 +103,7 @@
         // If we have the new stopForeground API, then use it.
         if (mStopForeground != null) {
             mStopForegroundArgs[0] = Boolean.TRUE;
-            try {
-                mStopForeground.invoke(this, mStopForegroundArgs);
-            } catch (InvocationTargetException e) {
-                // Should not happen.
-                Log.w("ApiDemos", "Unable to invoke stopForeground", e);
-            } catch (IllegalAccessException e) {
-                // Should not happen.
-                Log.w("ApiDemos", "Unable to invoke stopForeground", e);
-            }
+            invokeMethod(mStopForeground, mStopForegroundArgs);
             return;
         }
         
@@ -130,10 +122,10 @@
                     mStartForegroundSignature);
             mStopForeground = getClass().getMethod("stopForeground",
                     mStopForegroundSignature);
+            return;
         } catch (NoSuchMethodException e) {
             // Running on an older platform.
             mStartForeground = mStopForeground = null;
-            return;
         }
         try {
             mSetForeground = getClass().getMethod("setForeground",
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.java b/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.java
index a62a524..aade659 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.java
@@ -135,8 +135,13 @@
     };
 // END_INCLUDE(exposing_a_service)
     
-    private static final int REPORT_MSG = 1;
+    @Override
+    public void onTaskRemoved(Intent rootIntent) {
+        Toast.makeText(this, "Task removed: " + rootIntent, Toast.LENGTH_LONG).show();
+    }
     
+    private static final int REPORT_MSG = 1;
+
     /**
      * Our Handler used to execute operations on the main thread.  This is used
      * to schedule increments of our value.
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/Compass.java b/samples/ApiDemos/src/com/example/android/apis/graphics/Compass.java
index 85471d9..241d98f 100644
--- a/samples/ApiDemos/src/com/example/android/apis/graphics/Compass.java
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/Compass.java
@@ -23,7 +23,6 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.os.Bundle;
-import android.util.Config;
 import android.util.Log;
 import android.view.View;
 
@@ -38,7 +37,7 @@
 
     private final SensorEventListener mListener = new SensorEventListener() {
         public void onSensorChanged(SensorEvent event) {
-            if (Config.DEBUG) Log.d(TAG,
+            if (false) Log.d(TAG,
                     "sensorChanged (" + event.values[0] + ", " + event.values[1] + ", " + event.values[2] + ")");
             mValues = event.values;
             if (mView != null) {
@@ -62,7 +61,7 @@
     @Override
     protected void onResume()
     {
-        if (Config.DEBUG) Log.d(TAG, "onResume");
+        if (false) Log.d(TAG, "onResume");
         super.onResume();
 
         mSensorManager.registerListener(mListener, mSensor,
@@ -72,7 +71,7 @@
     @Override
     protected void onStop()
     {
-        if (Config.DEBUG) Log.d(TAG, "onStop");
+        if (false) Log.d(TAG, "onStop");
         mSensorManager.unregisterListener(mListener);
         super.onStop();
     }
@@ -117,14 +116,14 @@
         @Override
         protected void onAttachedToWindow() {
             mAnimate = true;
-            if (Config.DEBUG) Log.d(TAG, "onAttachedToWindow. mAnimate=" + mAnimate);
+            if (false) Log.d(TAG, "onAttachedToWindow. mAnimate=" + mAnimate);
             super.onAttachedToWindow();
         }
 
         @Override
         protected void onDetachedFromWindow() {
             mAnimate = false;
-            if (Config.DEBUG) Log.d(TAG, "onDetachedFromWindow. mAnimate=" + mAnimate);
+            if (false) Log.d(TAG, "onDetachedFromWindow. mAnimate=" + mAnimate);
             super.onDetachedFromWindow();
         }
     }
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/SensorTest.java b/samples/ApiDemos/src/com/example/android/apis/graphics/SensorTest.java
index dc07a27..7273cae 100644
--- a/samples/ApiDemos/src/com/example/android/apis/graphics/SensorTest.java
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/SensorTest.java
@@ -23,7 +23,6 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.os.Bundle;
-import android.util.Config;
 import android.util.Log;
 import android.view.View;
 
@@ -142,21 +141,21 @@
         mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
         mView = new SampleView(this);
         setContentView(mView);
-        if (Config.DEBUG) Log.d(TAG, "create " + mSensorManager);
+        if (false) Log.d(TAG, "create " + mSensorManager);
     }
 
     @Override
     protected void onResume() {
         super.onResume();
         mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_FASTEST);
-        if (Config.DEBUG) Log.d(TAG, "resume " + mSensorManager);
+        if (false) Log.d(TAG, "resume " + mSensorManager);
     }
 
     @Override
     protected void onStop() {
         mSensorManager.unregisterListener(mListener);
         super.onStop();
-        if (Config.DEBUG) Log.d(TAG, "stop " + mSensorManager);
+        if (false) Log.d(TAG, "stop " + mSensorManager);
     }
 
     private class SampleView extends View {
@@ -200,14 +199,14 @@
         @Override
         protected void onAttachedToWindow() {
             mAnimate = true;
-            if (Config.DEBUG) Log.d(TAG, "onAttachedToWindow. mAnimate="+mAnimate);
+            if (false) Log.d(TAG, "onAttachedToWindow. mAnimate="+mAnimate);
             super.onAttachedToWindow();
         }
 
         @Override
         protected void onDetachedFromWindow() {
             mAnimate = false;
-            if (Config.DEBUG) Log.d(TAG, "onAttachedToWindow. mAnimate="+mAnimate);
+            if (false) Log.d(TAG, "onAttachedToWindow. mAnimate="+mAnimate);
             super.onDetachedFromWindow();
         }
     }
diff --git a/samples/ApiDemos/src/com/example/android/apis/support/app/FragmentPagerSupport.java b/samples/ApiDemos/src/com/example/android/apis/support/app/FragmentPagerSupport.java
index 46d29ac..c78c6d3 100644
--- a/samples/ApiDemos/src/com/example/android/apis/support/app/FragmentPagerSupport.java
+++ b/samples/ApiDemos/src/com/example/android/apis/support/app/FragmentPagerSupport.java
@@ -40,26 +40,28 @@
     static final int NUM_ITEMS = 10;
 
     FragmentPager mPager;
-    int mCurPos;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.fragment_pager);
+        setContentView(R.layout.fragment_pager_support);
 
         mPager = (FragmentPager)findViewById(R.id.pager);
         mPager.setAdapter(this);
 
+        Button button;
+        
         // Watch for button clicks.
-        Button button = (Button)findViewById(R.id.new_fragment);
+        button = (Button)findViewById(R.id.goto_first);
         button.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
-                mCurPos++;
-                if (mCurPos < NUM_ITEMS) {
-                    mPager.setCurrentItem(mCurPos);
-                } else {
-                    mCurPos--;
-                }
+                mPager.setCurrentItem(0);
+            }
+        });
+        button = (Button)findViewById(R.id.goto_last);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                mPager.setCurrentItem(NUM_ITEMS-1);
             }
         });
     }
@@ -108,7 +110,7 @@
         @Override
         public View onCreateView(LayoutInflater inflater, ViewGroup container,
                 Bundle savedInstanceState) {
-            View v = inflater.inflate(R.layout.fragment_pager_list, container, false);
+            View v = inflater.inflate(R.layout.fragment_pager_support_list, container, false);
             View tv = v.findViewById(R.id.text);
             ((TextView)tv).setText("Fragment #" + mNum);
             return v;
diff --git a/samples/BrowserPlugin/jni/Android.mk b/samples/BrowserPlugin/jni/Android.mk
index b153e37..641ac63 100644
--- a/samples/BrowserPlugin/jni/Android.mk
+++ b/samples/BrowserPlugin/jni/Android.mk
@@ -41,6 +41,8 @@
 	video/VideoPlugin.cpp \
 	jni-bridge.cpp \
 
+WEBCORE_PATH := external/webkit/Source/WebCore
+
 LOCAL_C_INCLUDES += \
 	$(JNI_H_INCLUDE) \
 	$(LOCAL_PATH) \
@@ -51,10 +53,10 @@
 	$(LOCAL_PATH)/navigation \
 	$(LOCAL_PATH)/paint \
 	$(LOCAL_PATH)/video \
-	external/webkit/WebCore/bridge \
-	external/webkit/WebCore/plugins \
-	external/webkit/WebCore/platform/android/JavaVM \
-	external/webkit/WebKit/android/plugins \
+	$(WEBCORE_PATH)/bridge \
+	$(WEBCORE_PATH)/plugins \
+	$(WEBCORE_PATH)/platform/android/JavaVM \
+	external/webkit/Source/WebKit/android/plugins \
 	external/skia/include/core
 
 LOCAL_SHARED_LIBRARIES := \
@@ -66,7 +68,7 @@
 	libskia
 
 LOCAL_CFLAGS += -fvisibility=hidden 
-LOCAL_PRELINK_MODULE:=false
+
 
 LOCAL_MODULE:= libsampleplugin
 
diff --git a/samples/RenderScript/Balls/src/com/example/android/rs/balls/Balls.java b/samples/RenderScript/Balls/src/com/example/android/rs/balls/Balls.java
index d3b900a..2a7436a 100644
--- a/samples/RenderScript/Balls/src/com/example/android/rs/balls/Balls.java
+++ b/samples/RenderScript/Balls/src/com/example/android/rs/balls/Balls.java
@@ -26,7 +26,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.provider.Settings.System;
-import android.util.Config;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -51,7 +50,7 @@
 
     private static final String LOG_TAG = "libRS_jni";
     private static final boolean DEBUG  = false;
-    private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+    private static final boolean LOG_ENABLED = false;
 
     private BallsView mView;
     private SensorManager mSensorManager;
diff --git a/samples/RenderScript/Balls/src/com/example/android/rs/balls/balls.rs b/samples/RenderScript/Balls/src/com/example/android/rs/balls/balls.rs
index d86b804..f94e3e2 100644
--- a/samples/RenderScript/Balls/src/com/example/android/rs/balls/balls.rs
+++ b/samples/RenderScript/Balls/src/com/example/android/rs/balls/balls.rs
@@ -56,12 +56,12 @@
     Ball_t *bout;
 
     if (frame & 1) {
-        rsSetObject(&bc.ain, rsGetAllocation(balls2));
-        rsSetObject(&bc.aout, rsGetAllocation(balls1));
+        bc.ain = rsGetAllocation(balls2);
+        bc.aout = rsGetAllocation(balls1);
         bout = balls2;
     } else {
-        rsSetObject(&bc.ain, rsGetAllocation(balls1));
-        rsSetObject(&bc.aout, rsGetAllocation(balls2));
+        bc.ain = rsGetAllocation(balls1);
+        bc.aout = rsGetAllocation(balls2);
         bout = balls1;
     }
 
@@ -78,8 +78,6 @@
     frame++;
     rsgBindProgramFragment(gPFPoints);
     rsgDrawMesh(partMesh);
-    rsClearObject(&bc.ain);
-    rsClearObject(&bc.aout);
     return 1;
 }
 
diff --git a/samples/RenderScript/Fountain/src/com/example/android/rs/fountain/Fountain.java b/samples/RenderScript/Fountain/src/com/example/android/rs/fountain/Fountain.java
index 53b4f26..311455a 100644
--- a/samples/RenderScript/Fountain/src/com/example/android/rs/fountain/Fountain.java
+++ b/samples/RenderScript/Fountain/src/com/example/android/rs/fountain/Fountain.java
@@ -26,7 +26,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.provider.Settings.System;
-import android.util.Config;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -42,7 +41,7 @@
 
     private static final String LOG_TAG = "libRS_jni";
     private static final boolean DEBUG  = false;
-    private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+    private static final boolean LOG_ENABLED = false;
 
     private FountainView mView;
 
diff --git a/samples/RenderScript/HelloWorld/src/com/example/android/rs/helloworld/helloworld.rs b/samples/RenderScript/HelloWorld/src/com/example/android/rs/helloworld/helloworld.rs
index 34e940a..bcf624e 100644
--- a/samples/RenderScript/HelloWorld/src/com/example/android/rs/helloworld/helloworld.rs
+++ b/samples/RenderScript/HelloWorld/src/com/example/android/rs/helloworld/helloworld.rs
@@ -31,7 +31,7 @@
     gTouchY = 50.0f;
 }
 
-int root(int launchID) {
+int root(void) {
 
     // Clear the background color
     rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
diff --git a/samples/RenderScript/MiscSamples/src/com/example/android/rs/miscsamples/rslist.rs b/samples/RenderScript/MiscSamples/src/com/example/android/rs/miscsamples/rslist.rs
index 7b2dae2..d9d450d 100644
--- a/samples/RenderScript/MiscSamples/src/com/example/android/rs/miscsamples/rslist.rs
+++ b/samples/RenderScript/MiscSamples/src/com/example/android/rs/miscsamples/rslist.rs
@@ -34,7 +34,7 @@
 
 int textPos = 0;
 
-int root(int launchID) {
+int root(void) {
 
     rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
 
@@ -45,7 +45,7 @@
     rsgBindFont(gItalic);
 
     rs_allocation listAlloc;
-    rsSetObject(&listAlloc, rsGetAllocation(gList));
+    listAlloc = rsGetAllocation(gList);
     int allocSize = rsAllocationGetDimX(listAlloc);
 
     int width = rsgGetWidth();
diff --git a/samples/RenderScript/MiscSamples/src/com/example/android/rs/miscsamples/rsrenderstates.rs b/samples/RenderScript/MiscSamples/src/com/example/android/rs/miscsamples/rsrenderstates.rs
index b8ec8aa..5dabd00 100644
--- a/samples/RenderScript/MiscSamples/src/com/example/android/rs/miscsamples/rsrenderstates.rs
+++ b/samples/RenderScript/MiscSamples/src/com/example/android/rs/miscsamples/rsrenderstates.rs
@@ -633,7 +633,7 @@
     }
 }
 
-int root(int launchID) {
+int root(void) {
 
     gDt = rsGetDt();
 
diff --git a/samples/SampleSyncAdapter/Android.mk b/samples/SampleSyncAdapter/Android.mk
index a27a68f..0f87c17 100644
--- a/samples/SampleSyncAdapter/Android.mk
+++ b/samples/SampleSyncAdapter/Android.mk
@@ -6,10 +6,12 @@
 # Only compile source java files in this apk.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := Voiper
+LOCAL_PACKAGE_NAME := SampleSyncAdapter
 
 LOCAL_SDK_VERSION := current
 
+LOCAL_DX_FLAGS=--target-api=11
+
 include $(BUILD_PACKAGE)
 
 # Use the folloing include to make our test apk.
diff --git a/samples/SampleSyncAdapter/AndroidManifest.xml b/samples/SampleSyncAdapter/AndroidManifest.xml
index 202ed0e..fd53a16 100644
--- a/samples/SampleSyncAdapter/AndroidManifest.xml
+++ b/samples/SampleSyncAdapter/AndroidManifest.xml
@@ -46,7 +46,7 @@
     <uses-permission
         android:name="android.permission.WRITE_SYNC_SETTINGS" />
 
-    <uses-sdk android:minSdkVersion="5" />
+    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="11"/>
         
     <application
         android:icon="@drawable/icon"
@@ -82,11 +82,36 @@
             android:label="@string/ui_activity_title"
             android:theme="@android:style/Theme.Dialog"
             android:excludeFromRecents="true"
+            android:configChanges="orientation"
             >
             <!--
                 No intent-filter here! This activity is only ever launched by
                 someone who explicitly knows the class name
             -->
         </activity>
+
+        <activity
+            android:name=".editor.ContactEditorActivity"
+            android:theme="@style/ContactEditTheme"
+            android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action
+                    android:name="android.intent.action.INSERT" />
+                <data
+                    android:mimeType="vnd.android.cursor.item/contact" />
+            </intent-filter>
+
+            <!--
+                Note that the editor gets a raw contact URI, but is expected to call
+                setResult with the corresponding aggregate contact URI, not raw contact
+                URI.
+            -->
+            <intent-filter>
+                <action
+                    android:name="android.intent.action.EDIT" />
+                <data
+                    android:mimeType="vnd.android.cursor.item/raw_contact" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/samples/SampleSyncAdapter/_index.html b/samples/SampleSyncAdapter/_index.html
index 4191ba5..603083a 100644
--- a/samples/SampleSyncAdapter/_index.html
+++ b/samples/SampleSyncAdapter/_index.html
@@ -2,7 +2,8 @@
 cloud-based service and synchronize its data with data stored locally in a
 content provider. The sample uses two related parts of the Android framework
 &mdash; the account manager and the synchronization manager (through a sync
-adapter).</p>
+adapter). It also demonstrates how to provide users the ability to create
+and edit synchronized contacts using a custom editor.</p>
 
 <p> The <a
 href="../../../reference/android/accounts/AccountManager.html">account
@@ -26,7 +27,7 @@
 issues a sync operation for that sync adapter. </p>
 
 <p> The cloud-based service for this sample application is running at: </p>
-<p style="margin-left:2em;">http://samplesyncadapter.appspot.com/users</p>
+<p style="margin-left:2em;">http://samplesyncadapter2.appspot.com/</p>
 
 <p>When you install this sample application, a new syncable "SampleSyncAdapter"
 account will be added to your phone's account manager. You can go to "Settings |
diff --git a/samples/SampleSyncAdapter/res/drawable/border.xml b/samples/SampleSyncAdapter/res/drawable/border.xml
new file mode 100644
index 0000000..ab71f2c
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/drawable/border.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+  <solid android:color="@color/EditPanelBackgroundColor" />
+  <stroke android:width="2dip" android:color="@color/EditPanelBorderColor" />
+  <padding android:left="5dip" android:top="5dip" android:right="5dip" android:bottom="5dip" />
+</shape>
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/res/drawable/done_menu_icon.png b/samples/SampleSyncAdapter/res/drawable/done_menu_icon.png
new file mode 100644
index 0000000..3468bbd
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/drawable/done_menu_icon.png
Binary files differ
diff --git a/samples/SampleSyncAdapter/res/layout-xlarge/editor.xml b/samples/SampleSyncAdapter/res/layout-xlarge/editor.xml
new file mode 100644
index 0000000..3b7d97b
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/layout-xlarge/editor.xml
@@ -0,0 +1,43 @@
+<?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="match_parent"
+    android:orientation="vertical"
+    android:gravity="center_horizontal">
+    <LinearLayout
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="600dip"
+      android:layout_height="match_parent"
+      android:orientation="vertical"
+      android:background="@drawable/border">
+      <include layout="@layout/editor_header" />
+      <ScrollView
+          android:layout_width="match_parent"
+          android:layout_height="0dip"
+          android:paddingTop="20dip"
+          android:paddingRight="20dip"
+          android:paddingBottom="20dip"
+          android:paddingLeft="20dip"
+          android:layout_weight="1">
+          <include layout="@layout/editor_fields" />
+      </ScrollView>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/res/layout-xlarge/editor_header.xml b/samples/SampleSyncAdapter/res/layout-xlarge/editor_header.xml
new file mode 100644
index 0000000..648e6f9
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/layout-xlarge/editor_header.xml
@@ -0,0 +1,58 @@
+<?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.
+-->
+
+
+<!-- Account info header -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="64dip"
+    android:layout_width="match_parent"
+    android:background="?android:attr/selectableItemBackground">
+
+    <ImageView
+        android:id="@+id/header_account_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="7dip"
+        android:layout_marginRight="7dip"
+        android:layout_centerVertical="true"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/icon" />
+
+    <TextView
+        android:id="@+id/header_account_type"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toLeftOf="@+id/header_account_icon"
+        android:layout_alignTop="@id/header_account_icon"
+        android:layout_marginTop="-4dip"
+        android:textSize="24sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:singleLine="true"
+        android:text="@string/header_account_type" />
+
+    <TextView
+        android:id="@+id/header_account_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toLeftOf="@+id/header_account_icon"
+        android:layout_alignBottom="@+id/header_account_icon"
+        android:layout_marginBottom="2dip"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorPrimary"
+        android:singleLine="true" />
+
+</RelativeLayout>
diff --git a/samples/SampleSyncAdapter/res/layout/editor.xml b/samples/SampleSyncAdapter/res/layout/editor.xml
new file mode 100644
index 0000000..a0c36d2
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/layout/editor.xml
@@ -0,0 +1,36 @@
+<?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="match_parent"
+    android:orientation="vertical">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:paddingTop="20dip"
+        android:paddingRight="20dip"
+        android:paddingBottom="20dip"
+        android:paddingLeft="20dip"
+        android:layout_weight="1">
+        <include layout="@layout/editor_fields" />
+    </ScrollView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/res/layout/editor_fields.xml b/samples/SampleSyncAdapter/res/layout/editor_fields.xml
new file mode 100644
index 0000000..31d0128
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/layout/editor_fields.xml
@@ -0,0 +1,127 @@
+<?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.
+ */
+-->
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="fill_parent"
+  android:layout_height="fill_parent"
+  android:stretchColumns="2">
+
+  <TableRow>
+    <TextView
+      android:layout_column="1"
+      android:textStyle="bold"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/label_name"
+      android:padding="3dip" />
+    <EditText
+      android:layout_column="2"
+      android:id="@+id/editor_name"
+      android:singleLine="true"
+      android:inputType="textPersonName"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:minWidth="250dip"
+      android:scrollHorizontally="true"
+      android:capitalize="none"
+      android:textSize="@dimen/contact_name_text_size"
+      android:gravity="fill_horizontal"
+      android:autoText="false" />
+  </TableRow>
+  <TableRow>
+    <TextView
+      android:layout_column="1"
+      android:textStyle="bold"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/label_phone_home"
+      android:padding="3dip" />
+    <EditText
+      android:id="@+id/editor_phone_home"
+      android:singleLine="true"
+      android:inputType="phone"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:minWidth="250dip"
+      android:scrollHorizontally="true"
+      android:capitalize="none"
+      android:gravity="fill_horizontal"
+      android:autoText="false" />
+  </TableRow>
+  <TableRow>
+    <TextView
+      android:layout_column="1"
+      android:textStyle="bold"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/label_phone_mobile"
+      android:padding="3dip" />
+    <EditText
+      android:id="@+id/editor_phone_mobile"
+      android:singleLine="true"
+      android:inputType="phone"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:minWidth="250dip"
+      android:scrollHorizontally="true"
+      android:capitalize="none"
+      android:gravity="fill_horizontal"
+      android:autoText="false" />
+  </TableRow>
+  <TableRow>
+    <TextView
+      android:layout_column="1"
+      android:textStyle="bold"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/label_phone_work"
+      android:padding="3dip" />
+    <EditText
+      android:id="@+id/editor_phone_work"
+      android:singleLine="true"
+      android:phoneNumber="true"
+      android:autoText="true"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:minWidth="250dip"
+      android:scrollHorizontally="true"
+      android:capitalize="none"
+      android:gravity="fill_horizontal" />
+  </TableRow>
+  <TableRow>
+    <TextView
+      android:layout_column="1"
+      android:textStyle="bold"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/label_email"
+      android:padding="3dip" />
+    <EditText
+      android:id="@+id/editor_email"
+      android:singleLine="true"
+      android:inputType="textEmailAddress"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:minWidth="250dip"
+      android:scrollHorizontally="true"
+      android:capitalize="none"
+      android:gravity="fill_horizontal"
+      android:autoText="false" />
+  </TableRow>
+</TableLayout>
diff --git a/samples/SampleSyncAdapter/res/menu/edit.xml b/samples/SampleSyncAdapter/res/menu/edit.xml
new file mode 100644
index 0000000..1227584
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/menu/edit.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/menu_done"
+        android:alphabeticShortcut="\n"
+        android:icon="@drawable/done_menu_icon"
+        android:title="@string/menu_done"
+        android:showAsAction="always|withText" />
+
+    <item
+        android:id="@+id/menu_cancel"
+        android:alphabeticShortcut="q"
+        android:title="@string/menu_cancel"
+        android:showAsAction="always|withText" />
+</menu>
diff --git a/samples/SampleSyncAdapter/res/values-xlarge/dimens.xml b/samples/SampleSyncAdapter/res/values-xlarge/dimens.xml
new file mode 100644
index 0000000..f9d74a7
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/values-xlarge/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2011, 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.
+ */
+-->
+<resources>
+
+    <!-- Font size used for the contact name in the editor -->
+    <dimen name="contact_name_text_size">26sp</dimen>
+
+</resources>
diff --git a/samples/SampleSyncAdapter/res/values/dimens.xml b/samples/SampleSyncAdapter/res/values/dimens.xml
new file mode 100644
index 0000000..7518c07
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2011, 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.
+ */
+-->
+
+<resources>
+
+    <!-- Font size used for the contact name in the editor -->
+    <dimen name="contact_name_text_size">18sp</dimen>
+
+</resources>
diff --git a/samples/SampleSyncAdapter/res/values/strings.xml b/samples/SampleSyncAdapter/res/values/strings.xml
index 8139d65..3b5ac58 100644
--- a/samples/SampleSyncAdapter/res/values/strings.xml
+++ b/samples/SampleSyncAdapter/res/values/strings.xml
@@ -20,7 +20,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Label for this package -->
     <string
-        name="label">SamplesyncAdapter</string>
+        name="label">Sample SyncAdapter</string>
 
     <!-- Permission label -->
     <string
@@ -90,4 +90,16 @@
         name="profile_action">Sample profile</string>
     <string
         name="view_profile">View Profile</string>
+
+    <string name="header_account_type">SampleSync contact</string>
+
+    <string name="label_name">Name</string>
+    <string name="label_phone_home">Home Phone</string>
+    <string name="label_phone_mobile">Mobile Phone</string>
+    <string name="label_phone_work">Work Phone</string>
+    <string name="label_email">Email</string>
+    <string
+        name="menu_done">Done</string>
+    <string
+        name="menu_cancel">Cancel</string>
 </resources>
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/res/values/styles.xml b/samples/SampleSyncAdapter/res/values/styles.xml
new file mode 100644
index 0000000..074613e
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/values/styles.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+    <!--
+        These styles will only be used in Honeycomb and later because
+        Android doesn't support third-party contact editing in pre-
+        Honeycomb versions.
+    -->
+    <color name="EditPanelBackgroundColor">#ffffff</color>
+    <color name="EditPanelBorderColor">#cccccc</color>
+    <style name="ContactEditTheme" parent="android:Theme.Holo.Light">
+    </style>
+</resources>
diff --git a/samples/SampleSyncAdapter/res/xml-v11/contacts.xml b/samples/SampleSyncAdapter/res/xml-v11/contacts.xml
new file mode 100644
index 0000000..62dfa80
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/xml-v11/contacts.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2011, 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.
+ */
+-->
+
+<ContactsAccountType
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    editContactActivity="com.example.android.samplesync.editor.ContactEditorActivity"
+    createContactActivity="com.example.android.samplesync.editor.ContactEditorActivity"
+>
+
+    <ContactsDataKind
+        android:mimeType="vnd.android.cursor.item/vnd.samplesyncadapter.profile"
+        android:icon="@drawable/icon"
+        android:summaryColumn="data2"
+        android:detailColumn="data3"
+        android:detailSocialSummary="true" />
+
+</ContactsAccountType>
diff --git a/samples/SampleSyncAdapter/res/xml-v11/syncadapter.xml b/samples/SampleSyncAdapter/res/xml-v11/syncadapter.xml
new file mode 100644
index 0000000..dac8ade
--- /dev/null
+++ b/samples/SampleSyncAdapter/res/xml-v11/syncadapter.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2011, 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.
+ */
+-->
+
+<!--
+  The attributes in this XML file provide configuration information
+  for the SampleSyncAdapter.
+
+  See xml/syncadapter.xml for greater details, but this version of
+  the file specifies that uploading (and thus editing) is supported.
+-->
+
+<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+    android:contentAuthority="com.android.contacts"
+    android:accountType="com.example.android.samplesync"
+    android:supportsUploading="true"
+    android:userVisible="true"
+/>
diff --git a/samples/SampleSyncAdapter/res/xml/contacts.xml b/samples/SampleSyncAdapter/res/xml/contacts.xml
index 1ff9c05..b46257d 100644
--- a/samples/SampleSyncAdapter/res/xml/contacts.xml
+++ b/samples/SampleSyncAdapter/res/xml/contacts.xml
@@ -17,7 +17,11 @@
  */
 -->
 
-<ContactsSource xmlns:android="http://schemas.android.com/apk/res/android">
+<ContactsSource
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    editContactActivity="com.example.android.samplesync.editor.ContactEditorActivity"
+    createContactActivity="com.example.android.samplesync.editor.ContactEditorActivity"
+>
 
     <ContactsDataKind
         android:mimeType="vnd.android.cursor.item/vnd.samplesyncadapter.profile"
diff --git a/samples/SampleSyncAdapter/res/xml/syncadapter.xml b/samples/SampleSyncAdapter/res/xml/syncadapter.xml
index 1f75947..3053a24 100644
--- a/samples/SampleSyncAdapter/res/xml/syncadapter.xml
+++ b/samples/SampleSyncAdapter/res/xml/syncadapter.xml
@@ -17,11 +17,21 @@
  */
 -->
 
-<!-- The attributes in this XML file provide configuration information -->
-<!-- for the SyncAdapter. -->
+<!--
+  The attributes in this XML file provide configuration information
+  for the SampleSyncAdapter.
+
+  We have two versions of this file - one here, and one in the
+  xml-v11 directory (Honeycomb and beyond). This one specifies that
+  the syncadapter does not support uploading (and thus the contacts
+  associated with this syncadapter are not editable).  The SDK 11
+  version of the file specifies that the adapter DOES support
+  uploading, so the contacts on SDK 11 and greater are editable.
+-->
 
 <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
     android:contentAuthority="com.android.contacts"
     android:accountType="com.example.android.samplesync"
     android:supportsUploading="false"
+    android:userVisible="true"
 />
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/app.yaml b/samples/SampleSyncAdapter/samplesyncadapter_server/app.yaml
index 109eff3..8526177 100644
--- a/samples/SampleSyncAdapter/samplesyncadapter_server/app.yaml
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/app.yaml
@@ -1,44 +1,59 @@
-application: samplesyncadapter
+# 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.
+
+application: samplesyncadapter2
 version: 1
 runtime: python
 api_version: 1
 
 handlers:
+
+#
+# Define a handler for our static files (css, images, etc)
+#
+- url: /static
+  static_dir: static
+
+#
+# Route all "web services" requests to the main.py file
+#
 - url: /auth
-  script: main.py
+  script: web_services.py
 
-- url: /login
-  script: main.py
+- url: /sync
+  script: web_services.py
 
-- url: /fetch_friend_updates
-  script: main.py
-
-- url: /fetch_status
-  script: main.py
-
-- url: /add_user
+- url: /reset_database
+  script: web_services.py
+  
+#
+# Route all page requests to the dashboard.py file
+#
+- url: /
   script: dashboard.py
 
-- url: /edit_user
+- url: /add_contact
   script: dashboard.py
 
-- url: /users
+- url: /edit_contact
   script: dashboard.py
 
-- url: /delete_friend
+- url: /delete_contact
   script: dashboard.py
 
-- url: /edit_user
+- url: /avatar
   script: dashboard.py
 
-- url: /add_credentials
-  script: dashboard.py
-
-- url: /user_credentials
-  script: dashboard.py
-
-- url: /add_friend
-  script: dashboard.py
-
-- url: /user_friends
+- url: /edit_avatar
   script: dashboard.py
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/cron.yaml b/samples/SampleSyncAdapter/samplesyncadapter_server/cron.yaml
new file mode 100644
index 0000000..1a0badb
--- /dev/null
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/cron.yaml
@@ -0,0 +1,24 @@
+# Copyright (C) 2011 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.
+
+cron:
+#
+# Create a weekly cron job that cleans up the SampleSyncAdapter server database.
+# We remove all existing contacts from the db, and create three initial
+# contacts: Romeo, Juliet, and Tybalt.
+#
+- description: weekly cleanup job
+  url: /reset_database
+  schedule: every sunday 00:00
+  timezone: America/Los_Angeles
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py b/samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py
index c986f7e..b9bac5e 100644
--- a/samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py
@@ -1,21 +1,23 @@
 #!/usr/bin/python2.5
 
 # 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.
 
-"""Defines Django forms for inserting/updating/viewing data
-   to/from SampleSyncAdapter datastore."""
+"""
+Defines Django forms for inserting/updating/viewing contact data
+to/from SampleSyncAdapter datastore.
+"""
 
 import cgi
 import datetime
@@ -26,248 +28,181 @@
 from google.appengine.ext.webapp import template
 from google.appengine.ext.db import djangoforms
 from model import datastore
+from google.appengine.api import images
 
 import wsgiref.handlers
 
+class BaseRequestHandler(webapp.RequestHandler):
+    """
+    Base class for our page-based request handlers that contains
+    some helper functions we use in most pages.
+    """
 
-class UserForm(djangoforms.ModelForm):
-  """Represents django form for entering user info."""
+    """
+    Return a form (potentially partially filled-in) to
+    the user.
+    """
+    def send_form(self, title, action, contactId, handle, content_obj):
+        if (contactId >= 0):
+            idInfo = '<input type="hidden" name="_id" value="%s">'
+        else:
+            idInfo = ''
 
-  class Meta:
-    model = datastore.User
+        template_values = {
+                'title': title,
+                'header': title,
+                'action': action,
+                'contactId': contactId,
+                'handle': handle,
+                'has_contactId': (contactId >= 0),
+                'has_handle': (handle != None),
+                'form_data_rows': str(content_obj)
+                }
+
+        path = os.path.join(os.path.dirname(__file__), 'templates', 'simple_form.html')
+        self.response.out.write(template.render(path, template_values))
+
+class ContactForm(djangoforms.ModelForm):
+    """Represents django form for entering contact info."""
+
+    class Meta:
+        model = datastore.Contact
 
 
-class UserInsertPage(webapp.RequestHandler):
-  """Inserts new users. GET presents a blank form. POST processes it."""
+class ContactInsertPage(BaseRequestHandler):
+    """
+    Processes requests to add a new contact. GET presents an empty
+    contact form for the user to fill in.  POST saves the new contact
+    with the POSTed information.
+    """
 
-  def get(self):
-    self.response.out.write('<html><body>'
-                            '<form method="POST" '
-                            'action="/add_user">'
-                            '<table>')
-    # This generates our shopping list form and writes it in the response
-    self.response.out.write(UserForm())
-    self.response.out.write('</table>'
-                            '<input type="submit">'
-                            '</form></body></html>')
+    def get(self):
+        self.send_form('Add Contact', '/add_contact', -1, None, ContactForm())
 
-  def post(self):
-    data = UserForm(data=self.request.POST)
-    if data.is_valid():
-      # Save the data, and redirect to the view page
-      entity = data.save(commit=False)
-      entity.put()
-      self.redirect('/users')
-    else:
-      # Reprint the form
-      self.response.out.write('<html><body>'
-                              '<form method="POST" '
-                              'action="/">'
-                              '<table>')
-      self.response.out.write(data)
-      self.response.out.write('</table>'
-                              '<input type="submit">'
-                              '</form></body></html>')
+    def post(self):
+        data = ContactForm(data=self.request.POST)
+        if data.is_valid():
+            # Save the data, and redirect to the view page
+            entity = data.save(commit=False)
+            entity.put()
+            self.redirect('/')
+        else:
+            # Reprint the form
+            self.send_form('Add Contact', '/add_contact', -1, None, data)
 
 
-class UserEditPage(webapp.RequestHandler):
-  """Edits users. GET presents a form prefilled with user info
-     from datastore. POST processes it."""
+class ContactEditPage(BaseRequestHandler):
+    """
+    Process requests to edit a contact's information.  GET presents a form
+    with the current contact information filled in. POST saves new information
+    into the contact record.
+    """
 
-  def get(self):
-    id = int(self.request.get('user'))
-    user = datastore.User.get(db.Key.from_path('User', id))
-    self.response.out.write('<html><body>'
-                            '<form method="POST" '
-                            'action="/edit_user">'
-                            '<table>')
-    # This generates our shopping list form and writes it in the response
-    self.response.out.write(UserForm(instance=user))
-    self.response.out.write('</table>'
-                            '<input type="hidden" name="_id" value="%s">'
-                            '<input type="submit">'
-                            '</form></body></html>' % id)
+    def get(self):
+        id = int(self.request.get('id'))
+        contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+        self.send_form('Edit Contact', '/edit_contact', id, contact.handle, 
+                       ContactForm(instance=contact))
 
-  def post(self):
-    id = int(self.request.get('_id'))
-    user = datastore.User.get(db.Key.from_path('User', id))
-    data = UserForm(data=self.request.POST, instance=user)
-    if data.is_valid():
-      # Save the data, and redirect to the view page
-      entity = data.save(commit=False)
-      entity.updated = datetime.datetime.utcnow()
-      entity.put()
-      self.redirect('/users')
-    else:
-      # Reprint the form
-      self.response.out.write('<html><body>'
-                              '<form method="POST" '
-                              'action="/edit_user">'
-                              '<table>')
-      self.response.out.write(data)
-      self.response.out.write('</table>'
-                              '<input type="hidden" name="_id" value="%s">'
-                              '<input type="submit">'
-                              '</form></body></html>' % id)
+    def post(self):
+        id = int(self.request.get('id'))
+        contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+        data = ContactForm(data=self.request.POST, instance=contact)
+        if data.is_valid():
+            # Save the data, and redirect to the view page
+            entity = data.save(commit=False)
+            entity.updated = datetime.datetime.utcnow()
+            entity.put()
+            self.redirect('/')
+        else:
+            # Reprint the form
+            self.send_form('Edit Contact', '/edit_contact', id, contact.handle, data)
 
+class ContactDeletePage(BaseRequestHandler):
+    """Processes delete contact request."""
 
-class UsersListPage(webapp.RequestHandler):
-  """Lists all Users. In addition displays links for editing user info,
-     viewing user's friends and adding new users."""
+    def get(self):
+        id = int(self.request.get('id'))
+        contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+        contact.deleted = True
+        contact.updated = datetime.datetime.utcnow()
+        contact.put()
 
-  def get(self):
-    users = datastore.User.all()
-    template_values = {
-        'users': users
-        }
+        self.redirect('/')
 
-    path = os.path.join(os.path.dirname(__file__), 'templates', 'users.html')
-    self.response.out.write(template.render(path, template_values))
+class AvatarEditPage(webapp.RequestHandler):
+    """
+    Processes requests to edit contact's avatar. GET is used to fetch
+    a page that displays the contact's current avatar and allows the user 
+    to specify a file containing a new avatar image.  POST is used to
+    submit the form which will change the contact's avatar.
+    """
 
+    def get(self):
+        id = int(self.request.get('id'))
+        contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+        template_values = {
+                'avatar': contact.avatar,
+                'contactId': id
+                }
+        
+        path = os.path.join(os.path.dirname(__file__), 'templates', 'edit_avatar.html')
+        self.response.out.write(template.render(path, template_values))
 
-class UserCredentialsForm(djangoforms.ModelForm):
-  """Represents django form for entering user's credentials."""
+    def post(self):
+        id = int(self.request.get('id'))
+        contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+        #avatar = images.resize(self.request.get("avatar"), 128, 128)
+        avatar = self.request.get("avatar")
+        contact.avatar = db.Blob(avatar)
+        contact.updated = datetime.datetime.utcnow()
+        contact.put()
+        self.redirect('/')
 
-  class Meta:
-    model = datastore.UserCredentials
+class AvatarViewPage(BaseRequestHandler):
+    """
+    Processes request to view contact's avatar. This is different from
+    the GET AvatarEditPage request in that this doesn't return a page -
+    it just returns the raw image itself.
+    """
 
+    def get(self):
+        id = int(self.request.get('id'))
+        contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+        if (contact.avatar):
+            self.response.headers['Content-Type'] = "image/png"
+            self.response.out.write(contact.avatar)
+        else:
+            self.redirect(self.request.host_url + '/static/img/default_avatar.gif')
 
-class UserCredentialsInsertPage(webapp.RequestHandler):
-  """Inserts user credentials. GET shows a blank form, POST processes it."""
+class ContactsListPage(webapp.RequestHandler):
+    """
+    Display a page that lists all the contacts associated with
+    the specifies user account.
+    """
 
-  def get(self):
-    self.response.out.write('<html><body>'
-                            '<form method="POST" '
-                            'action="/add_credentials">'
-                            '<table>')
-    # This generates our shopping list form and writes it in the response
-    self.response.out.write(UserCredentialsForm())
-    self.response.out.write('</table>'
-                            '<input type="submit">'
-                            '</form></body></html>')
+    def get(self):
+        contacts = datastore.Contact.all()
+        template_values = {
+                'contacts': contacts,
+                'username': 'user'
+                }
 
-  def post(self):
-    data = UserCredentialsForm(data=self.request.POST)
-    if data.is_valid():
-      # Save the data, and redirect to the view page
-      entity = data.save(commit=False)
-      entity.put()
-      self.redirect('/users')
-    else:
-      # Reprint the form
-      self.response.out.write('<html><body>'
-                              '<form method="POST" '
-                              'action="/add_credentials">'
-                              '<table>')
-      self.response.out.write(data)
-      self.response.out.write('</table>'
-                              '<input type="submit">'
-                              '</form></body></html>')
-
-
-class UserFriendsForm(djangoforms.ModelForm):
-  """Represents django form for entering user's friends."""
-
-  class Meta:
-    model = datastore.UserFriends
-    exclude = ['deleted', 'username']
-
-
-class UserFriendsInsertPage(webapp.RequestHandler):
-  """Inserts user's new friends. GET shows a blank form, POST processes it."""
-
-  def get(self):
-    user = self.request.get('user')
-    self.response.out.write('<html><body>'
-                            '<form method="POST" '
-                            'action="/add_friend">'
-                            '<table>')
-    # This generates our shopping list form and writes it in the response
-    self.response.out.write(UserFriendsForm())
-    self.response.out.write('</table>'
-                            '<input type = hidden name = "user" value = "%s">'
-                            '<input type="submit">'
-                            '</form></body></html>' % user)
-
-  def post(self):
-    data = UserFriendsForm(data=self.request.POST)
-    if data.is_valid():
-      user = self.request.get('user')
-      # Save the data, and redirect to the view page
-      entity = data.save(commit=False)
-      entity.username = user
-      query = datastore.UserFriends.all()
-      query.filter('username = ', user)
-      query.filter('friend_handle = ', entity.friend_handle)
-      result = query.get()
-      if result:
-	result.deleted = False
-	result.updated = datetime.datetime.utcnow()
-	result.put()
-      else:
-        entity.deleted = False
-        entity.put()
-      self.redirect('/user_friends?user=' + user)
-    else:
-      # Reprint the form
-      self.response.out.write('<html><body>'
-                              '<form method="POST" '
-                              'action="/add_friend">'
-                              '<table>')
-      self.response.out.write(data)
-      self.response.out.write('</table>'
-                              '<input type="submit">'
-                              '</form></body></html>')
-
-
-class UserFriendsListPage(webapp.RequestHandler):
-  """Lists all friends for a user. In addition displays links for removing
-     friends and adding new friends."""
-
-  def get(self):
-    user = self.request.get('user')
-    query = datastore.UserFriends.all()
-    query.filter('deleted = ', False)
-    query.filter('username = ', user)
-    friends = query.fetch(50)
-    template_values = {
-        'friends': friends,
-        'user': user
-        }
-    path = os.path.join(os.path.dirname(__file__),
-                        'templates', 'view_friends.html')
-    self.response.out.write(template.render(path, template_values))
-
-
-class DeleteFriendPage(webapp.RequestHandler):
-  """Processes delete friend request."""
-
-  def get(self):
-    user = self.request.get('user')
-    friend = self.request.get('friend')
-    query = datastore.UserFriends.all()
-    query.filter('username =', user)
-    query.filter('friend_handle =', friend)
-    result = query.get()
-    result.deleted = True
-    result.updated = datetime.datetime.utcnow()
-    result.put()
-
-    self.redirect('/user_friends?user=' + user)
+        path = os.path.join(os.path.dirname(__file__), 'templates', 'contacts.html')
+        self.response.out.write(template.render(path, template_values))
 
 
 def main():
-  application = webapp.WSGIApplication(
-      [('/add_user', UserInsertPage),
-       ('/users', UsersListPage),
-       ('/add_credentials', UserCredentialsInsertPage),
-       ('/add_friend', UserFriendsInsertPage),
-       ('/user_friends', UserFriendsListPage),
-       ('/delete_friend', DeleteFriendPage),
-       ('/edit_user', UserEditPage)
-      ],
-      debug=True)
-  wsgiref.handlers.CGIHandler().run(application)
+    application = webapp.WSGIApplication(
+        [('/', ContactsListPage),
+         ('/add_contact', ContactInsertPage),
+         ('/edit_contact', ContactEditPage),
+         ('/delete_contact', ContactDeletePage),
+         ('/avatar', AvatarViewPage),
+         ('/edit_avatar', AvatarEditPage)
+        ],
+        debug=True)
+    wsgiref.handlers.CGIHandler().run(application)
 
 if __name__ == '__main__':
   main()
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/index.yaml b/samples/SampleSyncAdapter/samplesyncadapter_server/index.yaml
index 83ceea0..6f02db5 100644
--- a/samples/SampleSyncAdapter/samplesyncadapter_server/index.yaml
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/index.yaml
@@ -1,14 +1,15 @@
-indexes:
+# 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.
 
-# This index.yaml is automatically updated whenever the dev_appserver
-# detects that a new type of query is run.  If you want to manage the
-# index.yaml file manually, remove the above marker line (the line
-# saying "# AUTOGENERATED").  If you want to manage some indexes
-# manually, move them above the marker line.  The index.yaml file is
-# automatically uploaded to the admin console when you next deploy
-# your application using appcfg.py.
-
-- kind: UserFriends
-  properties:
-  - name: username
-  - name: updated
\ No newline at end of file
+# AUTOGENERATED
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/main.py b/samples/SampleSyncAdapter/samplesyncadapter_server/main.py
deleted file mode 100644
index 2d7c5c7..0000000
--- a/samples/SampleSyncAdapter/samplesyncadapter_server/main.py
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/python2.5
-
-# 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.
-
-"""Handlers for Sample SyncAdapter services.
-
-Contains several RequestHandler subclasses used to handle post operations.
-This script is designed to be run directly as a WSGI application.
-
-  Authenticate: Handles user requests for authentication.
-  FetchFriends: Handles user requests for friend list.
-  FriendData: Stores information about user's friends.
-"""
-
-import cgi
-from datetime import datetime
-from django.utils import simplejson
-from google.appengine.api import users
-from google.appengine.ext import db
-from google.appengine.ext import webapp
-from model import datastore
-import wsgiref.handlers
-
-
-class Authenticate(webapp.RequestHandler):
-  """Handles requests for login and authentication.
-
-  UpdateHandler only accepts post events. It expects each
-  request to include username and password fields. It returns authtoken
-  after successful authentication and "invalid credentials" error otherwise.
-  """
-
-  def post(self):
-    self.username = self.request.get('username')
-    self.password = self.request.get('password')
-    password = datastore.UserCredentials.get(self.username)
-    if password == self.password:
-      self.response.set_status(200, 'OK')
-      # return the password as AuthToken
-      self.response.out.write(password)
-    else:
-      self.response.set_status(401, 'Invalid Credentials')
-
-
-class FetchFriends(webapp.RequestHandler):
-  """Handles requests for fetching user's friendlist.
-
-  UpdateHandler only accepts post events. It expects each
-  request to include username and authtoken. If the authtoken is valid
-  it returns user's friend info in JSON format.It uses helper
-  class FriendData to fetch user's friendlist.
-  """
-
-  def post(self):
-    self.username = self.request.get('username')
-    self.password = self.request.get('password')
-    self.timestamp = None
-    timestamp = self.request.get('timestamp')
-    if timestamp:
-      self.timestamp = datetime.strptime(timestamp, '%Y/%m/%d %H:%M')
-    password = datastore.UserCredentials.get(self.username)
-    if password == self.password:
-      self.friend_list = []
-      friends = datastore.UserFriends.get_friends(self.username)
-      if friends:
-        for friend in friends:
-          friend_handle = getattr(friend, 'friend_handle')
-
-          if self.timestamp is None or getattr(friend, 'updated') > self.timestamp:
-            if (getattr(friend, 'deleted')) == True:
-              friend = {}
-              friend['u'] = friend_handle
-              friend['d'] = 'true'
-              friend['i'] = str(datastore.User.get_user_id(friend_handle))
-              self.friend_list.append(friend)
-            else:
-              FriendsData(self.friend_list, friend_handle)
-          else:
-            if datastore.User.get_user_last_updated(friend_handle) > self.timestamp:
-              FriendsData(self.friend_list, friend_handle)
-      self.response.set_status(200)
-      self.response.out.write(toJSON(self.friend_list))
-    else:
-      self.response.set_status(401, 'Invalid Credentials')
-
-class FetchStatus(webapp.RequestHandler):
-  """Handles requests fetching friend statuses.
-
-  UpdateHandler only accepts post events. It expects each
-  request to include username and authtoken. If the authtoken is valid
-  it returns status info in JSON format.
-  """
-
-  def post(self):
-    self.username = self.request.get('username')
-    self.password = self.request.get('password')
-    password = datastore.UserCredentials.get(self.username)
-    if password == self.password:
-      self.status_list = []
-      friends = datastore.UserFriends.get_friends(self.username)
-      if friends:
-        for friend in friends:
-          friend_handle = getattr(friend, 'friend_handle')
-          status_text = datastore.User.get_user_status(friend_handle)
-	  user_id = datastore.User.get_user_id(friend_handle)
-          status = {}
-          status['i'] = str(user_id)
-          status['s'] = status_text
-          self.status_list.append(status)
-      self.response.set_status(200)
-      self.response.out.write(toJSON(self.status_list))
-    else:
-      self.response.set_status(401, 'Invalid Credentials')
-
-  def toJSON(self):
-    """Dumps the data represented by the object to JSON for wire transfer."""
-    return simplejson.dumps(self.friend_list)
-
-
-def toJSON(object):
-  """Dumps the data represented by the object to JSON for wire transfer."""
-  return simplejson.dumps(object)
-
-class FriendsData(object):
-  """Holds data for user's friends.
-
-  This class knows how to serialize itself to JSON.
-  """
-  __FIELD_MAP = {
-      'handle': 'u',
-      'firstname': 'f',
-      'lastname': 'l',
-      'status': 's',
-      'phone_home': 'h',
-      'phone_office': 'o',
-      'phone_mobile': 'm',
-      'email': 'e',
-  }
-
-  def __init__(self, friend_list, username):
-    obj = datastore.User.get_user_info(username)
-    friend = {}
-    for obj_name, json_name in self.__FIELD_MAP.items():
-      if hasattr(obj, obj_name):
-        friend[json_name] = str(getattr(obj, obj_name))
-        friend['i'] = str(obj.key().id())
-    friend_list.append(friend)
-
-
-def main():
-  application = webapp.WSGIApplication(
-      [('/auth', Authenticate),
-       ('/login', Authenticate),
-       ('/fetch_friend_updates', FetchFriends),
-       ('/fetch_status', FetchStatus),
-      ],
-      debug=True)
-  wsgiref.handlers.CGIHandler().run(application)
-
-if __name__ == "__main__":
-  main()
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/model/datastore.py b/samples/SampleSyncAdapter/samplesyncadapter_server/model/datastore.py
index 71bd18a..1f91633 100644
--- a/samples/SampleSyncAdapter/samplesyncadapter_server/model/datastore.py
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/model/datastore.py
@@ -14,80 +14,51 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-"""Represents user's contact information, friends and credentials."""
+"""Represents user's contact information"""
 
 from google.appengine.ext import db
 
 
-class User(db.Model):
+class Contact(db.Model):
   """Data model class to hold user objects."""
 
   handle = db.StringProperty(required=True)
-  firstname = db.TextProperty()
-  lastname = db.TextProperty()
-  status = db.TextProperty()
+  firstname = db.StringProperty()
+  lastname = db.StringProperty()
   phone_home = db.PhoneNumberProperty()
   phone_office = db.PhoneNumberProperty()
   phone_mobile = db.PhoneNumberProperty()
   email = db.EmailProperty()
+  status = db.TextProperty()
+  avatar = db.BlobProperty()
   deleted = db.BooleanProperty()
   updated = db.DateTimeProperty(auto_now_add=True)
 
   @classmethod
-  def get_user_info(cls, username):
+  def get_contact_info(cls, username):
     if username not in (None, ''):
       query = cls.gql('WHERE handle = :1', username)
       return query.get()
     return None
 
   @classmethod
-  def get_user_last_updated(cls, username):
+  def get_contact_last_updated(cls, username):
     if username not in (None, ''):
       query = cls.gql('WHERE handle = :1', username)
       return query.get().updated
     return None
 
   @classmethod
-  def get_user_id(cls, username):
+  def get_contact_id(cls, username):
     if username not in (None, ''):
       query = cls.gql('WHERE handle = :1', username)
       return query.get().key().id()
     return None
 
   @classmethod
-  def get_user_status(cls, username):
+  def get_contact_status(cls, username):
     if username not in (None, ''):
       query = cls.gql('WHERE handle = :1', username)
       return query.get().status
     return None
 
-
-class UserCredentials(db.Model):
-  """Data model class to hold credentials for a Voiper user."""
-
-  username = db.StringProperty(required=True)
-  password = db.StringProperty()
-
-  @classmethod
-  def get(cls, username):
-    if username not in (None, ''):
-      query = cls.gql('WHERE username = :1', username)
-      return query.get().password
-    return None
-
-
-class UserFriends(db.Model):
-  """Data model class to hold user's friendlist info."""
-
-  username = db.StringProperty()
-  friend_handle = db.StringProperty(required=True)
-  updated = db.DateTimeProperty(auto_now_add=True)
-  deleted = db.BooleanProperty()
-
-  @classmethod
-  def get_friends(cls, username):
-    if username not in (None, ''):
-      query = cls.gql('WHERE username = :1', username)
-      friends = query.fetch(50)
-      return friends
-    return None
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/static/css/main.css b/samples/SampleSyncAdapter/samplesyncadapter_server/static/css/main.css
new file mode 100644
index 0000000..eab304d
--- /dev/null
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/static/css/main.css
@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 2011 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.
+*/
+
+html,body {
+    height: 100%;
+}
+
+body {
+    padding: 20px;
+    margin: 0;
+    background-color: #fff;
+    font-family: Verdana, Arial, Helvetica, sans-serif;
+    text-align: left;
+}
+
+a {
+    color: #0033cc;
+}
+
+h1 {
+    font-family: Arial;
+    font-weight: normal;
+    border-bottom: solid 1px #ccc;
+    margin: 0 0 20px 0;
+    padding: 0 0 5px 0;
+}
+
+h3 {
+    font-family: Arial;
+    font-weight: bold;
+    line-height: 2em;
+}
+
+table {
+    border-collapse: collapse;
+    margin-bottom: 20px;
+}
+
+th,td {
+    padding: 5px 8px;
+    text-align: left;
+}
+
+td.center {
+    text-align: center;
+}
+
+.deleted td {
+    text-decoration: line-through;
+}
+
+.data th {
+    font-weight: normal;
+    border-bottom: solid 1px #000;
+}
+
+.data td {
+    border-bottom: solid 1px #eee;
+}
+
+.form th {
+    font-weight: normal;
+    text-align: right;
+}
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/static/img/default_avatar.gif b/samples/SampleSyncAdapter/samplesyncadapter_server/static/img/default_avatar.gif
new file mode 100644
index 0000000..5089e95
--- /dev/null
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/static/img/default_avatar.gif
Binary files differ
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/templates/contacts.html b/samples/SampleSyncAdapter/samplesyncadapter_server/templates/contacts.html
new file mode 100644
index 0000000..b668d33
--- /dev/null
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/templates/contacts.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!--
+ * 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.
+-->
+    <head>
+        <title>SampleSync: Contacts for '{{ username }}'</title>
+        <link type="text/css" rel="stylesheet" href="/static/css/main.css" media="screen" />
+    </head>
+    <body>
+        <h1>SampleSync: Contacts for '{{ username }}'</h1>
+        <table class="data" cellpadding="0" cellspacing="0">
+        <tr>
+            <th>&nbsp;</th>
+            <th>Id</th>
+            <th>Name</th>
+            <th>Email</th>
+            <th>Home</th>
+            <th>Office</th>
+            <th>Mobile</th>
+            <th>Status</th>
+        </tr>
+        {% for contact in contacts %}
+        <tr {% if contact.deleted %} class="deleted" {% endif %}>
+            <td class="center">
+            <a href="/edit_avatar?id={{ contact.key.id }}"><img src="/avatar?id={{ contact.key.id }}" height="25" width="25" /></a>
+            </td>
+            <td><a href="/edit_contact?id={{ contact.key.id }}">{{ contact.key.id }}</a></td>
+            <td><a href="/edit_contact?id={{ contact.key.id }}">{{ contact.firstname }} {{ contact.lastname }}</a></td>
+            <td>{{ contact.email }}</td>
+            <td>{{ contact.phone_home }}</td>
+            <td>{{ contact.phone_office }}</td>
+            <td>{{ contact.phone_mobile }}</td>
+            <td><span style="whitespace: no-wrap;">{{ contact.status }}</span></td>
+        </tr>
+        {% endfor %}
+        </table>
+
+        <a href = "/add_contact">Add Contact</a>
+    </body>
+</html>
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/templates/edit_avatar.html b/samples/SampleSyncAdapter/samplesyncadapter_server/templates/edit_avatar.html
new file mode 100644
index 0000000..49cd3d3
--- /dev/null
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/templates/edit_avatar.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!--
+ * 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.
+-->
+    <head>
+        <title>SampleSync: Edit Picture</title>
+        <link type="text/css" rel="stylesheet" href="/static/css/main.css" media="screen" />
+    </head>
+    <body>
+        <h1>SampleSync: Edit Picture</h1>
+        <form method="POST" action="/edit_avatar" enctype="multipart/form-data">
+            <h3>Current Avatar:</h3>
+            <blockquote>
+                {% if avatar %}
+                <img src="/avatar?id={{ contactId }}" />
+                {% else %}
+                <i>You haven't added a picture for this friend...</i>
+                {% endif %}
+            </blockquote>
+            <h3>New Avatar:</h3>
+            <p>Please select a file containing the image you'd like to use for this friend</p>
+            <input type="file" name="avatar" />
+            <p>&nbsp;</p>
+            <input type="submit" name="Save" value="Save Changes" />
+            <input type="button" name="Cancel" value="Cancel" onclick="document.location='/';return false;" />
+            <input type="hidden" name="id" value="{{ contactId }}" />
+        </form>
+    </body>
+</html>
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/templates/simple_form.html b/samples/SampleSyncAdapter/samplesyncadapter_server/templates/simple_form.html
new file mode 100644
index 0000000..1d72819
--- /dev/null
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/templates/simple_form.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!--
+ * 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.
+-->
+    <head>
+        <title>SampleSync: {{ title }}</title>
+        <link type="text/css" rel="stylesheet" href="/static/css/main.css" media="screen" />
+    </head>
+    <body>
+        <h1>SampleSync: {{ header }}</h1>
+        <form method="POST" action="{{ action }}">
+            <table class="form" cellpadding="0" cellspacing="0">
+              {{ form_data_rows }}
+            </table>
+            <input type="submit" name="Save" value="Save Changes" />
+            <input type="button" name="Cancel" value="Cancel" onclick="document.location='/';return false;" />
+            {% if has_contactId %}
+            <input type="hidden" name="id" value="{{ contactId }}" />
+            {% endif %}
+            {% if has_handle %}
+            <input type="hidden" name="username" value="{{ handle }}" />
+            {% endif %}
+        </form>
+    </body>
+</html>
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/templates/users.html b/samples/SampleSyncAdapter/samplesyncadapter_server/templates/users.html
deleted file mode 100644
index 044c352..0000000
--- a/samples/SampleSyncAdapter/samplesyncadapter_server/templates/users.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<html>
-<body>
-<h1> Sample Sync Adapter </h1>
-
-<p>
-<h3> List of Users </h3>
-<table>
-
-{% for user in users %}
-  <tr><td>
-  <a
-  href="/edit_user?user={{ user.key.id}}">{{ user.firstname }}&nbsp; {{ user.lastname }} </a>
-  </td><td>&nbsp;&nbsp;<a href="/user_friends?user={{ user.handle }}">Friends</a> </td>
-  </tr>
-{% endfor %}
-</table>
-</p>
-
-<a href = "/add_user"> Insert More </a>
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/templates/view_friends.html b/samples/SampleSyncAdapter/samplesyncadapter_server/templates/view_friends.html
deleted file mode 100644
index d4ef892..0000000
--- a/samples/SampleSyncAdapter/samplesyncadapter_server/templates/view_friends.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<html>
-<body>
-<h1> Sample Sync Adapter </h1>
-
-<p>
-
-{{user}}'s friends 
-<table>
-{% for friend in friends %}
-  <tr><td>
-  {{ friend.friend_handle }} </td><td> <a href="/delete_friend?user={{ user }}&friend={{friend.friend_handle}}">Remove</a>  
-  </td></tr>
-{% endfor %}
-</table>
-</p>
-
-<a href = "/add_friend?user={{user}}"> Add More </a>
\ No newline at end of file
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/web_services.py b/samples/SampleSyncAdapter/samplesyncadapter_server/web_services.py
new file mode 100644
index 0000000..3028add
--- /dev/null
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/web_services.py
@@ -0,0 +1,400 @@
+#!/usr/bin/python2.5
+
+# 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.
+
+"""
+Handlers for Sample SyncAdapter services.
+
+Contains several RequestHandler subclasses used to handle post operations.
+This script is designed to be run directly as a WSGI application.
+
+"""
+
+import cgi
+import logging
+import time as _time
+from datetime import datetime
+from django.utils import simplejson
+from google.appengine.api import users
+from google.appengine.ext import db
+from google.appengine.ext import webapp
+from model import datastore
+import wsgiref.handlers
+
+
+class BaseWebServiceHandler(webapp.RequestHandler):
+    """
+    Base class for our web services. We put some common helper
+    functions here.
+    """
+
+    """
+    Since we're only simulating a single user account, declare our
+    hard-coded credentials here, so that they're easy to see/find.
+    We actually accept any and all usernames that start with this
+    hard-coded values. So if ACCT_USER_NAME is 'user', then we'll
+    accept 'user', 'user75', 'userbuddy', etc, all as legal account
+    usernames.
+    """
+    ACCT_USER_NAME  = 'user'
+    ACCT_PASSWORD   = 'test'
+    ACCT_AUTH_TOKEN = 'xyzzy'
+
+    DATE_TIME_FORMAT = '%Y/%m/%d %H:%M'
+
+    """
+    Process a request to authenticate a client.  We assume that the username
+    and password will be included in the request. If successful, we'll return
+    an authtoken as the only content.  If auth fails, we'll send an "invalid
+    credentials" error.
+    We return a boolean indicating whether we were successful (true) or not (false).
+    In the event that this call fails, we will setup the response, so callers just
+    need to RETURN in the error case.
+    """
+    def authenticate(self):
+        self.username = self.request.get('username')
+        self.password = self.request.get('password')
+
+        logging.info('Authenticatng username: ' + self.username)
+
+        if ((self.username != None) and
+                (self.username.startswith(BaseWebServiceHandler.ACCT_USER_NAME)) and
+                (self.password == BaseWebServiceHandler.ACCT_PASSWORD)):
+            # Authentication was successful - return our hard-coded
+            # auth-token as the only response.
+            self.response.set_status(200, 'OK')
+            self.response.out.write(BaseWebServiceHandler.ACCT_AUTH_TOKEN)
+            return True
+        else:
+            # Authentication failed. Return the standard HTTP auth failure
+            # response to let the client know.
+            self.response.set_status(401, 'Invalid Credentials')
+            return False
+
+    """
+    Validate the credentials of the client for a web service request.
+    The request should include username/password parameters that correspond
+    to our hard-coded single account values.
+    We return a boolean indicating whether we were successful (true) or not (false).
+    In the event that this call fails, we will setup the response, so callers just
+    need to RETURN in the error case.
+    """
+    def validate(self):
+        self.username = self.request.get('username')
+        self.authtoken = self.request.get('authtoken')
+
+        logging.info('Validating username: ' + self.username)
+
+        if ((self.username != None) and
+                (self.username.startswith(BaseWebServiceHandler.ACCT_USER_NAME)) and
+                (self.authtoken == BaseWebServiceHandler.ACCT_AUTH_TOKEN)):
+            return True
+        else:
+            self.response.set_status(401, 'Invalid Credentials')
+            return False
+
+
+class Authenticate(BaseWebServiceHandler):
+    """
+    Handles requests for login and authentication.
+
+    UpdateHandler only accepts post events. It expects each
+    request to include username and password fields. It returns authtoken
+    after successful authentication and "invalid credentials" error otherwise.
+    """
+
+    def post(self):
+        self.authenticate()
+
+    def get(self):
+        """Used for debugging in a browser..."""
+        self.post()
+
+
+class SyncContacts(BaseWebServiceHandler):
+    """Handles requests for fetching user's contacts.
+
+    UpdateHandler only accepts post events. It expects each
+    request to include username and authtoken. If the authtoken is valid
+    it returns user's contact info in JSON format.
+    """
+
+    def get(self):
+        """Used for debugging in a browser..."""
+        self.post()
+
+    def post(self):
+        logging.info('*** Starting contact sync ***')
+        if (not self.validate()):
+            return
+
+        updated_contacts = []
+
+        # Process any client-side changes sent up in the request.
+        # Any new contacts that were added are included in the
+        # updated_contacts list, so that we return them to the
+        # client. That way, the client can see the serverId of
+        # the newly added contact.
+        client_buffer = self.request.get('contacts')
+        if ((client_buffer != None) and (client_buffer != '')):
+            self.process_client_changes(client_buffer, updated_contacts)
+
+        # Add any contacts that have been updated on the server-side
+        # since the last sync by this client.
+        client_state = self.request.get('syncstate')
+        self.get_updated_contacts(client_state, updated_contacts)
+
+        logging.info('Returning ' + str(len(updated_contacts)) + ' contact records')
+
+        # Return the list of updated contacts to the client
+        self.response.set_status(200)
+        self.response.out.write(toJSON(updated_contacts))
+
+    def get_updated_contacts(self, client_state, updated_contacts):
+        logging.info('* Processing server changes')
+        timestamp = None
+
+        base_url = self.request.host_url
+
+        # The client sends the last high-water-mark that they successfully
+        # sync'd to in the syncstate parameter.  It's opaque to them, but
+        # its actually a seconds-in-unix-epoch timestamp that we use
+        # as a baseline.
+        if client_state:
+            logging.info('Client sync state: ' + client_state)
+            timestamp = datetime.utcfromtimestamp(float(client_state))
+
+        # Keep track of the update/delete counts, so we can log it
+        # below.  Makes debugging easier...
+        update_count = 0
+        delete_count = 0
+
+        contacts = datastore.Contact.all()
+        if contacts:
+            # Find the high-water mark for the most recently updated friend.
+            # We'll return this as the syncstate (x) value for all the friends
+            # we return from this function.
+            high_water_date = datetime.min
+            for contact in contacts:
+                if (contact.updated > high_water_date):
+                    high_water_date = contact.updated
+            high_water_mark = str(long(_time.mktime(high_water_date.utctimetuple())) + 1)
+            logging.info('New sync state: ' + high_water_mark)
+
+            # Now build the updated_contacts containing all the friends that have been
+            # changed since the last sync
+            for contact in contacts:
+                # If our list of contacts we're returning already contains this
+                # contact (for example, it's a contact just uploaded from the client)
+                # then don't bother processing it any further...
+                if (self.list_contains_contact(updated_contacts, contact)):
+                    continue
+
+                handle = contact.handle
+
+                if timestamp is None or contact.updated > timestamp:
+                    if contact.deleted == True:
+                        delete_count = delete_count + 1
+                        DeletedContactData(updated_contacts, handle, high_water_mark)
+                    else:
+                        update_count = update_count + 1
+                        UpdatedContactData(updated_contacts, handle, None, base_url, high_water_mark)
+
+        logging.info('Server-side updates: ' + str(update_count))
+        logging.info('Server-side deletes: ' + str(delete_count))
+
+    def process_client_changes(self, contacts_buffer, updated_contacts):
+        logging.info('* Processing client changes: ' + self.username)
+
+        base_url = self.request.host_url
+
+        # Build an array of generic objects containing contact data,
+        # using the Django built-in JSON parser
+        logging.info('Uploaded contacts buffer: ' + contacts_buffer)
+        json_list = simplejson.loads(contacts_buffer)
+        logging.info('Client-side updates: ' + str(len(json_list)))
+
+        # Keep track of the number of new contacts the client sent to us,
+        # so that we can log it below.
+        new_contact_count = 0
+
+        for jcontact in json_list:
+            new_contact = False
+            id = self.safe_attr(jcontact, 'i')
+            if (id != None):
+                logging.info('Updating contact: ' + str(id))
+                contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+            else:
+                logging.info('Creating new contact record')
+                new_contact = True
+                contact = datastore.Contact(handle='temp')
+
+            # If the 'change' for this contact is that they were deleted
+            # on the client-side, all we want to do is set the deleted
+            # flag here, and we're done.
+            if (self.safe_attr(jcontact, 'd') == True):
+                contact.deleted = True
+                contact.put()
+                logging.info('Deleted contact: ' + contact.handle)
+                continue
+
+            contact.firstname = self.safe_attr(jcontact, 'f')
+            contact.lastname = self.safe_attr(jcontact, 'l')
+            contact.phone_home = self.safe_attr(jcontact, 'h')
+            contact.phone_office = self.safe_attr(jcontact, 'o')
+            contact.phone_mobile = self.safe_attr(jcontact, 'm')
+            contact.email = self.safe_attr(jcontact, 'e')
+            contact.deleted = (self.safe_attr(jcontact, 'd') == 'true')
+            if (new_contact):
+                # New record - add them to db...
+                new_contact_count = new_contact_count + 1
+                contact.handle = contact.firstname + '_' + contact.lastname
+                logging.info('Created new contact handle: ' + contact.handle)
+            contact.put()
+            logging.info('Saved contact: ' + contact.handle)
+
+            # We don't save off the client_id value (thus we add it after
+            # the "put"), but we want it to be in the JSON object we
+            # serialize out, so that the client can match this contact
+            # up with the client version.
+            client_id = self.safe_attr(jcontact, 'c')
+
+            # Create a high-water-mark for sync-state from the 'updated' time
+            # for this contact, so we return the correct value to the client.
+            high_water = str(long(_time.mktime(contact.updated.utctimetuple())) + 1)
+
+            # Add new contacts to our updated_contacts, so that we return them
+            # to the client (so the client gets the serverId for the
+            # added contact)
+            if (new_contact):
+                UpdatedContactData(updated_contacts, contact.handle, client_id, base_url,
+                        high_water)
+
+        logging.info('Client-side adds: ' + str(new_contact_count))
+
+    def list_contains_contact(self, contact_list, contact):
+        if (contact is None):
+            return False
+        contact_id = str(contact.key().id())
+        for next in contact_list:
+            if ((next != None) and (next['i'] == contact_id)):
+                return True
+        return False
+
+    def safe_attr(self, obj, attr_name):
+        if attr_name in obj:
+            return obj[attr_name]
+        return None
+
+class ResetDatabase(BaseWebServiceHandler):
+    """
+    Handles cron request to reset the contact database.
+
+    We have a weekly cron task that resets the database back to a
+    few contacts, so that it doesn't grow to an absurd size.
+    """
+
+    def get(self):
+        # Delete all the existing contacts from the database
+        contacts = datastore.Contact.all()
+        for contact in contacts:
+            contact.delete()
+
+        # Now create three sample contacts
+        contact1 = datastore.Contact(handle = 'juliet',
+                firstname = 'Juliet',
+                lastname = 'Capulet',
+                phone_mobile = '(650) 555-1000',
+                phone_home = '(650) 555-1001',
+                status = 'Wherefore art thou Romeo?')
+        contact1.put()
+
+        contact2 = datastore.Contact(handle = 'romeo',
+                firstname = 'Romeo',
+                lastname = 'Montague',
+                phone_mobile = '(650) 555-2000',
+                phone_home = '(650) 555-2001',
+                status = 'I dream\'d a dream to-night')
+        contact2.put()
+
+        contact3 = datastore.Contact(handle = 'tybalt',
+                firstname = 'Tybalt',
+                lastname = 'Capulet',
+                phone_mobile = '(650) 555-3000',
+                phone_home = '(650) 555-3001',
+                status = 'Have at thee, coward')
+        contact3.put()
+
+
+
+
+def toJSON(object):
+    """Dumps the data represented by the object to JSON for wire transfer."""
+    return simplejson.dumps(object)
+
+class UpdatedContactData(object):
+    """Holds data for user's contacts.
+
+    This class knows how to serialize itself to JSON.
+    """
+    __FIELD_MAP = {
+        'handle': 'u',
+        'firstname': 'f',
+        'lastname': 'l',
+        'status': 's',
+        'phone_home': 'h',
+        'phone_office': 'o',
+        'phone_mobile': 'm',
+        'email': 'e',
+        'client_id': 'c'
+    }
+
+    def __init__(self, contact_list, username, client_id, host_url, high_water_mark):
+        obj = datastore.Contact.get_contact_info(username)
+        contact = {}
+        for obj_name, json_name in self.__FIELD_MAP.items():
+            if hasattr(obj, obj_name):
+              v = getattr(obj, obj_name)
+              if (v != None):
+                  contact[json_name] = str(v)
+              else:
+                  contact[json_name] = None
+        contact['i'] = str(obj.key().id())
+        contact['a'] = host_url + "/avatar?id=" + str(obj.key().id())
+        contact['x'] = high_water_mark
+        if (client_id != None):
+            contact['c'] = str(client_id)
+        contact_list.append(contact)
+
+class DeletedContactData(object):
+    def __init__(self, contact_list, username, high_water_mark):
+        obj = datastore.Contact.get_contact_info(username)
+        contact = {}
+        contact['d'] = 'true'
+        contact['i'] = str(obj.key().id())
+        contact['x'] = high_water_mark
+        contact_list.append(contact)
+
+def main():
+    application = webapp.WSGIApplication(
+            [('/auth', Authenticate),
+             ('/sync', SyncContacts),
+             ('/reset_database', ResetDatabase),
+            ],
+            debug=True)
+    wsgiref.handlers.CGIHandler().run(application)
+
+if __name__ == "__main__":
+    main()
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/Constants.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/Constants.java
index 49f92bf..bc09da2 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/Constants.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/Constants.java
@@ -1,12 +1,12 @@
 /*
  * 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
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/AuthenticationService.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/AuthenticationService.java
index 2c163be..9fc72eb 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/AuthenticationService.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/AuthenticationService.java
@@ -1,18 +1,19 @@
 /*
  * 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.samplesync.authenticator;
 
 import android.app.Service;
@@ -49,7 +50,7 @@
     public IBinder onBind(Intent intent) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "getBinder()...  returning the AccountAuthenticator binder for intent "
-                + intent);
+                    + intent);
         }
         return mAuthenticator.getIBinder();
     }
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/Authenticator.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/Authenticator.java
index 0c79c5e..2eb6ecd 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/Authenticator.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/Authenticator.java
@@ -1,38 +1,57 @@
 /*
  * 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.samplesync.authenticator;
 
+import com.example.android.samplesync.Constants;
+import com.example.android.samplesync.client.NetworkUtilities;
+
 import android.accounts.AbstractAccountAuthenticator;
 import android.accounts.Account;
 import android.accounts.AccountAuthenticatorResponse;
 import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
-
-import com.example.android.samplesync.Constants;
-import com.example.android.samplesync.R;
-import com.example.android.samplesync.client.NetworkUtilities;
+import android.text.TextUtils;
+import android.util.Log;
 
 /**
  * This class is an implementation of AbstractAccountAuthenticator for
- * authenticating accounts in the com.example.android.samplesync domain.
+ * authenticating accounts in the com.example.android.samplesync domain. The
+ * interesting thing that this class demonstrates is the use of authTokens as
+ * part of the authentication process. In the account setup UI, the user enters
+ * their username and password. But for our subsequent calls off to the service
+ * for syncing, we want to use an authtoken instead - so we're not continually
+ * sending the password over the wire. getAuthToken() will be called when
+ * SyncAdapter calls AccountManager.blockingGetAuthToken(). When we get called,
+ * we need to return the appropriate authToken for the specified account. If we
+ * already have an authToken stored in the account, we return that authToken. If
+ * we don't, but we do have a username and password, then we'll attempt to talk
+ * to the sample service to fetch an authToken. If that fails (or we didn't have
+ * a username/password), then we need to prompt the user - so we create an
+ * AuthenticatorActivity intent and return that. That will display the dialog
+ * that prompts the user for their login information.
  */
 class Authenticator extends AbstractAccountAuthenticator {
 
+    /** The tag used to log to adb console. **/
+    private static final String TAG = "Authenticator";
+
     // Authentication Service context
     private final Context mContext;
 
@@ -43,10 +62,9 @@
 
     @Override
     public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
-        String authTokenType, String[] requiredFeatures, Bundle options) {
-
+            String authTokenType, String[] requiredFeatures, Bundle options) {
+        Log.v(TAG, "addAccount()");
         final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
-        intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, authTokenType);
         intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
         final Bundle bundle = new Bundle();
         bundle.putParcelable(AccountManager.KEY_INTENT, intent);
@@ -54,54 +72,49 @@
     }
 
     @Override
-    public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
-        Bundle options) {
-
-        if (options != null && options.containsKey(AccountManager.KEY_PASSWORD)) {
-            final String password = options.getString(AccountManager.KEY_PASSWORD);
-            final boolean verified = onlineConfirmPassword(account.name, password);
-            final Bundle result = new Bundle();
-            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, verified);
-            return result;
-        }
-        // Launch AuthenticatorActivity to confirm credentials
-        final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
-        intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
-        intent.putExtra(AuthenticatorActivity.PARAM_CONFIRM_CREDENTIALS, true);
-        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
-        final Bundle bundle = new Bundle();
-        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
-        return bundle;
+    public Bundle confirmCredentials(
+            AccountAuthenticatorResponse response, Account account, Bundle options) {
+        Log.v(TAG, "confirmCredentials()");
+        return null;
     }
 
     @Override
     public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+        Log.v(TAG, "editProperties()");
         throw new UnsupportedOperationException();
     }
 
     @Override
     public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
-        String authTokenType, Bundle loginOptions) {
+            String authTokenType, Bundle loginOptions) throws NetworkErrorException {
+        Log.v(TAG, "getAuthToken()");
 
+        // If the caller requested an authToken type we don't support, then
+        // return an error
         if (!authTokenType.equals(Constants.AUTHTOKEN_TYPE)) {
             final Bundle result = new Bundle();
             result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
             return result;
         }
+
+        // Extract the username and password from the Account Manager, and ask
+        // the server for an appropriate AuthToken.
         final AccountManager am = AccountManager.get(mContext);
         final String password = am.getPassword(account);
         if (password != null) {
-            final boolean verified = onlineConfirmPassword(account.name, password);
-            if (verified) {
+            final String authToken = NetworkUtilities.authenticate(account.name, password);
+            if (!TextUtils.isEmpty(authToken)) {
                 final Bundle result = new Bundle();
                 result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
                 result.putString(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
-                result.putString(AccountManager.KEY_AUTHTOKEN, password);
+                result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
                 return result;
             }
         }
-        // the password was missing or incorrect, return an Intent to an
-        // Activity that will prompt the user for the password.
+
+        // If we get here, then we couldn't access the user's password - so we
+        // need to re-prompt them for their credentials. We do that by creating
+        // an intent to display our AuthenticatorActivity panel.
         final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
         intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
         intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, authTokenType);
@@ -113,39 +126,27 @@
 
     @Override
     public String getAuthTokenLabel(String authTokenType) {
-        if (Constants.AUTHTOKEN_TYPE.equals(authTokenType)) {
-            return mContext.getString(R.string.label);
-        }
+        // null means we don't support multiple authToken types
+        Log.v(TAG, "getAuthTokenLabel()");
         return null;
     }
 
     @Override
-    public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
-        String[] features) {
-
+    public Bundle hasFeatures(
+            AccountAuthenticatorResponse response, Account account, String[] features) {
+        // This call is used to query whether the Authenticator supports
+        // specific features. We don't expect to get called, so we always
+        // return false (no) for any queries.
+        Log.v(TAG, "hasFeatures()");
         final Bundle result = new Bundle();
         result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
         return result;
     }
 
-    /**
-     * Validates user's password on the server
-     */
-    private boolean onlineConfirmPassword(String username, String password) {
-        return NetworkUtilities
-            .authenticate(username, password, null/* Handler */, null/* Context */);
-    }
-
     @Override
     public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
-        String authTokenType, Bundle loginOptions) {
-
-        final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
-        intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
-        intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, authTokenType);
-        intent.putExtra(AuthenticatorActivity.PARAM_CONFIRM_CREDENTIALS, false);
-        final Bundle bundle = new Bundle();
-        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
-        return bundle;
+            String authTokenType, Bundle loginOptions) {
+        Log.v(TAG, "updateCredentials()");
+        return null;
     }
 }
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/AuthenticatorActivity.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/AuthenticatorActivity.java
index 4e1ee2a..2a3c0fc 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/AuthenticatorActivity.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/AuthenticatorActivity.java
@@ -1,20 +1,25 @@
 /*
  * 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.samplesync.authenticator;
 
+import com.example.android.samplesync.Constants;
+import com.example.android.samplesync.R;
+import com.example.android.samplesync.client.NetworkUtilities;
+
 import android.accounts.Account;
 import android.accounts.AccountAuthenticatorActivity;
 import android.accounts.AccountManager;
@@ -23,6 +28,7 @@
 import android.content.ContentResolver;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.ContactsContract;
@@ -33,41 +39,36 @@
 import android.widget.EditText;
 import android.widget.TextView;
 
-import com.example.android.samplesync.Constants;
-import com.example.android.samplesync.R;
-import com.example.android.samplesync.client.NetworkUtilities;
-
 /**
  * Activity which displays login screen to the user.
  */
 public class AuthenticatorActivity extends AccountAuthenticatorActivity {
-
-    /** The Intent flag to confirm credentials. **/
+    /** The Intent flag to confirm credentials. */
     public static final String PARAM_CONFIRM_CREDENTIALS = "confirmCredentials";
 
-    /** The Intent extra to store password. **/
+    /** The Intent extra to store password. */
     public static final String PARAM_PASSWORD = "password";
 
-    /** The Intent extra to store username. **/
+    /** The Intent extra to store username. */
     public static final String PARAM_USERNAME = "username";
 
-    /** The Intent extra to store authtoken type. **/
+    /** The Intent extra to store username. */
     public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType";
 
-    /** The tag used to log to adb console. **/
+    /** The tag used to log to adb console. */
     private static final String TAG = "AuthenticatorActivity";
-
     private AccountManager mAccountManager;
 
-    private Thread mAuthThread;
+    /** Keep track of the login task so can cancel it if requested */
+    private UserLoginTask mAuthTask = null;
 
-    private String mAuthtoken;
-
-    private String mAuthtokenType;
+    /** Keep track of the progress dialog so we can dismiss it */
+    private ProgressDialog mProgressDialog = null;
 
     /**
      * If set we are just checking that the user knows their credentials; this
-     * doesn't cause the user's password to be changed on the device.
+     * doesn't cause the user's password or authToken to be changed on the
+     * device.
      */
     private Boolean mConfirmCredentials = false;
 
@@ -99,14 +100,13 @@
         Log.i(TAG, "loading data from Intent");
         final Intent intent = getIntent();
         mUsername = intent.getStringExtra(PARAM_USERNAME);
-        mAuthtokenType = intent.getStringExtra(PARAM_AUTHTOKEN_TYPE);
         mRequestNewAccount = mUsername == null;
         mConfirmCredentials = intent.getBooleanExtra(PARAM_CONFIRM_CREDENTIALS, false);
         Log.i(TAG, "    request new: " + mRequestNewAccount);
         requestWindowFeature(Window.FEATURE_LEFT_ICON);
         setContentView(R.layout.login_activity);
-        getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
-            android.R.drawable.ic_dialog_alert);
+        getWindow().setFeatureDrawableResource(
+                Window.FEATURE_LEFT_ICON, android.R.drawable.ic_dialog_alert);
         mMessage = (TextView) findViewById(R.id.message);
         mUsernameEdit = (EditText) findViewById(R.id.username_edit);
         mPasswordEdit = (EditText) findViewById(R.id.password_edit);
@@ -118,27 +118,31 @@
      * {@inheritDoc}
      */
     @Override
-    protected Dialog onCreateDialog(int id) {
+    protected Dialog onCreateDialog(int id, Bundle args) {
         final ProgressDialog dialog = new ProgressDialog(this);
         dialog.setMessage(getText(R.string.ui_activity_authenticating));
         dialog.setIndeterminate(true);
         dialog.setCancelable(true);
         dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
             public void onCancel(DialogInterface dialog) {
-                Log.i(TAG, "dialog cancel has been invoked");
-                if (mAuthThread != null) {
-                    mAuthThread.interrupt();
-                    finish();
+                Log.i(TAG, "user cancelling authentication");
+                if (mAuthTask != null) {
+                    mAuthTask.cancel(true);
                 }
             }
         });
+        // We save off the progress dialog in a field so that we can dismiss
+        // it later. We can't just call dismissDialog(0) because the system
+        // can lose track of our dialog if there's an orientation change.
+        mProgressDialog = dialog;
         return dialog;
     }
 
     /**
      * Handles onClick event on the Submit button. Sends username/password to
-     * the server for authentication.
-     * 
+     * the server for authentication. The button is configured to call
+     * handleLogin() in the layout XML.
+     *
      * @param view The Submit button for which this method is invoked
      */
     public void handleLogin(View view) {
@@ -149,11 +153,11 @@
         if (TextUtils.isEmpty(mUsername) || TextUtils.isEmpty(mPassword)) {
             mMessage.setText(getMessage());
         } else {
+            // Show a progress dialog, and kick off a background task to perform
+            // the user login attempt.
             showProgress();
-            // Start authenticating...
-            mAuthThread =
-                NetworkUtilities.attemptAuth(mUsername, mPassword, mHandler,
-                    AuthenticatorActivity.this);
+            mAuthTask = new UserLoginTask();
+            mAuthTask.execute();
         }
     }
 
@@ -161,8 +165,8 @@
      * Called when response is received from the server for confirm credentials
      * request. See onAuthenticationResult(). Sets the
      * AccountAuthenticatorResult which is sent back to the caller.
-     * 
-     * @param the confirmCredentials result.
+     *
+     * @param result the confirmCredentials result.
      */
     private void finishConfirmCredentials(boolean result) {
         Log.i(TAG, "finishConfirmCredentials()");
@@ -178,12 +182,13 @@
     /**
      * Called when response is received from the server for authentication
      * request. See onAuthenticationResult(). Sets the
-     * AccountAuthenticatorResult which is sent back to the caller. Also sets
-     * the authToken in AccountManager for this account.
-     * 
-     * @param the confirmCredentials result.
+     * AccountAuthenticatorResult which is sent back to the caller. We store the
+     * authToken that's returned from the server as the 'password' for this
+     * account - so we're never storing the user's actual password locally.
+     *
+     * @param result the confirmCredentials result.
      */
-    private void finishLogin() {
+    private void finishLogin(String authToken) {
 
         Log.i(TAG, "finishLogin()");
         final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
@@ -195,37 +200,35 @@
             mAccountManager.setPassword(account, mPassword);
         }
         final Intent intent = new Intent();
-        mAuthtoken = mPassword;
         intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
         intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
-        if (mAuthtokenType != null && mAuthtokenType.equals(Constants.AUTHTOKEN_TYPE)) {
-            intent.putExtra(AccountManager.KEY_AUTHTOKEN, mAuthtoken);
-        }
         setAccountAuthenticatorResult(intent.getExtras());
         setResult(RESULT_OK, intent);
         finish();
     }
 
     /**
-     * Hides the progress UI for a lengthy operation.
-     */
-    private void hideProgress() {
-        dismissDialog(0);
-    }
-
-    /**
      * Called when the authentication process completes (see attemptLogin()).
+     *
+     * @param authToken the authentication token returned by the server, or NULL if
+     *            authentication failed.
      */
-    public void onAuthenticationResult(boolean result) {
+    public void onAuthenticationResult(String authToken) {
 
-        Log.i(TAG, "onAuthenticationResult(" + result + ")");
+        boolean success = ((authToken != null) && (authToken.length() > 0));
+        Log.i(TAG, "onAuthenticationResult(" + success + ")");
+
+        // Our task is complete, so clear it out
+        mAuthTask = null;
+
         // Hide the progress dialog
         hideProgress();
-        if (result) {
+
+        if (success) {
             if (!mConfirmCredentials) {
-                finishLogin();
+                finishLogin(authToken);
             } else {
-                finishConfirmCredentials(true);
+                finishConfirmCredentials(success);
             }
         } else {
             Log.e(TAG, "onAuthenticationResult: failed to authenticate");
@@ -241,6 +244,16 @@
         }
     }
 
+    public void onAuthenticationCancel() {
+        Log.i(TAG, "onAuthenticationCancel()");
+
+        // Our task is complete, so clear it out
+        mAuthTask = null;
+
+        // Hide the progress dialog
+        hideProgress();
+    }
+
     /**
      * Returns the message to be displayed at the top of the login dialog box.
      */
@@ -265,4 +278,49 @@
     private void showProgress() {
         showDialog(0);
     }
+
+    /**
+     * Hides the progress UI for a lengthy operation.
+     */
+    private void hideProgress() {
+        if (mProgressDialog != null) {
+            mProgressDialog.dismiss();
+            mProgressDialog = null;
+        }
+    }
+
+    /**
+     * Represents an asynchronous task used to authenticate a user against the
+     * SampleSync Service
+     */
+    public class UserLoginTask extends AsyncTask<Void, Void, String> {
+
+        @Override
+        protected String doInBackground(Void... params) {
+            // We do the actual work of authenticating the user
+            // in the NetworkUtilities class.
+            try {
+                return NetworkUtilities.authenticate(mUsername, mPassword);
+            } catch (Exception ex) {
+                Log.e(TAG, "UserLoginTask.doInBackground: failed to authenticate");
+                Log.i(TAG, ex.toString());
+                return null;
+            }
+        }
+
+        @Override
+        protected void onPostExecute(final String authToken) {
+            // On a successful authentication, call back into the Activity to
+            // communicate the authToken (or null for an error).
+            onAuthenticationResult(authToken);
+        }
+
+        @Override
+        protected void onCancelled() {
+            // If the action was canceled (by the user clicking the cancel
+            // button in the progress dialog), then call back into the
+            // activity to let it know.
+            onAuthenticationCancel();
+        }
+    }
 }
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/NetworkUtilities.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/NetworkUtilities.java
index 7824a4d..bebcd72 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/NetworkUtilities.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/NetworkUtilities.java
@@ -1,23 +1,27 @@
 /*
  * 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.samplesync.client;
 
 import android.accounts.Account;
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.os.Handler;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.example.android.samplesync.authenticator.AuthenticatorActivity;
@@ -37,11 +41,19 @@
 import org.apache.http.params.HttpConnectionParams;
 import org.apache.http.params.HttpParams;
 import org.apache.http.util.EntityUtils;
+import org.json.JSONObject;
 import org.json.JSONArray;
 import org.json.JSONException;
 
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -52,31 +64,26 @@
  * Provides utility methods for communicating with the server.
  */
 final public class NetworkUtilities {
-
-    /** The tag used to log to adb console. **/
+    /** The tag used to log to adb console. */
     private static final String TAG = "NetworkUtilities";
-
-    /** The Intent extra to store password. **/
-    public static final String PARAM_PASSWORD = "password";
-
-    /** The Intent extra to store username. **/
+    /** POST parameter name for the user's account name */
     public static final String PARAM_USERNAME = "username";
-
-    public static final String PARAM_UPDATED = "timestamp";
-
-    public static final String USER_AGENT = "AuthenticationService/1.0";
-
-    public static final int REGISTRATION_TIMEOUT_MS = 30 * 1000; // ms
-
-    public static final String BASE_URL = "https://samplesyncadapter.appspot.com";
-
+    /** POST parameter name for the user's password */
+    public static final String PARAM_PASSWORD = "password";
+    /** POST parameter name for the user's authentication token */
+    public static final String PARAM_AUTH_TOKEN = "authtoken";
+    /** POST parameter name for the client's last-known sync state */
+    public static final String PARAM_SYNC_STATE = "syncstate";
+    /** POST parameter name for the sending client-edited contact info */
+    public static final String PARAM_CONTACTS_DATA = "contacts";
+    /** Timeout (in ms) we specify for each http request */
+    public static final int HTTP_REQUEST_TIMEOUT_MS = 30 * 1000;
+    /** Base URL for the v2 Sample Sync Service */
+    public static final String BASE_URL = "https://samplesyncadapter2.appspot.com";
+    /** URI for authentication service */
     public static final String AUTH_URI = BASE_URL + "/auth";
-
-    public static final String FETCH_FRIEND_UPDATES_URI = BASE_URL + "/fetch_friend_updates";
-
-    public static final String FETCH_STATUS_URI = BASE_URL + "/fetch_status";
-
-    private static HttpClient mHttpClient;
+    /** URI for sync service */
+    public static final String SYNC_CONTACTS_URI = BASE_URL + "/sync";
 
     private NetworkUtilities() {
     }
@@ -84,224 +91,188 @@
     /**
      * Configures the httpClient to connect to the URL provided.
      */
-    public static void maybeCreateHttpClient() {
-        if (mHttpClient == null) {
-            mHttpClient = new DefaultHttpClient();
-            final HttpParams params = mHttpClient.getParams();
-            HttpConnectionParams.setConnectionTimeout(params, REGISTRATION_TIMEOUT_MS);
-            HttpConnectionParams.setSoTimeout(params, REGISTRATION_TIMEOUT_MS);
-            ConnManagerParams.setTimeout(params, REGISTRATION_TIMEOUT_MS);
-        }
+    public static HttpClient getHttpClient() {
+        HttpClient httpClient = new DefaultHttpClient();
+        final HttpParams params = httpClient.getParams();
+        HttpConnectionParams.setConnectionTimeout(params, HTTP_REQUEST_TIMEOUT_MS);
+        HttpConnectionParams.setSoTimeout(params, HTTP_REQUEST_TIMEOUT_MS);
+        ConnManagerParams.setTimeout(params, HTTP_REQUEST_TIMEOUT_MS);
+        return httpClient;
     }
 
     /**
-     * Executes the network requests on a separate thread.
-     * 
-     * @param runnable The runnable instance containing network mOperations to
-     *        be executed.
+     * Connects to the SampleSync test server, authenticates the provided
+     * username and password.
+     *
+     * @param username The server account username
+     * @param password The server account password
+     * @return String The authentication token returned by the server (or null)
      */
-    public static Thread performOnBackgroundThread(final Runnable runnable) {
-        final Thread t = new Thread() {
-            @Override
-            public void run() {
-                try {
-                    runnable.run();
-                } finally {
-                }
-            }
-        };
-        t.start();
-        return t;
-    }
-
-    /**
-     * Connects to the Voiper server, authenticates the provided username and
-     * password.
-     * 
-     * @param username The user's username
-     * @param password The user's password
-     * @param handler The hander instance from the calling UI thread.
-     * @param context The context of the calling Activity.
-     * @return boolean The boolean result indicating whether the user was
-     *         successfully authenticated.
-     */
-    public static boolean authenticate(String username, String password, Handler handler,
-        final Context context) {
+    public static String authenticate(String username, String password) {
 
         final HttpResponse resp;
         final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
         params.add(new BasicNameValuePair(PARAM_USERNAME, username));
         params.add(new BasicNameValuePair(PARAM_PASSWORD, password));
-        HttpEntity entity = null;
+        final HttpEntity entity;
         try {
             entity = new UrlEncodedFormEntity(params);
         } catch (final UnsupportedEncodingException e) {
             // this should never happen.
-            throw new AssertionError(e);
+            throw new IllegalStateException(e);
         }
+        Log.i(TAG, "Authenticating to: " + AUTH_URI);
         final HttpPost post = new HttpPost(AUTH_URI);
         post.addHeader(entity.getContentType());
         post.setEntity(entity);
-        maybeCreateHttpClient();
         try {
-            resp = mHttpClient.execute(post);
+            resp = getHttpClient().execute(post);
+            String authToken = null;
             if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Successful authentication");
+                InputStream istream = (resp.getEntity() != null) ? resp.getEntity().getContent()
+                        : null;
+                if (istream != null) {
+                    BufferedReader ireader = new BufferedReader(new InputStreamReader(istream));
+                    authToken = ireader.readLine().trim();
                 }
-                sendResult(true, handler, context);
-                return true;
+            }
+            if ((authToken != null) && (authToken.length() > 0)) {
+                Log.v(TAG, "Successful authentication");
+                return authToken;
             } else {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Error authenticating" + resp.getStatusLine());
-                }
-                sendResult(false, handler, context);
-                return false;
+                Log.e(TAG, "Error authenticating" + resp.getStatusLine());
+                return null;
             }
         } catch (final IOException e) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "IOException when getting authtoken", e);
-            }
-            sendResult(false, handler, context);
-            return false;
+            Log.e(TAG, "IOException when getting authtoken", e);
+            return null;
         } finally {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "getAuthtoken completing");
-            }
+            Log.v(TAG, "getAuthtoken completing");
         }
     }
 
     /**
-     * Sends the authentication response from server back to the caller main UI
-     * thread through its handler.
-     * 
-     * @param result The boolean holding authentication result
-     * @param handler The main UI thread's handler instance.
-     * @param context The caller Activity's context.
+     * Perform 2-way sync with the server-side contacts. We send a request that
+     * includes all the locally-dirty contacts so that the server can process
+     * those changes, and we receive (and return) a list of contacts that were
+     * updated on the server-side that need to be updated locally.
+     *
+     * @param account The account being synced
+     * @param authtoken The authtoken stored in the AccountManager for this
+     *            account
+     * @param serverSyncState A token returned from the server on the last sync
+     * @param dirtyContacts A list of the contacts to send to the server
+     * @return A list of contacts that we need to update locally
      */
-    private static void sendResult(final Boolean result, final Handler handler,
-        final Context context) {
-        if (handler == null || context == null) {
-            return;
+    public static List<RawContact> syncContacts(
+            Account account, String authtoken, long serverSyncState, List<RawContact> dirtyContacts)
+            throws JSONException, ParseException, IOException, AuthenticationException {
+        // Convert our list of User objects into a list of JSONObject
+        List<JSONObject> jsonContacts = new ArrayList<JSONObject>();
+        for (RawContact rawContact : dirtyContacts) {
+            jsonContacts.add(rawContact.toJSONObject());
         }
-        handler.post(new Runnable() {
-            public void run() {
-                ((AuthenticatorActivity) context).onAuthenticationResult(result);
-            }
-        });
-    }
 
-    /**
-     * Attempts to authenticate the user credentials on the server.
-     * 
-     * @param username The user's username
-     * @param password The user's password to be authenticated
-     * @param handler The main UI thread's handler instance.
-     * @param context The caller Activity's context
-     * @return Thread The thread on which the network mOperations are executed.
-     */
-    public static Thread attemptAuth(final String username, final String password,
-        final Handler handler, final Context context) {
+        // Create a special JSONArray of our JSON contacts
+        JSONArray buffer = new JSONArray(jsonContacts);
 
-        final Runnable runnable = new Runnable() {
-            public void run() {
-                authenticate(username, password, handler, context);
-            }
-        };
-        // run on background thread.
-        return NetworkUtilities.performOnBackgroundThread(runnable);
-    }
+        // Create an array that will hold the server-side contacts
+        // that have been changed (returned by the server).
+        final ArrayList<RawContact> serverDirtyList = new ArrayList<RawContact>();
 
-    /**
-     * Fetches the list of friend data updates from the server
-     * 
-     * @param account The account being synced.
-     * @param authtoken The authtoken stored in AccountManager for this account
-     * @param lastUpdated The last time that sync was performed
-     * @return list The list of updates received from the server.
-     */
-    public static List<User> fetchFriendUpdates(Account account, String authtoken, Date lastUpdated)
-        throws JSONException, ParseException, IOException, AuthenticationException {
-
-        final ArrayList<User> friendList = new ArrayList<User>();
+        // Prepare our POST data
         final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
         params.add(new BasicNameValuePair(PARAM_USERNAME, account.name));
-        params.add(new BasicNameValuePair(PARAM_PASSWORD, authtoken));
-        if (lastUpdated != null) {
-            final SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm");
-            formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
-            params.add(new BasicNameValuePair(PARAM_UPDATED, formatter.format(lastUpdated)));
+        params.add(new BasicNameValuePair(PARAM_AUTH_TOKEN, authtoken));
+        params.add(new BasicNameValuePair(PARAM_CONTACTS_DATA, buffer.toString()));
+        if (serverSyncState > 0) {
+            params.add(new BasicNameValuePair(PARAM_SYNC_STATE, Long.toString(serverSyncState)));
         }
         Log.i(TAG, params.toString());
-        HttpEntity entity = null;
-        entity = new UrlEncodedFormEntity(params);
-        final HttpPost post = new HttpPost(FETCH_FRIEND_UPDATES_URI);
+        HttpEntity entity = new UrlEncodedFormEntity(params);
+
+        // Send the updated friends data to the server
+        Log.i(TAG, "Syncing to: " + SYNC_CONTACTS_URI);
+        final HttpPost post = new HttpPost(SYNC_CONTACTS_URI);
         post.addHeader(entity.getContentType());
         post.setEntity(entity);
-        maybeCreateHttpClient();
-        final HttpResponse resp = mHttpClient.execute(post);
+        final HttpResponse resp = getHttpClient().execute(post);
         final String response = EntityUtils.toString(resp.getEntity());
         if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-            // Succesfully connected to the samplesyncadapter server and
-            // authenticated.
-            // Extract friends data in json format.
-            final JSONArray friends = new JSONArray(response);
+            // Our request to the server was successful - so we assume
+            // that they accepted all the changes we sent up, and
+            // that the response includes the contacts that we need
+            // to update on our side...
+            final JSONArray serverContacts = new JSONArray(response);
             Log.d(TAG, response);
-            for (int i = 0; i < friends.length(); i++) {
-                friendList.add(User.valueOf(friends.getJSONObject(i)));
+            for (int i = 0; i < serverContacts.length(); i++) {
+                RawContact rawContact = RawContact.valueOf(serverContacts.getJSONObject(i));
+                if (rawContact != null) {
+                    serverDirtyList.add(rawContact);
+                }
             }
         } else {
             if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
-                Log.e(TAG, "Authentication exception in fetching remote contacts");
+                Log.e(TAG, "Authentication exception in sending dirty contacts");
                 throw new AuthenticationException();
             } else {
-                Log.e(TAG, "Server error in fetching remote contacts: " + resp.getStatusLine());
+                Log.e(TAG, "Server error in sending dirty contacts: " + resp.getStatusLine());
                 throw new IOException();
             }
         }
-        return friendList;
+
+        return serverDirtyList;
     }
 
     /**
-     * Fetches status messages for the user's friends from the server
-     * 
-     * @param account The account being synced.
-     * @param authtoken The authtoken stored in the AccountManager for the
-     *        account
-     * @return list The list of status messages received from the server.
+     * Download the avatar image from the server.
+     *
+     * @param avatarUrl the URL pointing to the avatar image
+     * @return a byte array with the raw JPEG avatar image
      */
-    public static List<User.Status> fetchFriendStatuses(Account account, String authtoken)
-        throws JSONException, ParseException, IOException, AuthenticationException {
-
-        final ArrayList<User.Status> statusList = new ArrayList<User.Status>();
-        final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
-        params.add(new BasicNameValuePair(PARAM_USERNAME, account.name));
-        params.add(new BasicNameValuePair(PARAM_PASSWORD, authtoken));
-        HttpEntity entity = null;
-        entity = new UrlEncodedFormEntity(params);
-        final HttpPost post = new HttpPost(FETCH_STATUS_URI);
-        post.addHeader(entity.getContentType());
-        post.setEntity(entity);
-        maybeCreateHttpClient();
-        final HttpResponse resp = mHttpClient.execute(post);
-        final String response = EntityUtils.toString(resp.getEntity());
-        if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-            // Succesfully connected to the samplesyncadapter server and
-            // authenticated.
-            // Extract friends data in json format.
-            final JSONArray statuses = new JSONArray(response);
-            for (int i = 0; i < statuses.length(); i++) {
-                statusList.add(User.Status.valueOf(statuses.getJSONObject(i)));
-            }
-        } else {
-            if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
-                Log.e(TAG, "Authentication exception in fetching friend status list");
-                throw new AuthenticationException();
-            } else {
-                Log.e(TAG, "Server error in fetching friend status list");
-                throw new IOException();
-            }
+    public static byte[] downloadAvatar(final String avatarUrl) {
+        // If there is no avatar, we're done
+        if (TextUtils.isEmpty(avatarUrl)) {
+            return null;
         }
-        return statusList;
+
+        try {
+            Log.i(TAG, "Downloading avatar: " + avatarUrl);
+            // Request the avatar image from the server, and create a bitmap
+            // object from the stream we get back.
+            URL url = new URL(avatarUrl);
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            connection.connect();
+            try {
+                final BitmapFactory.Options options = new BitmapFactory.Options();
+                final Bitmap avatar = BitmapFactory.decodeStream(connection.getInputStream(),
+                        null, options);
+
+                // Take the image we received from the server, whatever format it
+                // happens to be in, and convert it to a JPEG image. Note: we're
+                // not resizing the avatar - we assume that the image we get from
+                // the server is a reasonable size...
+                Log.i(TAG, "Converting avatar to JPEG");
+                ByteArrayOutputStream convertStream = new ByteArrayOutputStream(
+                        avatar.getWidth() * avatar.getHeight() * 4);
+                avatar.compress(Bitmap.CompressFormat.JPEG, 95, convertStream);
+                convertStream.flush();
+                convertStream.close();
+                // On pre-Honeycomb systems, it's important to call recycle on bitmaps
+                avatar.recycle();
+                return convertStream.toByteArray();
+            } finally {
+                connection.disconnect();
+            }
+        } catch (MalformedURLException muex) {
+            // A bad URL - nothing we can really do about it here...
+            Log.e(TAG, "Malformed avatar URL: " + avatarUrl);
+        } catch (IOException ioex) {
+            // If we're unable to download the avatar, it's a bummer but not the
+            // end of the world. We'll try to get it next time we sync.
+            Log.e(TAG, "Failed to download user avatar: " + avatarUrl);
+        }
+        return null;
     }
+
 }
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/RawContact.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/RawContact.java
new file mode 100644
index 0000000..6aa73d9
--- /dev/null
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/RawContact.java
@@ -0,0 +1,260 @@
+/*
+ * 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.samplesync.client;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.json.JSONObject;
+import org.json.JSONException;
+
+import java.lang.StringBuilder;
+
+/**
+ * Represents a low-level contacts RawContact - or at least
+ * the fields of the RawContact that we care about.
+ */
+final public class RawContact {
+
+    /** The tag used to log to adb console. **/
+    private static final String TAG = "RawContact";
+
+    private final String mUserName;
+
+    private final String mFullName;
+
+    private final String mFirstName;
+
+    private final String mLastName;
+
+    private final String mCellPhone;
+
+    private final String mOfficePhone;
+
+    private final String mHomePhone;
+
+    private final String mEmail;
+
+    private final String mStatus;
+
+    private final String mAvatarUrl;
+
+    private final boolean mDeleted;
+
+    private final boolean mDirty;
+
+    private final long mServerContactId;
+
+    private final long mRawContactId;
+
+    private final long mSyncState;
+
+    public long getServerContactId() {
+        return mServerContactId;
+    }
+
+    public long getRawContactId() {
+        return mRawContactId;
+    }
+
+    public String getUserName() {
+        return mUserName;
+    }
+
+    public String getFirstName() {
+        return mFirstName;
+    }
+
+    public String getLastName() {
+        return mLastName;
+    }
+
+    public String getFullName() {
+        return mFullName;
+    }
+
+    public String getCellPhone() {
+        return mCellPhone;
+    }
+
+    public String getOfficePhone() {
+        return mOfficePhone;
+    }
+
+    public String getHomePhone() {
+        return mHomePhone;
+    }
+
+    public String getEmail() {
+        return mEmail;
+    }
+
+    public String getStatus() {
+        return mStatus;
+    }
+
+    public String getAvatarUrl() {
+        return mAvatarUrl;
+    }
+
+    public boolean isDeleted() {
+        return mDeleted;
+    }
+
+    public boolean isDirty() {
+        return mDirty;
+    }
+
+    public long getSyncState() {
+        return mSyncState;
+    }
+
+    public String getBestName() {
+        if (!TextUtils.isEmpty(mFullName)) {
+            return mFullName;
+        } else if (TextUtils.isEmpty(mFirstName)) {
+            return mLastName;
+        } else {
+            return mFirstName;
+        }
+    }
+
+    /**
+     * Convert the RawContact object into a JSON string.  From the
+     * JSONString interface.
+     * @return a JSON string representation of the object
+     */
+    public JSONObject toJSONObject() {
+        JSONObject json = new JSONObject();
+
+        try {
+            if (!TextUtils.isEmpty(mFirstName)) {
+                json.put("f", mFirstName);
+            }
+            if (!TextUtils.isEmpty(mLastName)) {
+                json.put("l", mLastName);
+            }
+            if (!TextUtils.isEmpty(mCellPhone)) {
+                json.put("m", mCellPhone);
+            }
+            if (!TextUtils.isEmpty(mOfficePhone)) {
+                json.put("o", mOfficePhone);
+            }
+            if (!TextUtils.isEmpty(mHomePhone)) {
+                json.put("h", mHomePhone);
+            }
+            if (!TextUtils.isEmpty(mEmail)) {
+                json.put("e", mEmail);
+            }
+            if (mServerContactId > 0) {
+                json.put("i", mServerContactId);
+            }
+            if (mRawContactId > 0) {
+                json.put("c", mRawContactId);
+            }
+            if (mDeleted) {
+                json.put("d", mDeleted);
+            }
+        } catch (final Exception ex) {
+            Log.i(TAG, "Error converting RawContact to JSONObject" + ex.toString());
+        }
+
+        return json;
+    }
+
+    public RawContact(String name, String fullName, String firstName, String lastName,
+            String cellPhone, String officePhone, String homePhone, String email,
+            String status, String avatarUrl, boolean deleted, long serverContactId,
+            long rawContactId, long syncState, boolean dirty) {
+        mUserName = name;
+        mFullName = fullName;
+        mFirstName = firstName;
+        mLastName = lastName;
+        mCellPhone = cellPhone;
+        mOfficePhone = officePhone;
+        mHomePhone = homePhone;
+        mEmail = email;
+        mStatus = status;
+        mAvatarUrl = avatarUrl;
+        mDeleted = deleted;
+        mServerContactId = serverContactId;
+        mRawContactId = rawContactId;
+        mSyncState = syncState;
+        mDirty = dirty;
+    }
+
+    /**
+     * Creates and returns an instance of the RawContact from the provided JSON data.
+     *
+     * @param user The JSONObject containing user data
+     * @return user The new instance of Sample RawContact created from the JSON data.
+     */
+    public static RawContact valueOf(JSONObject contact) {
+
+        try {
+            final String userName = !contact.isNull("u") ? contact.getString("u") : null;
+            final int serverContactId = !contact.isNull("i") ? contact.getInt("i") : -1;
+            // If we didn't get either a username or serverId for the contact, then
+            // we can't do anything with it locally...
+            if ((userName == null) && (serverContactId <= 0)) {
+                throw new JSONException("JSON contact missing required 'u' or 'i' fields");
+            }
+
+            final int rawContactId = !contact.isNull("c") ? contact.getInt("c") : -1;
+            final String firstName = !contact.isNull("f")  ? contact.getString("f") : null;
+            final String lastName = !contact.isNull("l") ? contact.getString("l") : null;
+            final String cellPhone = !contact.isNull("m") ? contact.getString("m") : null;
+            final String officePhone = !contact.isNull("o") ? contact.getString("o") : null;
+            final String homePhone = !contact.isNull("h") ? contact.getString("h") : null;
+            final String email = !contact.isNull("e") ? contact.getString("e") : null;
+            final String status = !contact.isNull("s") ? contact.getString("s") : null;
+            final String avatarUrl = !contact.isNull("a") ? contact.getString("a") : null;
+            final boolean deleted = !contact.isNull("d") ? contact.getBoolean("d") : false;
+            final long syncState = !contact.isNull("x") ? contact.getLong("x") : 0;
+            return new RawContact(userName, null, firstName, lastName, cellPhone,
+                    officePhone, homePhone, email, status, avatarUrl, deleted,
+                    serverContactId, rawContactId, syncState, false);
+        } catch (final Exception ex) {
+            Log.i(TAG, "Error parsing JSON contact object" + ex.toString());
+        }
+        return null;
+    }
+
+    /**
+     * Creates and returns RawContact instance from all the supplied parameters.
+     */
+    public static RawContact create(String fullName, String firstName, String lastName,
+            String cellPhone, String officePhone, String homePhone,
+            String email, String status, boolean deleted, long rawContactId,
+            long serverContactId) {
+        return new RawContact(null, fullName, firstName, lastName, cellPhone, officePhone,
+                homePhone, email, status, null, deleted, serverContactId, rawContactId,
+                -1, true);
+    }
+
+    /**
+     * Creates and returns a User instance that represents a deleted user.
+     * Since the user is deleted, all we need are the client/server IDs.
+     * @param clientUserId The client-side ID for the contact
+     * @param serverUserId The server-side ID for the contact
+     * @return a minimal User object representing the deleted contact.
+     */
+    public static RawContact createDeletedContact(long rawContactId, long serverContactId)
+    {
+        return new RawContact(null, null, null, null, null, null, null,
+                null, null, null, true, serverContactId, rawContactId, -1, true);
+    }
+}
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/User.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/User.java
deleted file mode 100644
index 217a383..0000000
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/client/User.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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.samplesync.client;
-
-import android.util.Log;
-
-import org.json.JSONObject;
-
-/**
- * Represents a sample SyncAdapter user
- */
-final public class User {
-
-    private final String mUserName;
-
-    private final String mFirstName;
-
-    private final String mLastName;
-
-    private final String mCellPhone;
-
-    private final String mOfficePhone;
-
-    private final String mHomePhone;
-
-    private final String mEmail;
-
-    private final boolean mDeleted;
-
-    private final int mUserId;
-
-    public int getUserId() {
-        return mUserId;
-    }
-
-    public String getUserName() {
-        return mUserName;
-    }
-
-    public String getFirstName() {
-        return mFirstName;
-    }
-
-    public String getLastName() {
-        return mLastName;
-    }
-
-    public String getCellPhone() {
-        return mCellPhone;
-    }
-
-    public String getOfficePhone() {
-        return mOfficePhone;
-    }
-
-    public String getHomePhone() {
-        return mHomePhone;
-    }
-
-    public String getEmail() {
-        return mEmail;
-    }
-
-    public boolean isDeleted() {
-        return mDeleted;
-    }
-
-    private User(String name, String firstName, String lastName, String cellPhone,
-        String officePhone, String homePhone, String email, Boolean deleted, Integer userId) {
-
-        mUserName = name;
-        mFirstName = firstName;
-        mLastName = lastName;
-        mCellPhone = cellPhone;
-        mOfficePhone = officePhone;
-        mHomePhone = homePhone;
-        mEmail = email;
-        mDeleted = deleted;
-        mUserId = userId;
-    }
-
-    /**
-     * Creates and returns an instance of the user from the provided JSON data.
-     * 
-     * @param user The JSONObject containing user data
-     * @return user The new instance of Voiper user created from the JSON data.
-     */
-    public static User valueOf(JSONObject user) {
-
-        try {
-            final String userName = user.getString("u");
-            final String firstName = user.has("f") ? user.getString("f") : null;
-            final String lastName = user.has("l") ? user.getString("l") : null;
-            final String cellPhone = user.has("m") ? user.getString("m") : null;
-            final String officePhone = user.has("o") ? user.getString("o") : null;
-            final String homePhone = user.has("h") ? user.getString("h") : null;
-            final String email = user.has("e") ? user.getString("e") : null;
-            final boolean deleted = user.has("d") ? user.getBoolean("d") : false;
-            final int userId = user.getInt("i");
-            return new User(userName, firstName, lastName, cellPhone, officePhone, homePhone,
-                email, deleted, userId);
-        } catch (final Exception ex) {
-            Log.i("User", "Error parsing JSON user object" + ex.toString());
-        }
-        return null;
-    }
-
-    /**
-     * Represents the User's status messages
-     * 
-     */
-    final public static class Status {
-
-        private final Integer mUserId;
-
-        private final String mStatus;
-
-        public int getUserId() {
-            return mUserId;
-        }
-
-        public String getStatus() {
-            return mStatus;
-        }
-
-        public Status(Integer userId, String status) {
-            mUserId = userId;
-            mStatus = status;
-        }
-
-        public static User.Status valueOf(JSONObject userStatus) {
-            try {
-                final int userId = userStatus.getInt("i");
-                final String status = userStatus.getString("s");
-                return new User.Status(userId, status);
-            } catch (final Exception ex) {
-                Log.i("User.Status", "Error parsing JSON user object");
-            }
-            return null;
-        }
-    }
-}
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/editor/ContactEditorActivity.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/editor/ContactEditorActivity.java
new file mode 100644
index 0000000..2e30be7
--- /dev/null
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/editor/ContactEditorActivity.java
@@ -0,0 +1,369 @@
+/*
+ * 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.samplesync.editor;
+
+import com.example.android.samplesync.R;
+import com.example.android.samplesync.client.RawContact;
+import com.example.android.samplesync.platform.BatchOperation;
+import com.example.android.samplesync.platform.ContactManager;
+import com.example.android.samplesync.platform.ContactManager.EditorQuery;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.EditText;
+import android.widget.TextView;
+
+/**
+ * Implements a sample editor for a contact that belongs to a remote contact service.
+ * The editor can be invoked for an existing SampleSyncAdapter contact, or it can
+ * be used to create a brand new SampleSyncAdapter contact. We look at the Intent
+ * object to figure out whether this is a "new" or "edit" operation.
+ */
+public class ContactEditorActivity extends Activity {
+    private static final String TAG = "SampleSyncAdapter";
+
+    // Keep track of whether we're inserting a new contact or editing an
+    // existing contact.
+    private boolean mIsInsert;
+
+    // The name of the external account we're syncing this contact to.
+    private String mAccountName;
+
+    // For existing contacts, this is the URI to the contact data.
+    private Uri mRawContactUri;
+
+    // The raw clientId for this contact
+    private long mRawContactId;
+
+    // Make sure we only attempt to save the contact once if the
+    // user presses the "done" button multiple times...
+    private boolean mSaveInProgress = false;
+
+    // Keep track of the controls used to edit contact values, so we can get/set
+    // those values easily.
+    private EditText mNameEditText;
+    private EditText mHomePhoneEditText;
+    private EditText mMobilePhoneEditText;
+    private EditText mWorkPhoneEditText;
+    private EditText mEmailEditText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.editor);
+
+        mNameEditText = (EditText)findViewById(R.id.editor_name);
+        mHomePhoneEditText = (EditText)findViewById(R.id.editor_phone_home);
+        mMobilePhoneEditText = (EditText)findViewById(R.id.editor_phone_mobile);
+        mWorkPhoneEditText = (EditText)findViewById(R.id.editor_phone_work);
+        mEmailEditText = (EditText)findViewById(R.id.editor_email);
+
+        // Figure out whether we're creating a new contact (ACTION_INSERT) or editing
+        // an existing contact.
+        Intent intent = getIntent();
+        String action = intent.getAction();
+        if (Intent.ACTION_INSERT.equals(action)) {
+            // We're inserting a new contact, so save off the external account name
+            // which should have been added to the intent we were passed.
+            mIsInsert = true;
+            String accountName = intent.getStringExtra(RawContacts.ACCOUNT_NAME);
+            if (accountName == null) {
+                Log.e(TAG, "Account name is required");
+                finish();
+            }
+            setAccountName(accountName);
+        } else {
+            // We're editing an existing contact. Load in the data from the contact
+            // so that the user can edit it.
+            mIsInsert = false;
+            mRawContactUri = intent.getData();
+            if (mRawContactUri == null) {
+                Log.e(TAG, "Raw contact URI is required");
+                finish();
+            }
+            startLoadRawContactEntity();
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        // This method will have been called if the user presses the "Back" button
+        // in the ActionBar.  We treat that the same way as the "Done" button in
+        // the ActionBar.
+        save();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // This method gets called so that we can place items in the main Options menu -
+        // for example, the ActionBar items.  We add our menus from the res/menu/edit.xml
+        // file.
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.edit, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+            case R.id.menu_done:
+                // The user pressed the "Home" button or our "Done" button - both
+                // in the ActionBar.  In both cases, we want to save the contact
+                // and exit.
+                save();
+                return true;
+            case R.id.menu_cancel:
+                // The user pressed the Cancel menu item in the ActionBar.
+                // Close the editor without saving any changes.
+                finish();
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Create an AsyncTask to load the contact from the Contacts data provider
+     */
+    private void startLoadRawContactEntity() {
+        Uri uri = Uri.withAppendedPath(mRawContactUri, RawContacts.Entity.CONTENT_DIRECTORY);
+        new LoadRawContactTask().execute(uri);
+    }
+
+    /**
+     * Called by the LoadRawContactTask when the contact information has been
+     * successfully loaded from the Contacts data provider.
+     */
+    public void onRawContactEntityLoaded(Cursor cursor) {
+        while (cursor.moveToNext()) {
+            String mimetype = cursor.getString(EditorQuery.COLUMN_MIMETYPE);
+            if (StructuredName.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                setAccountName(cursor.getString(EditorQuery.COLUMN_ACCOUNT_NAME));
+                mRawContactId = cursor.getLong(EditorQuery.COLUMN_RAW_CONTACT_ID);
+                mNameEditText.setText(cursor.getString(EditorQuery.COLUMN_FULL_NAME));
+            } else if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                final int type = cursor.getInt(EditorQuery.COLUMN_PHONE_TYPE);
+                if (type == Phone.TYPE_HOME) {
+                    mHomePhoneEditText.setText(cursor.getString(EditorQuery.COLUMN_PHONE_NUMBER));
+                } else if (type == Phone.TYPE_MOBILE) {
+                    mMobilePhoneEditText.setText(cursor.getString(EditorQuery.COLUMN_PHONE_NUMBER));
+                } else if (type == Phone.TYPE_WORK) {
+                    mWorkPhoneEditText.setText(cursor.getString(EditorQuery.COLUMN_PHONE_NUMBER));
+                }
+            } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                mEmailEditText.setText(cursor.getString(EditorQuery.COLUMN_DATA1));
+            }
+        }
+    }
+
+    /**
+     * Save the updated contact data. We actually take two different actions
+     * depending on whether we are creating a new contact or editing an
+     * existing contact.
+     */
+    public void save() {
+        // If we're already saving this contact, don't kick-off yet
+        // another save - the user probably just pressed the "Done"
+        // button multiple times...
+        if (mSaveInProgress) {
+            return;
+        }
+
+        mSaveInProgress = true;
+        if (mIsInsert) {
+            saveNewContact();
+        } else {
+            saveChanges();
+        }
+    }
+
+    /**
+     * Save off the external contacts provider account name. We show the account name
+     * in the header section of the edit panel, and we also need it later when we
+     * save off a brand new contact.
+     */
+    private void setAccountName(String accountName) {
+        mAccountName = accountName;
+        if (accountName != null) {
+            TextView accountNameLabel = (TextView)findViewById(R.id.header_account_name);
+            if (accountNameLabel != null) {
+                accountNameLabel.setText(accountName);
+            }
+        }
+    }
+
+    /**
+     * Save a new contact using the Contacts content provider. The actual insertion
+     * is performed in an AsyncTask.
+     */
+    @SuppressWarnings("unchecked")
+    private void saveNewContact() {
+        new InsertContactTask().execute(buildRawContact());
+    }
+
+    /**
+     * Save changes to an existing contact.  The actual update is performed in
+     * an AsyncTask.
+     */
+    @SuppressWarnings("unchecked")
+    private void saveChanges() {
+        new UpdateContactTask().execute(buildRawContact());
+    }
+
+    /**
+     * Build a RawContact object from the data in the user-editable form
+     * @return a new RawContact object representing the edited user
+     */
+    private RawContact buildRawContact() {
+        return RawContact.create(mNameEditText.getText().toString(),
+                null,
+                null,
+                mMobilePhoneEditText.getText().toString(),
+                mWorkPhoneEditText.getText().toString(),
+                mHomePhoneEditText.getText().toString(),
+                mEmailEditText.getText().toString(),
+                null,
+                false,
+                mRawContactId,
+                -1);
+    }
+
+    /**
+     * Called after a contact is saved - both for edited contacts and new contacts.
+     * We set the final result of the activity to be "ok", and then close the activity
+     * by calling finish().
+     */
+    public void onContactSaved(Uri result) {
+        if (result != null) {
+            Intent intent = new Intent();
+            intent.setData(result);
+            setResult(RESULT_OK, intent);
+            finish();
+        }
+        mSaveInProgress = false;
+    }
+
+    /**
+     * Represents an asynchronous task used to load a contact from
+     * the Contacts content provider.
+     *
+     */
+    public class LoadRawContactTask extends AsyncTask<Uri, Void, Cursor> {
+
+        @Override
+        protected Cursor doInBackground(Uri... params) {
+            // Our background task is to load the contact from the Contacts provider
+            return getContentResolver().query(params[0], EditorQuery.PROJECTION, null, null, null);
+        }
+
+        @Override
+        protected void onPostExecute(Cursor cursor) {
+            // After we've successfully loaded the contact, call back into
+            // the ContactEditorActivity so we can update the UI
+            try {
+                if (cursor != null) {
+                    onRawContactEntityLoaded(cursor);
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    /**
+     * Represents an asynchronous task used to save a new contact
+     * into the contacts database.
+     */
+    public class InsertContactTask extends AsyncTask<RawContact, Void, Uri> {
+
+        @Override
+        protected Uri doInBackground(RawContact... params) {
+            try {
+                final RawContact rawContact = params[0];
+                final Context context = getApplicationContext();
+                final ContentResolver resolver = getContentResolver();
+                final BatchOperation batchOperation = new BatchOperation(context, resolver);
+                ContactManager.addContact(context, mAccountName, rawContact, false, batchOperation);
+                Uri rawContactUri = batchOperation.execute();
+
+                // Convert the raw contact URI to a contact URI
+                if (rawContactUri != null) {
+                    return RawContacts.getContactLookupUri(resolver, rawContactUri);
+                } else {
+                    Log.e(TAG, "Could not save new contact");
+                    return null;
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "An error occurred while saving new contact", e);
+            }
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Uri result) {
+            // Tell the UI that the contact has been successfully saved
+            onContactSaved(result);
+        }
+    }
+
+
+    /**
+     * Represents an asynchronous task used to save an updated contact
+     * into the contacts database.
+     */
+    public class UpdateContactTask extends AsyncTask<RawContact, Void, Uri> {
+
+        @Override
+        protected Uri doInBackground(RawContact... params) {
+            try {
+                final RawContact rawContact = params[0];
+                final Context context = getApplicationContext();
+                final ContentResolver resolver = getContentResolver();
+                final BatchOperation batchOperation = new BatchOperation(context, resolver);
+                ContactManager.updateContact(context, resolver, rawContact, false, false, false,
+                        false, rawContact.getRawContactId(), batchOperation);
+                batchOperation.execute();
+
+                // Convert the raw contact URI to a contact URI
+                return RawContacts.getContactLookupUri(resolver, mRawContactUri);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not save changes", e);
+            }
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Uri result) {
+            // Tell the UI that the contact has been successfully saved
+            onContactSaved(result);
+        }
+    }
+}
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/BatchOperation.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/BatchOperation.java
index 0be3daa..3a9c879 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/BatchOperation.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/BatchOperation.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -16,9 +16,11 @@
 package com.example.android.samplesync.platform;
 
 import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.OperationApplicationException;
+import android.net.Uri;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
 import android.util.Log;
@@ -50,19 +52,24 @@
         mOperations.add(cpo);
     }
 
-    public void execute() {
+    public Uri execute() {
+        Uri result = null;
 
         if (mOperations.size() == 0) {
-            return;
+            return result;
         }
         // Apply the mOperations to the content provider
         try {
-            mResolver.applyBatch(ContactsContract.AUTHORITY, mOperations);
+            ContentProviderResult[] results = mResolver.applyBatch(ContactsContract.AUTHORITY,
+                    mOperations);
+            if ((results != null) && (results.length > 0))
+                result = results[0].uri;
         } catch (final OperationApplicationException e1) {
             Log.e(TAG, "storing contact data failed", e1);
         } catch (final RemoteException e2) {
             Log.e(TAG, "storing contact data failed", e2);
         }
         mOperations.clear();
+        return result;
     }
 }
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/ContactManager.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/ContactManager.java
index 218b165..2335d38 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/ContactManager.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/ContactManager.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -15,25 +15,30 @@
  */
 package com.example.android.samplesync.platform;
 
+import com.example.android.samplesync.Constants;
+import com.example.android.samplesync.R;
+import com.example.android.samplesync.client.RawContact;
+
+import android.accounts.Account;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StatusUpdates;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.Settings;
+import android.provider.ContactsContract.StatusUpdates;
 import android.util.Log;
 
-import com.example.android.samplesync.Constants;
-import com.example.android.samplesync.R;
-import com.example.android.samplesync.client.User;
-
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -49,81 +54,172 @@
     private static final String TAG = "ContactManager";
 
     /**
-     * Synchronize raw contacts
-     * 
+     * Take a list of updated contacts and apply those changes to the
+     * contacts database. Typically this list of contacts would have been
+     * returned from the server, and we want to apply those changes locally.
+     *
      * @param context The context of Authenticator Activity
      * @param account The username for the account
-     * @param users The list of users
+     * @param rawContacts The list of contacts to update
+     * @param lastSyncMarker The previous server sync-state
+     * @return the server syncState that should be used in our next
+     * sync request.
      */
-    public static synchronized void syncContacts(Context context, String account, List<User> users) {
+    public static synchronized long updateContacts(Context context, String account,
+            List<RawContact> rawContacts, long lastSyncMarker) {
 
-        long userId;
-        long rawContactId = 0;
+        long currentSyncMarker = lastSyncMarker;
         final ContentResolver resolver = context.getContentResolver();
         final BatchOperation batchOperation = new BatchOperation(context, resolver);
+        final List<RawContact> newUsers = new ArrayList<RawContact>();
+
         Log.d(TAG, "In SyncContacts");
-        for (final User user : users) {
-            userId = user.getUserId();
-            // Check to see if the contact needs to be inserted or updated
-            rawContactId = lookupRawContact(resolver, userId);
+        for (final RawContact rawContact : rawContacts) {
+            // The server returns a syncState (x) value with each contact record.
+            // The syncState is sequential, so higher values represent more recent
+            // changes than lower values. We keep track of the highest value we
+            // see, and consider that a "high water mark" for the changes we've
+            // received from the server.  That way, on our next sync, we can just
+            // ask for changes that have occurred since that most-recent change.
+            if (rawContact.getSyncState() > currentSyncMarker) {
+                currentSyncMarker = rawContact.getSyncState();
+            }
+
+            // If the server returned a clientId for this user, then it's likely
+            // that the user was added here, and was just pushed to the server
+            // for the first time. In that case, we need to update the main
+            // row for this contact so that the RawContacts.SOURCE_ID value
+            // contains the correct serverId.
+            final long rawContactId;
+            final boolean updateServerId;
+            if (rawContact.getRawContactId() > 0) {
+                rawContactId = rawContact.getRawContactId();
+                updateServerId = true;
+            } else {
+                long serverContactId = rawContact.getServerContactId();
+                rawContactId = lookupRawContact(resolver, serverContactId);
+                updateServerId = false;
+            }
             if (rawContactId != 0) {
-                if (!user.isDeleted()) {
-                    // update contact
-                    updateContact(context, resolver, account, user, rawContactId, batchOperation);
+                if (!rawContact.isDeleted()) {
+                    updateContact(context, resolver, rawContact, updateServerId,
+                            true, true, true, rawContactId, batchOperation);
                 } else {
-                    // delete contact
                     deleteContact(context, rawContactId, batchOperation);
                 }
             } else {
-                // add new contact
                 Log.d(TAG, "In addContact");
-                if (!user.isDeleted()) {
-                    addContact(context, account, user, batchOperation);
+                if (!rawContact.isDeleted()) {
+                    newUsers.add(rawContact);
+                    addContact(context, account, rawContact, true, batchOperation);
                 }
             }
             // A sync adapter should batch operations on multiple contacts,
             // because it will make a dramatic performance difference.
+            // (UI updates, etc)
             if (batchOperation.size() >= 50) {
                 batchOperation.execute();
             }
         }
         batchOperation.execute();
+
+        return currentSyncMarker;
     }
 
     /**
-     * Add a list of status messages to the contacts provider.
-     * 
-     * @param context the context to use
-     * @param accountName the username of the logged in user
-     * @param statuses the list of statuses to store
+     * Return a list of the local contacts that have been marked as
+     * "dirty", and need syncing to the SampleSync server.
+     *
+     * @param context The context of Authenticator Activity
+     * @param account The account that we're interested in syncing
+     * @return a list of Users that are considered "dirty"
      */
-    public static void insertStatuses(Context context, String username, List<User.Status> list) {
+    public static List<RawContact> getDirtyContacts(Context context, Account account) {
+        Log.i(TAG, "*** Looking for local dirty contacts");
+        List<RawContact> dirtyContacts = new ArrayList<RawContact>();
 
-        final ContentValues values = new ContentValues();
+        final ContentResolver resolver = context.getContentResolver();
+        final Cursor c = resolver.query(DirtyQuery.CONTENT_URI,
+                DirtyQuery.PROJECTION,
+                DirtyQuery.SELECTION,
+                new String[] {account.name},
+                null);
+        try {
+            while (c.moveToNext()) {
+                final long rawContactId = c.getLong(DirtyQuery.COLUMN_RAW_CONTACT_ID);
+                final long serverContactId = c.getLong(DirtyQuery.COLUMN_SERVER_ID);
+                final boolean isDirty = "1".equals(c.getString(DirtyQuery.COLUMN_DIRTY));
+                final boolean isDeleted = "1".equals(c.getString(DirtyQuery.COLUMN_DELETED));
+
+                // The system actually keeps track of a change version number for
+                // each contact. It may be something you're interested in for your
+                // client-server sync protocol. We're not using it in this example,
+                // other than to log it.
+                final long version = c.getLong(DirtyQuery.COLUMN_VERSION);
+
+                Log.i(TAG, "Dirty Contact: " + Long.toString(rawContactId));
+                Log.i(TAG, "Contact Version: " + Long.toString(version));
+
+                if (isDeleted) {
+                    Log.i(TAG, "Contact is marked for deletion");
+                    RawContact rawContact = RawContact.createDeletedContact(rawContactId,
+                            serverContactId);
+                    dirtyContacts.add(rawContact);
+                } else if (isDirty) {
+                    RawContact rawContact = getRawContact(context, rawContactId);
+                    Log.i(TAG, "Contact Name: " + rawContact.getBestName());
+                    dirtyContacts.add(rawContact);
+                }
+            }
+
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return dirtyContacts;
+    }
+
+    /**
+     * Update the status messages for a list of users.  This is typically called
+     * for contacts we've just added to the system, since we can't monkey with
+     * the contact's status until they have a profileId.
+     *
+     * @param context The context of Authenticator Activity
+     * @param rawContacts The list of users we want to update
+     */
+    public static void updateStatusMessages(Context context, List<RawContact> rawContacts) {
         final ContentResolver resolver = context.getContentResolver();
         final BatchOperation batchOperation = new BatchOperation(context, resolver);
-        for (final User.Status status : list) {
-            // Look up the user's sample SyncAdapter data row
-            final long userId = status.getUserId();
-            final long profileId = lookupProfile(resolver, userId);
-            // Insert the activity into the stream
-            if (profileId > 0) {
-                values.put(StatusUpdates.DATA_ID, profileId);
-                values.put(StatusUpdates.STATUS, status.getStatus());
-                values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_CUSTOM);
-                values.put(StatusUpdates.CUSTOM_PROTOCOL, CUSTOM_IM_PROTOCOL);
-                values.put(StatusUpdates.IM_ACCOUNT, username);
-                values.put(StatusUpdates.IM_HANDLE, status.getUserId());
-                values.put(StatusUpdates.STATUS_RES_PACKAGE, context.getPackageName());
-                values.put(StatusUpdates.STATUS_ICON, R.drawable.icon);
-                values.put(StatusUpdates.STATUS_LABEL, R.string.label);
-                batchOperation.add(ContactOperations.newInsertCpo(StatusUpdates.CONTENT_URI, true)
-                    .withValues(values).build());
-                // A sync adapter should batch operations on multiple contacts,
-                // because it will make a dramatic performance difference.
-                if (batchOperation.size() >= 50) {
-                    batchOperation.execute();
-                }
+        for (RawContact rawContact : rawContacts) {
+            updateContactStatus(context, rawContact, batchOperation);
+        }
+        batchOperation.execute();
+    }
+
+    /**
+     * After we've finished up a sync operation, we want to clean up the sync-state
+     * so that we're ready for the next time.  This involves clearing out the 'dirty'
+     * flag on the synced contacts - but we also have to finish the DELETE operation
+     * on deleted contacts.  When the user initially deletes them on the client, they're
+     * marked for deletion - but they're not actually deleted until we delete them
+     * again, and include the ContactsContract.CALLER_IS_SYNCADAPTER parameter to
+     * tell the contacts provider that we're really ready to let go of this contact.
+     *
+     * @param context The context of Authenticator Activity
+     * @param dirtyContacts The list of contacts that we're cleaning up
+     */
+    public static void clearSyncFlags(Context context, List<RawContact> dirtyContacts) {
+        Log.i(TAG, "*** Clearing Sync-related Flags");
+        final ContentResolver resolver = context.getContentResolver();
+        final BatchOperation batchOperation = new BatchOperation(context, resolver);
+        for (RawContact rawContact : dirtyContacts) {
+            if (rawContact.isDeleted()) {
+                Log.i(TAG, "Deleting contact: " + Long.toString(rawContact.getRawContactId()));
+                deleteContact(context, rawContact.getRawContactId(), batchOperation);
+            } else if (rawContact.isDirty()) {
+                Log.i(TAG, "Clearing dirty flag for: " + rawContact.getBestName());
+                clearDirtyFlag(context, rawContact.getRawContactId(), batchOperation);
             }
         }
         batchOperation.execute();
@@ -131,89 +227,312 @@
 
     /**
      * Adds a single contact to the platform contacts provider.
-     * 
+     * This can be used to respond to a new contact found as part
+     * of sync information returned from the server, or because a
+     * user added a new contact.
+     *
      * @param context the Authenticator Activity context
      * @param accountName the account the contact belongs to
-     * @param user the sample SyncAdapter User object
+     * @param rawContact the sample SyncAdapter User object
+     * @param inSync is the add part of a client-server sync?
+     * @param batchOperation allow us to batch together multiple operations
+     *        into a single provider call
      */
-    private static void addContact(Context context, String accountName, User user,
-        BatchOperation batchOperation) {
+    public static void addContact(Context context, String accountName, RawContact rawContact,
+        boolean inSync, BatchOperation batchOperation) {
 
         // Put the data in the contacts provider
-        final ContactOperations contactOp =
-            ContactOperations.createNewContact(context, user.getUserId(), accountName,
-                batchOperation);
-        contactOp.addName(user.getFirstName(), user.getLastName()).addEmail(user.getEmail())
-            .addPhone(user.getCellPhone(), Phone.TYPE_MOBILE).addPhone(user.getHomePhone(),
-                Phone.TYPE_OTHER).addProfileAction(user.getUserId());
+        final ContactOperations contactOp = ContactOperations.createNewContact(
+                context, rawContact.getServerContactId(), accountName, inSync, batchOperation);
+
+        contactOp.addName(rawContact.getFullName(), rawContact.getFirstName(),
+                rawContact.getLastName())
+                .addEmail(rawContact.getEmail())
+                .addPhone(rawContact.getCellPhone(), Phone.TYPE_MOBILE)
+                .addPhone(rawContact.getHomePhone(), Phone.TYPE_HOME)
+                .addPhone(rawContact.getOfficePhone(), Phone.TYPE_WORK)
+                .addAvatar(rawContact.getAvatarUrl());
+
+        // If we have a serverId, then go ahead and create our status profile.
+        // Otherwise skip it - and we'll create it after we sync-up to the
+        // server later on.
+        if (rawContact.getServerContactId() > 0) {
+            contactOp.addProfileAction(rawContact.getServerContactId());
+        }
     }
 
     /**
      * Updates a single contact to the platform contacts provider.
-     * 
+     * This method can be used to update a contact from a sync
+     * operation or as a result of a user editing a contact
+     * record.
+     *
+     * This operation is actually relatively complex.  We query
+     * the database to find all the rows of info that already
+     * exist for this Contact. For rows that exist (and thus we're
+     * modifying existing fields), we create an update operation
+     * to change that field.  But for fields we're adding, we create
+     * "add" operations to create new rows for those fields.
+     *
      * @param context the Authenticator Activity context
      * @param resolver the ContentResolver to use
-     * @param accountName the account the contact belongs to
-     * @param user the sample SyncAdapter contact object.
+     * @param rawContact the sample SyncAdapter contact object
+     * @param updateStatus should we update this user's status
+     * @param updateAvatar should we update this user's avatar image
+     * @param inSync is the update part of a client-server sync?
      * @param rawContactId the unique Id for this rawContact in contacts
      *        provider
+     * @param batchOperation allow us to batch together multiple operations
+     *        into a single provider call
      */
-    private static void updateContact(Context context, ContentResolver resolver,
-        String accountName, User user, long rawContactId, BatchOperation batchOperation) {
+    public static void updateContact(Context context, ContentResolver resolver,
+        RawContact rawContact, boolean updateServerId, boolean updateStatus, boolean updateAvatar,
+        boolean inSync, long rawContactId, BatchOperation batchOperation) {
 
-        Uri uri;
-        String cellPhone = null;
-        String otherPhone = null;
-        String email = null;
+        boolean existingCellPhone = false;
+        boolean existingHomePhone = false;
+        boolean existingWorkPhone = false;
+        boolean existingEmail = false;
+        boolean existingAvatar = false;
+
         final Cursor c =
-            resolver.query(Data.CONTENT_URI, DataQuery.PROJECTION, DataQuery.SELECTION,
+                resolver.query(DataQuery.CONTENT_URI, DataQuery.PROJECTION, DataQuery.SELECTION,
                 new String[] {String.valueOf(rawContactId)}, null);
         final ContactOperations contactOp =
-            ContactOperations.updateExistingContact(context, rawContactId, batchOperation);
+                ContactOperations.updateExistingContact(context, rawContactId,
+                inSync, batchOperation);
         try {
+            // Iterate over the existing rows of data, and update each one
+            // with the information we received from the server.
             while (c.moveToNext()) {
                 final long id = c.getLong(DataQuery.COLUMN_ID);
                 final String mimeType = c.getString(DataQuery.COLUMN_MIMETYPE);
-                uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
+                final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
                 if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
-                    final String lastName = c.getString(DataQuery.COLUMN_FAMILY_NAME);
-                    final String firstName = c.getString(DataQuery.COLUMN_GIVEN_NAME);
-                    contactOp.updateName(uri, firstName, lastName, user.getFirstName(), user
-                        .getLastName());
+                    contactOp.updateName(uri,
+                            c.getString(DataQuery.COLUMN_GIVEN_NAME),
+                            c.getString(DataQuery.COLUMN_FAMILY_NAME),
+                            c.getString(DataQuery.COLUMN_FULL_NAME),
+                            rawContact.getFirstName(),
+                            rawContact.getLastName(),
+                            rawContact.getFullName());
                 } else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
                     final int type = c.getInt(DataQuery.COLUMN_PHONE_TYPE);
                     if (type == Phone.TYPE_MOBILE) {
-                        cellPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
-                        contactOp.updatePhone(cellPhone, user.getCellPhone(), uri);
-                    } else if (type == Phone.TYPE_OTHER) {
-                        otherPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
-                        contactOp.updatePhone(otherPhone, user.getHomePhone(), uri);
+                        existingCellPhone = true;
+                        contactOp.updatePhone(c.getString(DataQuery.COLUMN_PHONE_NUMBER),
+                                rawContact.getCellPhone(), uri);
+                    } else if (type == Phone.TYPE_HOME) {
+                        existingHomePhone = true;
+                        contactOp.updatePhone(c.getString(DataQuery.COLUMN_PHONE_NUMBER),
+                                rawContact.getHomePhone(), uri);
+                    } else if (type == Phone.TYPE_WORK) {
+                        existingWorkPhone = true;
+                        contactOp.updatePhone(c.getString(DataQuery.COLUMN_PHONE_NUMBER),
+                                rawContact.getOfficePhone(), uri);
                     }
-                } else if (Data.MIMETYPE.equals(Email.CONTENT_ITEM_TYPE)) {
-                    email = c.getString(DataQuery.COLUMN_EMAIL_ADDRESS);
-                    contactOp.updateEmail(user.getEmail(), email, uri);
+                } else if (mimeType.equals(Email.CONTENT_ITEM_TYPE)) {
+                    existingEmail = true;
+                    contactOp.updateEmail(rawContact.getEmail(),
+                            c.getString(DataQuery.COLUMN_EMAIL_ADDRESS), uri);
+                } else if (mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
+                    existingAvatar = true;
+                    contactOp.updateAvatar(rawContact.getAvatarUrl(), uri);
                 }
             } // while
         } finally {
             c.close();
         }
+
         // Add the cell phone, if present and not updated above
-        if (cellPhone == null) {
-            contactOp.addPhone(user.getCellPhone(), Phone.TYPE_MOBILE);
+        if (!existingCellPhone) {
+            contactOp.addPhone(rawContact.getCellPhone(), Phone.TYPE_MOBILE);
         }
-        // Add the other phone, if present and not updated above
-        if (otherPhone == null) {
-            contactOp.addPhone(user.getHomePhone(), Phone.TYPE_OTHER);
+        // Add the home phone, if present and not updated above
+        if (!existingHomePhone) {
+            contactOp.addPhone(rawContact.getHomePhone(), Phone.TYPE_HOME);
+        }
+
+        // Add the work phone, if present and not updated above
+        if (!existingWorkPhone) {
+            contactOp.addPhone(rawContact.getOfficePhone(), Phone.TYPE_WORK);
         }
         // Add the email address, if present and not updated above
-        if (email == null) {
-            contactOp.addEmail(user.getEmail());
+        if (!existingEmail) {
+            contactOp.addEmail(rawContact.getEmail());
+        }
+        // Add the avatar if we didn't update the existing avatar
+        if (!existingAvatar) {
+            contactOp.addAvatar(rawContact.getAvatarUrl());
+        }
+
+        // If we need to update the serverId of the contact record, take
+        // care of that.  This will happen if the contact is created on the
+        // client, and then synced to the server. When we get the updated
+        // record back from the server, we can set the SOURCE_ID property
+        // on the contact, so we can (in the future) lookup contacts by
+        // the serverId.
+        if (updateServerId) {
+            Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+            contactOp.updateServerId(rawContact.getServerContactId(), uri);
+        }
+
+        // If we don't have a status profile, then create one.  This could
+        // happen for contacts that were created on the client - we don't
+        // create the status profile until after the first sync...
+        final long serverId = rawContact.getServerContactId();
+        final long profileId = lookupProfile(resolver, serverId);
+        if (profileId <= 0) {
+            contactOp.addProfileAction(serverId);
         }
     }
 
     /**
-     * Deletes a contact from the platform contacts provider.
-     * 
+     * When we first add a sync adapter to the system, the contacts from that
+     * sync adapter will be hidden unless they're merged/grouped with an existing
+     * contact.  But typically we want to actually show those contacts, so we
+     * need to mess with the Settings table to get them to show up.
+     *
+     * @param context the Authenticator Activity context
+     * @param account the Account who's visibility we're changing
+     * @param visible true if we want the contacts visible, false for hidden
+     */
+    public static void setAccountContactsVisibility(Context context, Account account,
+            boolean visible) {
+        ContentValues values = new ContentValues();
+        values.put(RawContacts.ACCOUNT_NAME, account.name);
+        values.put(RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
+        values.put(Settings.UNGROUPED_VISIBLE, visible ? 1 : 0);
+
+        context.getContentResolver().insert(Settings.CONTENT_URI, values);
+    }
+
+    /**
+     * Return a User object with data extracted from a contact stored
+     * in the local contacts database.
+     *
+     * Because a contact is actually stored over several rows in the
+     * database, our query will return those multiple rows of information.
+     * We then iterate over the rows and build the User structure from
+     * what we find.
+     *
+     * @param context the Authenticator Activity context
+     * @param rawContactId the unique ID for the local contact
+     * @return a User object containing info on that contact
+     */
+    private static RawContact getRawContact(Context context, long rawContactId) {
+        String firstName = null;
+        String lastName = null;
+        String fullName = null;
+        String cellPhone = null;
+        String homePhone = null;
+        String workPhone = null;
+        String email = null;
+        long serverId = -1;
+
+        final ContentResolver resolver = context.getContentResolver();
+        final Cursor c =
+            resolver.query(DataQuery.CONTENT_URI, DataQuery.PROJECTION, DataQuery.SELECTION,
+                new String[] {String.valueOf(rawContactId)}, null);
+        try {
+            while (c.moveToNext()) {
+                final long id = c.getLong(DataQuery.COLUMN_ID);
+                final String mimeType = c.getString(DataQuery.COLUMN_MIMETYPE);
+                final long tempServerId = c.getLong(DataQuery.COLUMN_SERVER_ID);
+                if (tempServerId > 0) {
+                    serverId = tempServerId;
+                }
+                final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
+                if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
+                    lastName = c.getString(DataQuery.COLUMN_FAMILY_NAME);
+                    firstName = c.getString(DataQuery.COLUMN_GIVEN_NAME);
+                    fullName = c.getString(DataQuery.COLUMN_FULL_NAME);
+                } else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
+                    final int type = c.getInt(DataQuery.COLUMN_PHONE_TYPE);
+                    if (type == Phone.TYPE_MOBILE) {
+                        cellPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
+                    } else if (type == Phone.TYPE_HOME) {
+                        homePhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
+                    } else if (type == Phone.TYPE_WORK) {
+                        workPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
+                    }
+                } else if (mimeType.equals(Email.CONTENT_ITEM_TYPE)) {
+                    email = c.getString(DataQuery.COLUMN_EMAIL_ADDRESS);
+                }
+            } // while
+        } finally {
+            c.close();
+        }
+
+        // Now that we've extracted all the information we care about,
+        // create the actual User object.
+        RawContact rawContact = RawContact.create(fullName, firstName, lastName, cellPhone,
+                workPhone, homePhone, email, null, false, rawContactId, serverId);
+
+        return rawContact;
+    }
+
+    /**
+     * Update the status message associated with the specified user.  The status
+     * message would be something that is likely to be used by IM or social
+     * networking sync providers, and less by a straightforward contact provider.
+     * But it's a useful demo to see how it's done.
+     *
+     * @param context the Authenticator Activity context
+     * @param rawContact the contact who's status we should update
+     * @param batchOperation allow us to batch together multiple operations
+     */
+    private static void updateContactStatus(Context context, RawContact rawContact,
+            BatchOperation batchOperation) {
+        final ContentValues values = new ContentValues();
+        final ContentResolver resolver = context.getContentResolver();
+
+        final long userId = rawContact.getServerContactId();
+        final String username = rawContact.getUserName();
+        final String status = rawContact.getStatus();
+
+        // Look up the user's sample SyncAdapter data row
+        final long profileId = lookupProfile(resolver, userId);
+
+        // Insert the activity into the stream
+        if (profileId > 0) {
+            values.put(StatusUpdates.DATA_ID, profileId);
+            values.put(StatusUpdates.STATUS, status);
+            values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_CUSTOM);
+            values.put(StatusUpdates.CUSTOM_PROTOCOL, CUSTOM_IM_PROTOCOL);
+            values.put(StatusUpdates.IM_ACCOUNT, username);
+            values.put(StatusUpdates.IM_HANDLE, userId);
+            values.put(StatusUpdates.STATUS_RES_PACKAGE, context.getPackageName());
+            values.put(StatusUpdates.STATUS_ICON, R.drawable.icon);
+            values.put(StatusUpdates.STATUS_LABEL, R.string.label);
+            batchOperation.add(ContactOperations.newInsertCpo(StatusUpdates.CONTENT_URI,
+                    false, true).withValues(values).build());
+        }
+    }
+
+    /**
+     * Clear the local system 'dirty' flag for a contact.
+     *
+     * @param context the Authenticator Activity context
+     * @param rawContactId the id of the contact update
+     * @param batchOperation allow us to batch together multiple operations
+     */
+    private static void clearDirtyFlag(Context context, long rawContactId,
+        BatchOperation batchOperation) {
+        final ContactOperations contactOp =
+                ContactOperations.updateExistingContact(context, rawContactId, true,
+                batchOperation);
+
+        final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+        contactOp.updateDirtyFlag(false, uri);
+    }
+
+     /**
+     * Deletes a contact from the platform contacts provider. This method is used
+     * both for contacts that were deleted locally and then that deletion was synced
+     * to the server, and for contacts that were deleted on the server and the
+     * deletion was synced to the client.
+     *
      * @param context the Authenticator Activity context
      * @param rawContactId the unique Id for this rawContact in contacts
      *        provider
@@ -222,39 +541,43 @@
         BatchOperation batchOperation) {
 
         batchOperation.add(ContactOperations.newDeleteCpo(
-            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), true).build());
+                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
+                true, true).build());
     }
 
     /**
      * Returns the RawContact id for a sample SyncAdapter contact, or 0 if the
      * sample SyncAdapter user isn't found.
-     * 
-     * @param context the Authenticator Activity context
-     * @param userId the sample SyncAdapter user ID to lookup
+     *
+     * @param resolver the content resolver to use
+     * @param serverContactId the sample SyncAdapter user ID to lookup
      * @return the RawContact id, or 0 if not found
      */
-    private static long lookupRawContact(ContentResolver resolver, long userId) {
+    private static long lookupRawContact(ContentResolver resolver, long serverContactId) {
 
-        long authorId = 0;
-        final Cursor c =
-            resolver.query(RawContacts.CONTENT_URI, UserIdQuery.PROJECTION, UserIdQuery.SELECTION,
-                new String[] {String.valueOf(userId)}, null);
+        long rawContactId = 0;
+        final Cursor c = resolver.query(
+                UserIdQuery.CONTENT_URI,
+                UserIdQuery.PROJECTION,
+                UserIdQuery.SELECTION,
+                new String[] {String.valueOf(serverContactId)},
+                null);
         try {
-            if (c.moveToFirst()) {
-                authorId = c.getLong(UserIdQuery.COLUMN_ID);
+            if ((c != null) && c.moveToFirst()) {
+                rawContactId = c.getLong(UserIdQuery.COLUMN_RAW_CONTACT_ID);
             }
         } finally {
             if (c != null) {
                 c.close();
             }
         }
-        return authorId;
+        return rawContactId;
     }
 
     /**
      * Returns the Data id for a sample SyncAdapter contact's profile row, or 0
      * if the sample SyncAdapter user isn't found.
-     * 
+     *
      * @param resolver a content resolver
      * @param userId the sample SyncAdapter user ID to lookup
      * @return the profile Data row id, or 0 if not found
@@ -266,7 +589,7 @@
             resolver.query(Data.CONTENT_URI, ProfileQuery.PROJECTION, ProfileQuery.SELECTION,
                 new String[] {String.valueOf(userId)}, null);
         try {
-            if (c != null && c.moveToFirst()) {
+            if ((c != null) && c.moveToFirst()) {
                 profileId = c.getLong(ProfileQuery.COLUMN_ID);
             }
         } finally {
@@ -277,6 +600,46 @@
         return profileId;
     }
 
+    final public static class EditorQuery {
+
+        private EditorQuery() {
+        }
+
+        public static final String[] PROJECTION = new String[] {
+            RawContacts.ACCOUNT_NAME,
+            Data._ID,
+            RawContacts.Entity.DATA_ID,
+            Data.MIMETYPE,
+            Data.DATA1,
+            Data.DATA2,
+            Data.DATA3,
+            Data.DATA15,
+            Data.SYNC1
+            };
+
+        public static final int COLUMN_ACCOUNT_NAME = 0;
+        public static final int COLUMN_RAW_CONTACT_ID = 1;
+        public static final int COLUMN_DATA_ID = 2;
+        public static final int COLUMN_MIMETYPE = 3;
+        public static final int COLUMN_DATA1 = 4;
+        public static final int COLUMN_DATA2 = 5;
+        public static final int COLUMN_DATA3 = 6;
+        public static final int COLUMN_DATA15 = 7;
+        public static final int COLUMN_SYNC1 = 8;
+
+        public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1;
+        public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2;
+        public static final int COLUMN_EMAIL_ADDRESS = COLUMN_DATA1;
+        public static final int COLUMN_EMAIL_TYPE = COLUMN_DATA2;
+        public static final int COLUMN_FULL_NAME = COLUMN_DATA1;
+        public static final int COLUMN_GIVEN_NAME = COLUMN_DATA2;
+        public static final int COLUMN_FAMILY_NAME = COLUMN_DATA3;
+        public static final int COLUMN_AVATAR_IMAGE = COLUMN_DATA15;
+        public static final int COLUMN_SYNC_DIRTY = COLUMN_SYNC1;
+
+        public static final String SELECTION = Data.RAW_CONTACT_ID + "=?";
+    }
+
     /**
      * Constants for a query to find a contact given a sample SyncAdapter user
      * ID.
@@ -304,9 +667,15 @@
         private UserIdQuery() {
         }
 
-        public final static String[] PROJECTION = new String[] {RawContacts._ID};
+        public final static String[] PROJECTION = new String[] {
+            RawContacts._ID,
+            RawContacts.CONTACT_ID
+            };
 
-        public final static int COLUMN_ID = 0;
+        public final static int COLUMN_RAW_CONTACT_ID = 0;
+        public final static int COLUMN_LINKED_CONTACT_ID = 1;
+
+        public final static Uri CONTENT_URI = RawContacts.CONTENT_URI;
 
         public static final String SELECTION =
             RawContacts.ACCOUNT_TYPE + "='" + Constants.ACCOUNT_TYPE + "' AND "
@@ -314,6 +683,40 @@
     }
 
     /**
+     * Constants for a query to find SampleSyncAdapter contacts that are
+     * in need of syncing to the server. This should cover new, edited,
+     * and deleted contacts.
+     */
+    final private static class DirtyQuery {
+
+        private DirtyQuery() {
+        }
+
+        public final static String[] PROJECTION = new String[] {
+            RawContacts._ID,
+            RawContacts.SOURCE_ID,
+            RawContacts.DIRTY,
+            RawContacts.DELETED,
+            RawContacts.VERSION
+            };
+
+        public final static int COLUMN_RAW_CONTACT_ID = 0;
+        public final static int COLUMN_SERVER_ID = 1;
+        public final static int COLUMN_DIRTY = 2;
+        public final static int COLUMN_DELETED = 3;
+        public final static int COLUMN_VERSION = 4;
+
+        public static final Uri CONTENT_URI = RawContacts.CONTENT_URI.buildUpon()
+            .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+            .build();
+
+        public static final String SELECTION =
+            RawContacts.DIRTY + "=1 AND "
+                + RawContacts.ACCOUNT_TYPE + "='" + Constants.ACCOUNT_TYPE + "' AND "
+                + RawContacts.ACCOUNT_NAME + "=?";
+    }
+
+    /**
      * Constants for a query to get contact data for a given rawContactId
      */
     final private static class DataQuery {
@@ -322,29 +725,29 @@
         }
 
         public static final String[] PROJECTION =
-            new String[] {Data._ID, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3,};
+            new String[] {Data._ID, RawContacts.SOURCE_ID, Data.MIMETYPE, Data.DATA1,
+            Data.DATA2, Data.DATA3, Data.DATA15, Data.SYNC1};
 
         public static final int COLUMN_ID = 0;
+        public static final int COLUMN_SERVER_ID = 1;
+        public static final int COLUMN_MIMETYPE = 2;
+        public static final int COLUMN_DATA1 = 3;
+        public static final int COLUMN_DATA2 = 4;
+        public static final int COLUMN_DATA3 = 5;
+        public static final int COLUMN_DATA15 = 6;
+        public static final int COLUMN_SYNC1 = 7;
 
-        public static final int COLUMN_MIMETYPE = 1;
-
-        public static final int COLUMN_DATA1 = 2;
-
-        public static final int COLUMN_DATA2 = 3;
-
-        public static final int COLUMN_DATA3 = 4;
+        public static final Uri CONTENT_URI = Data.CONTENT_URI;
 
         public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1;
-
         public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2;
-
         public static final int COLUMN_EMAIL_ADDRESS = COLUMN_DATA1;
-
         public static final int COLUMN_EMAIL_TYPE = COLUMN_DATA2;
-
+        public static final int COLUMN_FULL_NAME = COLUMN_DATA1;
         public static final int COLUMN_GIVEN_NAME = COLUMN_DATA2;
-
         public static final int COLUMN_FAMILY_NAME = COLUMN_DATA3;
+        public static final int COLUMN_AVATAR_IMAGE = COLUMN_DATA15;
+        public static final int COLUMN_SYNC_DIRTY = COLUMN_SYNC1;
 
         public static final String SELECTION = Data.RAW_CONTACT_ID + "=?";
     }
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/ContactOperations.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/ContactOperations.java
index db01f48..cb8e97b 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/ContactOperations.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/ContactOperations.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -15,115 +15,134 @@
  */
 package com.example.android.samplesync.platform;
 
+import com.example.android.samplesync.Constants;
+import com.example.android.samplesync.R;
+import com.example.android.samplesync.client.NetworkUtilities;
+
 import android.content.ContentProviderOperation;
 import android.content.ContentValues;
 import android.content.Context;
 import android.net.Uri;
 import android.provider.ContactsContract;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
-import android.util.Log;
-
-import com.example.android.samplesync.Constants;
-import com.example.android.samplesync.R;
 
 /**
  * Helper class for storing data in the platform content providers.
  */
 public class ContactOperations {
-
     private final ContentValues mValues;
-
-    private ContentProviderOperation.Builder mBuilder;
-
     private final BatchOperation mBatchOperation;
-
     private final Context mContext;
-
-    private boolean mYield;
-
+    private boolean mIsSyncOperation;
     private long mRawContactId;
-
     private int mBackReference;
-
     private boolean mIsNewContact;
 
     /**
+     * Since we're sending a lot of contact provider operations in a single
+     * batched operation, we want to make sure that we "yield" periodically
+     * so that the Contact Provider can write changes to the DB, and can
+     * open a new transaction.  This prevents ANR (application not responding)
+     * errors.  The recommended time to specify that a yield is permitted is
+     * with the first operation on a particular contact.  So if we're updating
+     * multiple fields for a single contact, we make sure that we call
+     * withYieldAllowed(true) on the first field that we update. We use
+     * mIsYieldAllowed to keep track of what value we should pass to
+     * withYieldAllowed().
+     */
+    private boolean mIsYieldAllowed;
+
+    /**
      * Returns an instance of ContactOperations instance for adding new contact
      * to the platform contacts provider.
-     * 
+     *
      * @param context the Authenticator Activity context
      * @param userId the userId of the sample SyncAdapter user object
-     * @param accountName the username of the current login
+     * @param accountName the username for the SyncAdapter account
+     * @param isSyncOperation are we executing this as part of a sync operation?
      * @return instance of ContactOperations
      */
-    public static ContactOperations createNewContact(Context context, int userId,
-        String accountName, BatchOperation batchOperation) {
-
-        return new ContactOperations(context, userId, accountName, batchOperation);
+    public static ContactOperations createNewContact(Context context, long userId,
+            String accountName, boolean isSyncOperation, BatchOperation batchOperation) {
+        return new ContactOperations(context, userId, accountName, isSyncOperation, batchOperation);
     }
 
     /**
      * Returns an instance of ContactOperations for updating existing contact in
      * the platform contacts provider.
-     * 
+     *
      * @param context the Authenticator Activity context
      * @param rawContactId the unique Id of the existing rawContact
+     * @param isSyncOperation are we executing this as part of a sync operation?
      * @return instance of ContactOperations
      */
     public static ContactOperations updateExistingContact(Context context, long rawContactId,
-        BatchOperation batchOperation) {
-
-        return new ContactOperations(context, rawContactId, batchOperation);
+            boolean isSyncOperation, BatchOperation batchOperation) {
+        return new ContactOperations(context, rawContactId, isSyncOperation, batchOperation);
     }
 
-    public ContactOperations(Context context, BatchOperation batchOperation) {
+    public ContactOperations(Context context, boolean isSyncOperation,
+            BatchOperation batchOperation) {
         mValues = new ContentValues();
-        mYield = true;
+        mIsYieldAllowed = true;
+        mIsSyncOperation = isSyncOperation;
         mContext = context;
         mBatchOperation = batchOperation;
     }
 
-    public ContactOperations(Context context, int userId, String accountName,
-        BatchOperation batchOperation) {
-
-        this(context, batchOperation);
+    public ContactOperations(Context context, long userId, String accountName,
+            boolean isSyncOperation, BatchOperation batchOperation) {
+        this(context, isSyncOperation, batchOperation);
         mBackReference = mBatchOperation.size();
         mIsNewContact = true;
         mValues.put(RawContacts.SOURCE_ID, userId);
         mValues.put(RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
         mValues.put(RawContacts.ACCOUNT_NAME, accountName);
-        mBuilder = newInsertCpo(RawContacts.CONTENT_URI, true).withValues(mValues);
-        mBatchOperation.add(mBuilder.build());
+        ContentProviderOperation.Builder builder =
+                newInsertCpo(RawContacts.CONTENT_URI, mIsSyncOperation, true).withValues(mValues);
+        mBatchOperation.add(builder.build());
     }
 
-    public ContactOperations(Context context, long rawContactId, BatchOperation batchOperation) {
-        this(context, batchOperation);
+    public ContactOperations(Context context, long rawContactId, boolean isSyncOperation,
+            BatchOperation batchOperation) {
+        this(context, isSyncOperation, batchOperation);
         mIsNewContact = false;
         mRawContactId = rawContactId;
     }
 
     /**
-     * Adds a contact name
-     * 
-     * @param name Name of contact
-     * @param nameType type of name: family name, given name, etc.
+     * Adds a contact name. We can take either a full name ("Bob Smith") or separated
+     * first-name and last-name ("Bob" and "Smith").
+     *
+     * @param fullName The full name of the contact - typically from an edit form
+     *      Can be null if firstName/lastName are specified.
+     * @param firstName The first name of the contact - can be null if fullName
+     *      is specified.
+     * @param lastName The last name of the contact - can be null if fullName
+     *      is specified.
      * @return instance of ContactOperations
      */
-    public ContactOperations addName(String firstName, String lastName) {
-
+    public ContactOperations addName(String fullName, String firstName, String lastName) {
         mValues.clear();
-        if (!TextUtils.isEmpty(firstName)) {
-            mValues.put(StructuredName.GIVEN_NAME, firstName);
+
+        if (!TextUtils.isEmpty(fullName)) {
+            mValues.put(StructuredName.DISPLAY_NAME, fullName);
             mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
-        }
-        if (!TextUtils.isEmpty(lastName)) {
-            mValues.put(StructuredName.FAMILY_NAME, lastName);
-            mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        } else {
+            if (!TextUtils.isEmpty(firstName)) {
+                mValues.put(StructuredName.GIVEN_NAME, firstName);
+                mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+            }
+            if (!TextUtils.isEmpty(lastName)) {
+                mValues.put(StructuredName.FAMILY_NAME, lastName);
+                mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+            }
         }
         if (mValues.size() > 0) {
             addInsertOp();
@@ -133,8 +152,8 @@
 
     /**
      * Adds an email
-     * 
-     * @param new email for user
+     *
+     * @param the email address we're adding
      * @return instance of ContactOperations
      */
     public ContactOperations addEmail(String email) {
@@ -150,7 +169,7 @@
 
     /**
      * Adds a phone number
-     * 
+     *
      * @param phone new phone number for the contact
      * @param phoneType the type: cell, home, etc.
      * @return instance of ContactOperations
@@ -166,9 +185,22 @@
         return this;
     }
 
+    public ContactOperations addAvatar(String avatarUrl) {
+        if (avatarUrl != null) {
+            byte[] avatarBuffer = NetworkUtilities.downloadAvatar(avatarUrl);
+            if (avatarBuffer != null) {
+                mValues.clear();
+                mValues.put(Photo.PHOTO, avatarBuffer);
+                mValues.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
+                addInsertOp();
+            }
+        }
+        return this;
+    }
+
     /**
      * Adds a profile action
-     * 
+     *
      * @param userId the userId of the sample SyncAdapter user object
      * @return instance of ContactOperations
      */
@@ -187,8 +219,22 @@
     }
 
     /**
+     * Updates contact's serverId
+     *
+     * @param serverId the serverId for this contact
+     * @param uri Uri for the existing raw contact to be updated
+     * @return instance of ContactOperations
+     */
+    public ContactOperations updateServerId(long serverId, Uri uri) {
+        mValues.clear();
+        mValues.put(RawContacts.SOURCE_ID, serverId);
+        addUpdateOp(uri);
+        return this;
+    }
+
+    /**
      * Updates contact's email
-     * 
+     *
      * @param email email id of the sample SyncAdapter user
      * @param uri Uri for the existing raw contact to be updated
      * @return instance of ContactOperations
@@ -203,25 +249,38 @@
     }
 
     /**
-     * Updates contact's name
-     * 
-     * @param name Name of contact
-     * @param existingName Name of contact stored in provider
-     * @param nameType type of name: family name, given name, etc.
+     * Updates contact's name. The caller can either provide first-name
+     * and last-name fields or a full-name field.
+     *
      * @param uri Uri for the existing raw contact to be updated
+     * @param existingFirstName the first name stored in provider
+     * @param existingLastName the last name stored in provider
+     * @param existingFullName the full name stored in provider
+     * @param firstName the new first name to store
+     * @param lastName the new last name to store
+     * @param fullName the new full name to store
      * @return instance of ContactOperations
      */
-    public ContactOperations updateName(Uri uri, String existingFirstName, String existingLastName,
-        String firstName, String lastName) {
+    public ContactOperations updateName(Uri uri,
+        String existingFirstName,
+        String existingLastName,
+        String existingFullName,
+        String firstName,
+        String lastName,
+        String fullName) {
 
-        Log.i("ContactOperations", "ef=" + existingFirstName + "el=" + existingLastName + "f="
-            + firstName + "l=" + lastName);
         mValues.clear();
-        if (!TextUtils.equals(existingFirstName, firstName)) {
-            mValues.put(StructuredName.GIVEN_NAME, firstName);
-        }
-        if (!TextUtils.equals(existingLastName, lastName)) {
-            mValues.put(StructuredName.FAMILY_NAME, lastName);
+        if (TextUtils.isEmpty(fullName)) {
+            if (!TextUtils.equals(existingFirstName, firstName)) {
+                mValues.put(StructuredName.GIVEN_NAME, firstName);
+            }
+            if (!TextUtils.equals(existingLastName, lastName)) {
+                mValues.put(StructuredName.FAMILY_NAME, lastName);
+            }
+        } else {
+            if (!TextUtils.equals(existingFullName, fullName)) {
+                mValues.put(StructuredName.DISPLAY_NAME, fullName);
+            }
         }
         if (mValues.size() > 0) {
             addUpdateOp(uri);
@@ -229,9 +288,17 @@
         return this;
     }
 
+    public ContactOperations updateDirtyFlag(boolean isDirty, Uri uri) {
+        int isDirtyValue = isDirty ? 1 : 0;
+        mValues.clear();
+        mValues.put(RawContacts.DIRTY, isDirtyValue);
+        addUpdateOp(uri);
+        return this;
+    }
+
     /**
      * Updates contact's phone
-     * 
+     *
      * @param existingNumber phone number stored in contacts provider
      * @param phone new phone number for the contact
      * @param uri Uri for the existing raw contact to be updated
@@ -246,9 +313,22 @@
         return this;
     }
 
+    public ContactOperations updateAvatar(String avatarUrl, Uri uri) {
+        if (avatarUrl != null) {
+            byte[] avatarBuffer = NetworkUtilities.downloadAvatar(avatarUrl);
+            if (avatarBuffer != null) {
+                mValues.clear();
+                mValues.put(Photo.PHOTO, avatarBuffer);
+                mValues.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
+                addUpdateOp(uri);
+            }
+        }
+        return this;
+    }
+
     /**
      * Updates contact's profile action
-     * 
+     *
      * @param userId sample SyncAdapter user id
      * @param uri Uri for the existing raw contact to be updated
      * @return instance of ContactOperations
@@ -268,41 +348,62 @@
         if (!mIsNewContact) {
             mValues.put(Phone.RAW_CONTACT_ID, mRawContactId);
         }
-        mBuilder = newInsertCpo(addCallerIsSyncAdapterParameter(Data.CONTENT_URI), mYield);
-        mBuilder.withValues(mValues);
+        ContentProviderOperation.Builder builder =
+                newInsertCpo(Data.CONTENT_URI, mIsSyncOperation, mIsYieldAllowed);
+        builder.withValues(mValues);
         if (mIsNewContact) {
-            mBuilder.withValueBackReference(Data.RAW_CONTACT_ID, mBackReference);
+            builder.withValueBackReference(Data.RAW_CONTACT_ID, mBackReference);
         }
-        mYield = false;
-        mBatchOperation.add(mBuilder.build());
+        mIsYieldAllowed = false;
+        mBatchOperation.add(builder.build());
     }
 
     /**
      * Adds an update operation into the batch
      */
     private void addUpdateOp(Uri uri) {
-        mBuilder = newUpdateCpo(uri, mYield).withValues(mValues);
-        mYield = false;
-        mBatchOperation.add(mBuilder.build());
+        ContentProviderOperation.Builder builder =
+                newUpdateCpo(uri, mIsSyncOperation, mIsYieldAllowed).withValues(mValues);
+        mIsYieldAllowed = false;
+        mBatchOperation.add(builder.build());
     }
 
-    public static ContentProviderOperation.Builder newInsertCpo(Uri uri, boolean yield) {
-        return ContentProviderOperation.newInsert(addCallerIsSyncAdapterParameter(uri))
-            .withYieldAllowed(yield);
+    public static ContentProviderOperation.Builder newInsertCpo(Uri uri,
+            boolean isSyncOperation, boolean isYieldAllowed) {
+        return ContentProviderOperation
+                .newInsert(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
+                .withYieldAllowed(isYieldAllowed);
     }
 
-    public static ContentProviderOperation.Builder newUpdateCpo(Uri uri, boolean yield) {
-        return ContentProviderOperation.newUpdate(addCallerIsSyncAdapterParameter(uri))
-            .withYieldAllowed(yield);
+    public static ContentProviderOperation.Builder newUpdateCpo(Uri uri,
+            boolean isSyncOperation, boolean isYieldAllowed) {
+        return ContentProviderOperation
+                .newUpdate(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
+                .withYieldAllowed(isYieldAllowed);
     }
 
-    public static ContentProviderOperation.Builder newDeleteCpo(Uri uri, boolean yield) {
-        return ContentProviderOperation.newDelete(addCallerIsSyncAdapterParameter(uri))
-            .withYieldAllowed(yield);
+    public static ContentProviderOperation.Builder newDeleteCpo(Uri uri,
+            boolean isSyncOperation, boolean isYieldAllowed) {
+        return ContentProviderOperation
+                .newDelete(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
+                .withYieldAllowed(isYieldAllowed);
     }
 
-    private static Uri addCallerIsSyncAdapterParameter(Uri uri) {
-        return uri.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
-            .build();
+    private static Uri addCallerIsSyncAdapterParameter(Uri uri, boolean isSyncOperation) {
+        if (isSyncOperation) {
+            // If we're in the middle of a real sync-adapter operation, then go ahead
+            // and tell the Contacts provider that we're the sync adapter.  That
+            // gives us some special permissions - like the ability to really
+            // delete a contact, and the ability to clear the dirty flag.
+            //
+            // If we're not in the middle of a sync operation (for example, we just
+            // locally created/edited a new contact), then we don't want to use
+            // the special permissions, and the system will automagically mark
+            // the contact as 'dirty' for us!
+            return uri.buildUpon()
+                    .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+                    .build();
+        }
+        return uri;
     }
 }
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/SampleSyncAdapterColumns.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/SampleSyncAdapterColumns.java
index 7b60d5b..e8a99a4 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/SampleSyncAdapterColumns.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/platform/SampleSyncAdapterColumns.java
@@ -1,12 +1,12 @@
 /*
  * 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
diff --git a/samples/SampleSyncAdapter/src/com/example/android/samplesync/syncadapter/SyncAdapter.java b/samples/SampleSyncAdapter/src/com/example/android/samplesync/syncadapter/SyncAdapter.java
index 206189a..0ca8dee 100644
--- a/samples/SampleSyncAdapter/src/com/example/android/samplesync/syncadapter/SyncAdapter.java
+++ b/samples/SampleSyncAdapter/src/com/example/android/samplesync/syncadapter/SyncAdapter.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -24,12 +24,12 @@
 import android.content.Context;
 import android.content.SyncResult;
 import android.os.Bundle;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.example.android.samplesync.Constants;
 import com.example.android.samplesync.client.NetworkUtilities;
-import com.example.android.samplesync.client.User;
-import com.example.android.samplesync.client.User.Status;
+import com.example.android.samplesync.client.RawContact;
 import com.example.android.samplesync.platform.ContactManager;
 
 import org.apache.http.ParseException;
@@ -37,23 +37,27 @@
 import org.json.JSONException;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
 /**
  * SyncAdapter implementation for syncing sample SyncAdapter contacts to the
- * platform ContactOperations provider.
+ * platform ContactOperations provider.  This sample shows a basic 2-way
+ * sync between the client and a sample server.  It also contains an
+ * example of how to update the contacts' status messages, which
+ * would be useful for a messaging or social networking client.
  */
 public class SyncAdapter extends AbstractThreadedSyncAdapter {
 
     private static final String TAG = "SyncAdapter";
+    private static final String SYNC_MARKER_KEY = "com.example.android.samplesync.marker";
+    private static final boolean NOTIFY_AUTH_FAILURE = true;
 
     private final AccountManager mAccountManager;
 
     private final Context mContext;
 
-    private Date mLastUpdated;
-
     public SyncAdapter(Context context, boolean autoInitialize) {
         super(context, autoInitialize);
         mContext = context;
@@ -64,42 +68,101 @@
     public void onPerformSync(Account account, Bundle extras, String authority,
         ContentProviderClient provider, SyncResult syncResult) {
 
-        List<User> users;
-        List<Status> statuses;
-        String authtoken = null;
         try {
-            // use the account manager to request the credentials
-            authtoken =
-                mAccountManager
-                    .blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE, true /* notifyAuthFailure */);
-            // fetch updates from the sample service over the cloud
-            users = NetworkUtilities.fetchFriendUpdates(account, authtoken, mLastUpdated);
-            // update the last synced date.
-            mLastUpdated = new Date();
-            // update platform contacts.
+            // see if we already have a sync-state attached to this account. By handing
+            // This value to the server, we can just get the contacts that have
+            // been updated on the server-side since our last sync-up
+            long lastSyncMarker = getServerSyncMarker(account);
+
+            // By default, contacts from a 3rd party provider are hidden in the contacts
+            // list. So let's set the flag that causes them to be visible, so that users
+            // can actually see these contacts.
+            if (lastSyncMarker == 0) {
+                ContactManager.setAccountContactsVisibility(getContext(), account, true);
+            }
+
+            List<RawContact> dirtyContacts;
+            List<RawContact> updatedContacts;
+
+            // Use the account manager to request the AuthToken we'll need
+            // to talk to our sample server.  If we don't have an AuthToken
+            // yet, this could involve a round-trip to the server to request
+            // and AuthToken.
+            final String authtoken = mAccountManager.blockingGetAuthToken(account,
+                    Constants.AUTHTOKEN_TYPE, NOTIFY_AUTH_FAILURE);
+
+            // Find the local 'dirty' contacts that we need to tell the server about...
+            // Find the local users that need to be sync'd to the server...
+            dirtyContacts = ContactManager.getDirtyContacts(mContext, account);
+
+            // Send the dirty contacts to the server, and retrieve the server-side changes
+            updatedContacts = NetworkUtilities.syncContacts(account, authtoken,
+                    lastSyncMarker, dirtyContacts);
+
+            // Update the local contacts database with the changes. updateContacts()
+            // returns a syncState value that indicates the high-water-mark for
+            // the changes we received.
             Log.d(TAG, "Calling contactManager's sync contacts");
-            ContactManager.syncContacts(mContext, account.name, users);
-            // fetch and update status messages for all the synced users.
-            statuses = NetworkUtilities.fetchFriendStatuses(account, authtoken);
-            ContactManager.insertStatuses(mContext, account.name, statuses);
+            long newSyncState = ContactManager.updateContacts(mContext,
+                    account.name,
+                    updatedContacts,
+                    lastSyncMarker);
+
+            // This is a demo of how you can update IM-style status messages
+            // for contacts on the client. This probably won't apply to
+            // 2-way contact sync providers - it's more likely that one-way
+            // sync providers (IM clients, social networking apps, etc) would
+            // use this feature.
+            ContactManager.updateStatusMessages(mContext, updatedContacts);
+
+            // Save off the new sync marker. On our next sync, we only want to receive
+            // contacts that have changed since this sync...
+            setServerSyncMarker(account, newSyncState);
+
+            if (dirtyContacts.size() > 0) {
+                ContactManager.clearSyncFlags(mContext, dirtyContacts);
+            }
+
         } catch (final AuthenticatorException e) {
-            syncResult.stats.numParseExceptions++;
             Log.e(TAG, "AuthenticatorException", e);
+            syncResult.stats.numParseExceptions++;
         } catch (final OperationCanceledException e) {
             Log.e(TAG, "OperationCanceledExcetpion", e);
         } catch (final IOException e) {
             Log.e(TAG, "IOException", e);
             syncResult.stats.numIoExceptions++;
         } catch (final AuthenticationException e) {
-            mAccountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE, authtoken);
-            syncResult.stats.numAuthExceptions++;
             Log.e(TAG, "AuthenticationException", e);
+            syncResult.stats.numAuthExceptions++;
         } catch (final ParseException e) {
-            syncResult.stats.numParseExceptions++;
             Log.e(TAG, "ParseException", e);
-        } catch (final JSONException e) {
             syncResult.stats.numParseExceptions++;
+        } catch (final JSONException e) {
             Log.e(TAG, "JSONException", e);
+            syncResult.stats.numParseExceptions++;
         }
     }
+
+    /**
+     * This helper function fetches the last known high-water-mark
+     * we received from the server - or 0 if we've never synced.
+     * @param account the account we're syncing
+     * @return the change high-water-mark
+     */
+    private long getServerSyncMarker(Account account) {
+        String markerString = mAccountManager.getUserData(account, SYNC_MARKER_KEY);
+        if (!TextUtils.isEmpty(markerString)) {
+            return Long.parseLong(markerString);
+        }
+        return 0;
+    }
+
+    /**
+     * Save off the high-water-mark we receive back from the server.
+     * @param account The account we're syncing
+     * @param marker The high-water-mark we want to save.
+     */
+    private void setServerSyncMarker(Account account, long marker) {
+        mAccountManager.setUserData(account, SYNC_MARKER_KEY, Long.toString(marker));
+    }
 }
diff --git a/samples/SimpleJNI/jni/Android.mk b/samples/SimpleJNI/jni/Android.mk
index 528196b..a704fcf 100644
--- a/samples/SimpleJNI/jni/Android.mk
+++ b/samples/SimpleJNI/jni/Android.mk
@@ -44,11 +44,4 @@
 # No special compiler flags.
 LOCAL_CFLAGS +=
 
-# Don't prelink this library.  For more efficient code, you may want
-# to add this library to the prelink map and set this to true. However,
-# it's difficult to do this for applications that are not supplied as
-# part of a system image.
-
-LOCAL_PRELINK_MODULE := false
-
 include $(BUILD_SHARED_LIBRARY)
diff --git a/tools/a3dconvert/ColladaGeometry.h b/tools/a3dconvert/ColladaGeometry.h
index 5774997..a89fa69 100644
--- a/tools/a3dconvert/ColladaGeometry.h
+++ b/tools/a3dconvert/ColladaGeometry.h
@@ -27,8 +27,8 @@
     ColladaGeometry();
     bool init(domGeometryRef geometry);
 
-    Mesh *getMesh(Context *rsc) {
-        return mConvertedMesh.getMesh(rsc);
+    SimpleMesh *getMesh() {
+        return &mConvertedMesh;
     }
 
 private:
diff --git a/tools/a3dconvert/ColladaLoader.cpp b/tools/a3dconvert/ColladaLoader.cpp
index 8a74748..32d6250 100644
--- a/tools/a3dconvert/ColladaLoader.cpp
+++ b/tools/a3dconvert/ColladaLoader.cpp
@@ -17,8 +17,6 @@
 #include "ColladaLoader.h"
 #include "ColladaConditioner.h"
 #include "ColladaGeometry.h"
-#include "rsContext.h"
-#include "rsFileA3D.h"
 
 #include <dae.h>
 #include <dom/domCOLLADA.h>
@@ -64,22 +62,8 @@
     return convertSuceeded;
 }
 
-bool ColladaLoader::convertToA3D(const char *a3dFile) {
-    if (mGeometries.size() == 0) {
-        return false;
-    }
-    // Now write all this stuff out
-    Context rsc;
-    FileA3D file(&rsc);
-
-    for (uint32_t i = 0; i < mGeometries.size(); i++) {
-        Mesh *exportedMesh = mGeometries[i]->getMesh(&rsc);
-        file.appendToFile(exportedMesh);
-        delete exportedMesh;
-    }
-
-    file.writeFile(a3dFile);
-    return true;
+SimpleMesh *ColladaLoader::getMesh(uint32_t meshIndex) {
+    return mGeometries[meshIndex]->getMesh();
 }
 
 bool ColladaLoader::convertAllGeometry(domLibrary_geometries *allGeometry) {
diff --git a/tools/a3dconvert/ColladaLoader.h b/tools/a3dconvert/ColladaLoader.h
index aa66e7d..3d7bb7d 100644
--- a/tools/a3dconvert/ColladaLoader.h
+++ b/tools/a3dconvert/ColladaLoader.h
@@ -22,14 +22,20 @@
 class domLibrary_geometries;
 class domGeometry;
 class ColladaGeometry;
+class SimpleMesh;
 
-class ColladaLoader {
+#include "GeometryLoader.h"
+
+class ColladaLoader : public GeometryLoader {
 public:
     ColladaLoader();
-    ~ColladaLoader();
+    virtual ~ColladaLoader();
 
-    bool init(const char *colladaFile);
-    bool convertToA3D(const char *a3dFile);
+    virtual bool init(const char *colladaFile);
+    virtual SimpleMesh *getMesh(uint32_t meshIndex);
+    virtual uint32_t getNumMeshes() const {
+        return mGeometries.size();
+    }
 
 private:
     void clearGeometry();
diff --git a/tools/a3dconvert/GeometryLoader.h b/tools/a3dconvert/GeometryLoader.h
new file mode 100644
index 0000000..e0aca7a
--- /dev/null
+++ b/tools/a3dconvert/GeometryLoader.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef _GEOMETRY_LOADER_H_
+#define _GEOMETRY_LOADER_H_
+
+#include "SimpleMesh.h"
+
+class GeometryLoader {
+public:
+    virtual ~GeometryLoader() {
+    }
+    virtual bool init(const char *file) = 0;
+    virtual uint32_t getNumMeshes() const = 0;
+    virtual SimpleMesh *getMesh(uint32_t meshIndex) = 0;
+};
+
+#endif _GEOMETRY_LOADER_H_
diff --git a/tools/a3dconvert/ObjLoader.cpp b/tools/a3dconvert/ObjLoader.cpp
index 4465f4f..19b6e0b 100644
--- a/tools/a3dconvert/ObjLoader.cpp
+++ b/tools/a3dconvert/ObjLoader.cpp
@@ -228,24 +228,6 @@
     return true;
 }
 
-bool ObjLoader::convertToA3D(const char *a3dFile) {
-    if (!getNumMeshes()) {
-        return false;
-    }
-    // Now write all this stuff out
-    Context rsc;
-    FileA3D file(&rsc);
-
-    for (uint32_t i = 0; i < getNumMeshes(); i ++) {
-        Mesh *exportedMesh = getMesh(&rsc, i);
-        file.appendToFile(exportedMesh);
-        delete exportedMesh;
-    }
-
-    file.writeFile(a3dFile);
-    return true;
-}
-
 void ObjLoader::reIndexGeometry() {
     // We want to know where each vertex lands
     mVertexRemap.resize(mObjPositions.size() / mPositionsStride);
diff --git a/tools/a3dconvert/ObjLoader.h b/tools/a3dconvert/ObjLoader.h
index 210b35f..a3eee04 100644
--- a/tools/a3dconvert/ObjLoader.h
+++ b/tools/a3dconvert/ObjLoader.h
@@ -22,28 +22,28 @@
 #include <iostream>
 #include <fstream>
 
-#include "SimpleMesh.h"
-#include <rsContext.h>
+#include "GeometryLoader.h"
 
 using namespace android;
 using namespace android::renderscript;
 
 #define MAX_INDEX 0xffffffff
 
-class ObjLoader {
+class ObjLoader : public GeometryLoader {
 public:
     ObjLoader();
-    bool init(const char *objFile);
-    bool convertToA3D(const char *a3dFile);
-private:
-
-    Mesh *getMesh(Context *rsc, uint32_t meshIndex) {
-        return mMeshes[meshIndex].getMesh(rsc);
+    virtual ~ObjLoader() {
     }
-    uint32_t getNumMeshes() const {
+    virtual bool init(const char *objFile);
+
+    virtual SimpleMesh *getMesh(uint32_t meshIndex) {
+        return &mMeshes[meshIndex];
+    }
+    virtual uint32_t getNumMeshes() const {
         return mMeshes.size();
     }
 
+private:
     // .obj has a global list of vertex data
     std::vector<float> mObjPositions;
     std::vector<float> mObjNormals;
diff --git a/tools/a3dconvert/SimpleMesh.h b/tools/a3dconvert/SimpleMesh.h
index 57e1a7c..4efd838 100644
--- a/tools/a3dconvert/SimpleMesh.h
+++ b/tools/a3dconvert/SimpleMesh.h
@@ -19,6 +19,7 @@
 
 #include <rsContext.h>
 #include <rsMesh.h>
+#include <string>
 using namespace android;
 using namespace android::renderscript;
 
@@ -68,7 +69,7 @@
     }
 
     // Generates a renderscript mesh that could be used for a3d serialization
-    Mesh *getMesh(Context *rsc) {
+    Mesh *getRsMesh(Context *rsc) {
         if (mChannels.size() == 0) {
             return NULL;
         }
@@ -113,19 +114,12 @@
         // Now lets write index data
         const Element *indexElem = Element::create(rsc, RS_TYPE_UNSIGNED_16, RS_KIND_USER, false, 1);
 
-        Mesh *mesh = new Mesh(rsc);
+        Mesh *mesh = new Mesh(rsc, 1, mTriangleLists.size());
         mesh->setName(mName.c_str());
-        mesh->mVertexBufferCount = 1;
-        mesh->mVertexBuffers = new ObjectBaseRef<Allocation>[1];
-        mesh->mVertexBuffers[0].set(vertexAlloc);
-
-        mesh->mPrimitivesCount = mTriangleLists.size();
-        mesh->mPrimitives = new Mesh::Primitive_t *[mesh->mPrimitivesCount];
+        mesh->setVertexBuffer(vertexAlloc, 0);
 
         // load all primitives
-        for (uint32_t pCount = 0; pCount < mesh->mPrimitivesCount; pCount ++) {
-            Mesh::Primitive_t *prim = new Mesh::Primitive_t;
-            mesh->mPrimitives[pCount] = prim;
+        for (uint32_t pCount = 0; pCount < mTriangleLists.size(); pCount ++) {
 
             uint32_t numIndicies = mTriangleLists[pCount].size();
             Type *indexType = Type::getType(rsc, indexElem, numIndicies, 0, 0, false, false );
@@ -143,8 +137,7 @@
                 indexPtr[i * 3 + 2] = (uint16_t)indexList[i * 3 + 2];
             }
             indexAlloc->setName(mTriangleListNames[pCount].c_str());
-            prim->mIndexBuffer.set(indexAlloc);
-            prim->mPrimitive = RS_PRIMITIVE_TRIANGLE;
+            mesh->setPrimitive(indexAlloc, RS_PRIMITIVE_TRIANGLE, pCount);
         }
 
         return mesh;
diff --git a/tools/a3dconvert/a3dconvert.cpp b/tools/a3dconvert/a3dconvert.cpp
index 33f5733..893df10 100644
--- a/tools/a3dconvert/a3dconvert.cpp
+++ b/tools/a3dconvert/a3dconvert.cpp
@@ -19,6 +19,26 @@
 
 #include "ColladaLoader.h"
 #include "ObjLoader.h"
+#include "rsContext.h"
+#include "rsFileA3D.h"
+
+bool convertToA3D(GeometryLoader *loader, const char *a3dFile) {
+    if (!loader->getNumMeshes()) {
+        return false;
+    }
+    // Now write all this stuff out
+    Context rsc;
+    FileA3D file(&rsc);
+
+    for (uint32_t i = 0; i < loader->getNumMeshes(); i ++) {
+        Mesh *exportedMesh = loader->getMesh(i)->getRsMesh(&rsc);
+        file.appendToFile(exportedMesh);
+        delete exportedMesh;
+    }
+
+    file.writeFile(a3dFile);
+    return true;
+}
 
 int main (int argc, char * const argv[]) {
     const char *objExt = ".obj";
@@ -42,24 +62,24 @@
         return 1;
     }
 
+    GeometryLoader *loader = NULL;
     std::string ext = filename.substr(dotPos);
     if (ext == daeExt) {
-        ColladaLoader converter;
-        isSuccessful = converter.init(argv[1]);
-        if (isSuccessful) {
-            isSuccessful = converter.convertToA3D(argv[2]);
-        }
+        loader = new ColladaLoader();
     } else if (ext == objExt) {
-        ObjLoader objConv;
-        isSuccessful = objConv.init(argv[1]);
-        if (isSuccessful) {
-            isSuccessful = objConv.convertToA3D(argv[2]);
-        }
+        loader = new ObjLoader();
     } else {
         printf("Invalid input. Currently .obj and .dae (collada) input files are accepted\n");
         return 1;
     }
 
+    isSuccessful = loader->init(argv[1]);
+    if (isSuccessful) {
+        isSuccessful = convertToA3D(loader, argv[2]);
+    }
+
+    delete loader;
+
     if(isSuccessful) {
         printf("---All done---\n");
     } else {
diff --git a/tools/emulator/system/gps/Android.mk b/tools/emulator/system/gps/Android.mk
index 41bdc64..d41c5a7 100644
--- a/tools/emulator/system/gps/Android.mk
+++ b/tools/emulator/system/gps/Android.mk
@@ -23,15 +23,19 @@
 LOCAL_PATH := $(call my-dir)
 
 ifneq ($(TARGET_PRODUCT),sim)
-# HAL module implemenation, not prelinked and stored in
+# HAL module implemenation stored in
 # hw/<GPS_HARDWARE_MODULE_ID>.<ro.hardware>.so
 include $(CLEAR_VARS)
-LOCAL_PRELINK_MODULE := false
+
 LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
 LOCAL_CFLAGS += -DQEMU_HARDWARE
 LOCAL_SHARED_LIBRARIES := liblog libcutils libhardware
 LOCAL_SRC_FILES := gps_qemu.c
+ifeq ($(TARGET_PRODUCT),vbox_x86)
+LOCAL_MODULE := gps.vbox_x86
+else
 LOCAL_MODULE := gps.goldfish
+endif
 LOCAL_MODULE_TAGS := debug
 include $(BUILD_SHARED_LIBRARY)
 endif
diff --git a/tools/emulator/system/sensors/Android.mk b/tools/emulator/system/sensors/Android.mk
index 9b0e83d..4ae048b 100644
--- a/tools/emulator/system/sensors/Android.mk
+++ b/tools/emulator/system/sensors/Android.mk
@@ -23,14 +23,18 @@
 LOCAL_PATH := $(call my-dir)
 
 ifneq ($(TARGET_PRODUCT),sim)
-# HAL module implemenation, not prelinked and stored in
+# HAL module implemenation stored in
 # hw/<SENSORS_HARDWARE_MODULE_ID>.<ro.hardware>.so
 include $(CLEAR_VARS)
-LOCAL_PRELINK_MODULE := false
+
 LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
 LOCAL_SHARED_LIBRARIES := liblog libcutils
 LOCAL_SRC_FILES := sensors_qemu.c
+ifeq ($(TARGET_PRODUCT),vbox_x86)
+LOCAL_MODULE := sensors.vbox_x86
+else
 LOCAL_MODULE := sensors.goldfish
+endif
 LOCAL_MODULE_TAGS := debug
 include $(BUILD_SHARED_LIBRARY)
 endif
diff --git a/tools/glesv2debugger/.classpath b/tools/glesv2debugger/.classpath
new file mode 100755
index 0000000..4ba3585
--- /dev/null
+++ b/tools/glesv2debugger/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="lib" path="lib/sdklib.jar"/>
+	<classpathentry kind="lib" path="lib/liblzf.jar"/>
+	<classpathentry kind="lib" path="lib/host-libprotobuf-java-2.3.0-lite.jar"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="test"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/glesv2debugger/.gitignore b/tools/glesv2debugger/.gitignore
new file mode 100644
index 0000000..574bfc7
--- /dev/null
+++ b/tools/glesv2debugger/.gitignore
@@ -0,0 +1,4 @@
+lib/*.jar
+bin/*
+.settings/*
+
diff --git a/tools/glesv2debugger/.project b/tools/glesv2debugger/.project
new file mode 100755
index 0000000..0c974ca
--- /dev/null
+++ b/tools/glesv2debugger/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>GLESv2DebuggerClient</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/tools/glesv2debugger/META-INF/MANIFEST.MF b/tools/glesv2debugger/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..cf42bdb
--- /dev/null
+++ b/tools/glesv2debugger/META-INF/MANIFEST.MF
@@ -0,0 +1,15 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: GLESv2DebuggerClient
+Bundle-SymbolicName: GLESv2DebuggerClient; singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: com.android.glesv2debugger.Activator
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.junit
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-ClassPath: lib/host-libprotobuf-java-2.3.0-lite.jar,
+ lib/liblzf.jar,
+ lib/sdklib.jar,
+ .
diff --git a/tools/glesv2debugger/README.android b/tools/glesv2debugger/README.android
new file mode 100644
index 0000000..ae95463
--- /dev/null
+++ b/tools/glesv2debugger/README.android
@@ -0,0 +1,20 @@
+The following is taken from slide 3 & 4 of https://docs.google.com/a/google.com/present/edit?id=0AcZLV3icFYi0ZGZxa3NqZndfMGRqa2tiOXB4&authkey=CMfb8ukI&hl=en
+The spec doc is at https://docs.google.com/a/google.com/document/d/1dsASXCF9Suq8KOGcxwB2mAwgdRlrFj4QhMxkfaRJlA0/edit?hl=en&authkey=CPj4tKkO#
+
+
+Building and Running
+
+Debugger server is linked into EGL, code is in framework/base/opengl/libs/GLES2_dbg and already included in latest master builds, no action needed.
+Use development/tools/glesv2debugger/setup.sh to build and copy the jars: libprotobuf-java-2.3.0-lite, liblzf, sdklib into development/tools/glesv2debugger/lib
+Install Eclipse SDK for Eclipse: Eclipse->Help->Install New Software. Select "All Available Sites" in the "Work with:" drop down, then find "Eclipse SDK". (If Eclipse reports dependency conflicts, try install updates first)
+Debugger client is an Eclipse plug-in, code is at development/tools/glesv2debugger, built in Eclipse
+Optional: build glsl_compiler and copy to plug-in working directory; this is used for shader syntax check
+
+
+"Attaching" to a Process
+
+adb shell setprop debug.egl.debug_proc <process name> before running process. ie: com.example.android.apis
+EGL checks /proc/<proc_id>/cmdline for match during init and sets debug functions in eglMakeCurrent
+EGL will bind to socket and wait for incoming connection, so need to adb forward tcp:5039 tcp:5039. Port can be overridden by adb shell setprop debug.egl.debug_port <port>
+If create socket failed, EGL will try to open /data/local/tmp/dump.gles2dbg for write, and exit when 8MB is written. The relevant properties are ...debug_forceUseFile, ...debug_maxFileSize, and ...debug_filePath
+Now manually start the process on device; on host, open development/tools/glesv2debugger/.project and run/debug as Eclipse application, then Window->Show View->Other->Debug->OpenGL ES 2.0 Debugger, then Connect or Open File
diff --git a/tools/glesv2debugger/build.properties b/tools/glesv2debugger/build.properties
new file mode 100644
index 0000000..39d82d2
--- /dev/null
+++ b/tools/glesv2debugger/build.properties
@@ -0,0 +1,11 @@
+source.. = src/,\

+           test/

+output.. = bin/

+bin.includes = plugin.xml,\

+               META-INF/,\

+               .,\

+               icons/,\
+               contexts.xml,\
+               lib/host-libprotobuf-java-2.3.0-lite.jar,\
+               lib/liblzf.jar,\
+               lib/sdklib.jar
diff --git a/tools/glesv2debugger/contexts.xml b/tools/glesv2debugger/contexts.xml
new file mode 100644
index 0000000..02e26e4
--- /dev/null
+++ b/tools/glesv2debugger/contexts.xml
@@ -0,0 +1,12 @@
+<contexts>
+	<context id="viewer" title="Sample View">
+		<description>This is the context help for the sample view with a table viewer. It was generated by a PDE template.</description>
+		<topic href="/PLUGINS_ROOT/org.eclipse.platform.doc.isv/guide/ua_help_context.htm" label="Context-sensitive help">
+			<enablement>
+				<with variable="platform">
+	            	<test property="org.eclipse.core.runtime.isBundleInstalled" args="org.eclipse.platform.doc.isv"/>
+	     		</with>
+			</enablement>
+		</topic>
+	</context>
+</contexts>
diff --git a/tools/glesv2debugger/generate_GLEnum_java.py b/tools/glesv2debugger/generate_GLEnum_java.py
new file mode 100755
index 0000000..cf543c8
--- /dev/null
+++ b/tools/glesv2debugger/generate_GLEnum_java.py
@@ -0,0 +1,73 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+#
+# Copyright 2011, 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.
+#
+
+if __name__ == "__main__":
+    externs = []
+    lines = open("../../../frameworks/base/opengl/libs/enums.in").readlines()
+    output = open("src/com/android/glesv2debugger/GLEnum.java", "w")
+    i = 0
+    output.write(
+"""/*
+ ** Copyright 2011, 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.
+ */
+
+// auto generated by generate_GLEnum_java.py"
+
+package com.android.glesv2debugger;
+
+public enum GLEnum {
+""")
+    
+    index = 0
+    for line in lines:
+        value = line[line.find("(") + 1: line.find(",")]
+        name = line[line.find(",") + 1: line.find(")")]    
+        output.write("    %s(%s),\n" % (name, value))
+
+    output.write("""    ;
+
+    public final int value;
+    GLEnum(final int value) {
+        this.value = value;
+    }
+
+    private static final java.util.HashMap<Integer, GLEnum> reverseMap = new java.util.HashMap<Integer, GLEnum>();
+    static {
+        for (GLEnum e : GLEnum.values())
+        reverseMap.put(e.value, e);
+    }
+
+    public static GLEnum valueOf(final int value) {
+        return reverseMap.get(value);
+    }
+}""")
+
+
diff --git a/tools/glesv2debugger/generate_MessageFormatter_java.py b/tools/glesv2debugger/generate_MessageFormatter_java.py
new file mode 100755
index 0000000..dfbf2ea
--- /dev/null
+++ b/tools/glesv2debugger/generate_MessageFormatter_java.py
@@ -0,0 +1,281 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+#
+# Copyright 2011, 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.
+#
+
+import os
+import sys
+
+def RemoveAnnotation(line):
+    if line.find(":") >= 0:
+        annotation = line[line.find(":"): line.find(" ", line.find(":"))]
+        return line.replace(annotation, "*")
+    else:
+        return line
+        
+if __name__ == "__main__":
+    externs = []
+    lines = open("../../../frameworks/base/opengl/libs/GLES2_dbg/gl2_api_annotated.in").readlines()
+    output = open("src/com/android/glesv2debugger/MessageFormatter.java", "w")
+    
+    i = 0
+    output.write(
+"""/*
+ ** Copyright 2011, 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.
+ */
+
+// auto generated by generate_MessageFormatter_java.py"
+
+package com.android.glesv2debugger;
+
+import java.nio.ByteBuffer;
+
+public class MessageFormatter {
+
+    static String formatFloats(int count, final ByteBuffer data) {
+        if (data.remaining() == 0)
+            return "{}";
+        data.order(SampleView.targetByteOrder);
+        String ret = "{";
+        for (int i = 0; i < count; i++) {
+            ret += Float.intBitsToFloat(data.getInt());
+            if (i < count - 1)
+                ret += ", ";
+        }
+        return ret + "}";
+    }
+
+    static String formatInts(int count, final ByteBuffer data) {
+        if (data.remaining() == 0)
+            return "{}";
+        data.order(SampleView.targetByteOrder);
+        String ret = "{";
+        for (int i = 0; i < count; i++) {
+            ret += data.getInt();
+            if (i < count - 1)
+                ret += ", ";
+        }
+        return ret + "}";
+    }
+
+    static String formatUInts(int count, final ByteBuffer data) {
+        if (data.remaining() == 0)
+            return "{}";
+        data.order(SampleView.targetByteOrder);
+        String ret = "{";
+        for (int i = 0; i < count; i++) {
+            long bits = data.getInt() & 0xffffffff;
+            ret += bits;
+            if (i < count - 1)
+                ret += ", ";
+        }
+        return ret + "}";
+    }
+
+    static String formatMatrix(int columns, int count, final ByteBuffer data) {
+        if (data.remaining() == 0)
+            return "{}";
+        data.order(SampleView.targetByteOrder);
+        String ret = "{";
+        for (int i = 0; i < count; i++) {
+            ret += Float.intBitsToFloat(data.getInt());
+            if (i < count - 1)
+                ret += ", ";
+            if (i % columns == columns - 1)
+                ret += "\\n                                             ";
+        }
+        return ret + "}";
+    }
+
+    public static String format(final DebuggerMessage.Message msg,
+                                final boolean code) {
+        String str;
+        switch (msg.getFunction()) {
+""")
+    #in source code these turn into program_%d etc.
+    nameReplaces = ["program", "shader", "texture", "buffer", "framebuffer", "renderbuffer"]
+    for line in lines:
+        if line.find("API_ENTRY(") >= 0: # a function prototype
+            returnType = line[0: line.find(" API_ENTRY(")].replace("const ", "")
+            functionName = line[line.find("(") + 1: line.find(")")] #extract GL function name
+            parameterList = line[line.find(")(") + 2: line.find(") {")]
+
+            parameters = parameterList.split(',')
+            paramIndex = 0
+
+            formatString = "%s"
+            formatArgs = ""
+            if returnType != "void":
+                if returnType == "GLenum":
+                    formatArgs += '\
+                    (code ? "%s" : GLEnum.valueOf(msg.getRet()))\n' % (functionName)
+                elif returnType.find("*") >= 0:
+                    formatArgs += '\
+                    (code ? "%s" : "0x" + Integer.toHexString(msg.getRet()))\n' % (functionName)
+                else:
+                    formatArgs += '\
+                    (code ? "%s" : msg.getRet())\n' % (functionName)
+            else:
+                formatArgs += '\
+                    (code ? "%s" : "void")\n' % (functionName)
+
+            formatString += "("
+
+            if parameterList == "void":
+                parameters = []
+            inout = ""
+
+            paramNames = []
+
+            for parameter in parameters:
+                parameter = parameter.replace("const","")
+                parameter = parameter.strip()
+                paramType = parameter.split(' ')[0]
+                paramName = parameter.split(' ')[1]
+                annotation = ""
+
+                formatString += "%s%s"
+                formatArgs += '\
+                    , (code ? "/*%s*/ " : "%s=")\n' % (paramName, paramName)
+                if parameter.find(":") >= 0:
+                    assert inout == "" # only one parameter should be annotated
+                    inout = paramType.split(":")[2]
+                    annotation = paramType.split(":")[1]
+                    paramType = paramType.split(":")[0]
+                    count = 1
+                    countArg = ""
+                    if annotation.find("*") >= 0: # [1,n] * param
+                        count = int(annotation.split("*")[0])
+                        countArg = annotation.split("*")[1]
+                        assert countArg in paramNames
+                    elif annotation in paramNames:
+                        count = 1
+                        countArg = annotation
+                    elif annotation == "GLstring":
+                        annotation = annotation
+                    else:
+                        count = int(annotation)
+                    dataFormatter = ""
+                    if paramType == "GLfloat":
+                        dataFormatter = "formatFloats"
+                    elif paramType == "GLint":
+                        dataFormatter = "formatInts"
+                    elif paramType == "GLuint":
+                        dataFormatter = "formatUInts"
+                    elif annotation == "GLstring":
+                        assert paramType == "GLchar"
+                    elif paramType.find("void") >= 0:
+                        assert 1
+                    else:
+                        assert 0
+                    if functionName.find("Matrix") >= 0:
+                        columns = int(functionName[functionName.find("fv") - 1: functionName.find("fv")])
+                        assert columns * columns == count
+                        assert countArg != ""
+                        assert paramType == "GLfloat"
+                        formatArgs += '\
+                    , (code ? "(GLfloat [])" : "") + formatMatrix(%d, %d * msg.getArg%d(), msg.getData().asReadOnlyByteBuffer())' % (
+                        columns, count, paramNames.index(countArg))
+                    elif annotation == "GLstring":
+                        formatArgs += '\
+                    , (code ? "\\"" : "") + msg.getData().toStringUtf8() + (code ? "\\"" : "")'
+                    elif paramType.find("void") >= 0:
+                        formatArgs += '\
+                    , (code ? "arg%d" : "0x" + Integer.toHexString(msg.getArg%d()))' % (paramIndex, paramIndex)
+                    elif countArg == "":
+                        formatArgs += '\
+                    , (code ? "(%s [])" : "") + %s(%d, msg.getData().asReadOnlyByteBuffer())' % (
+                        paramType, dataFormatter, count)
+                    else:
+                        formatArgs += '\
+                    , (code ? "(%s [])" : "") +  %s(%d * msg.getArg%d(), msg.getData().asReadOnlyByteBuffer())' % (
+                        paramType, dataFormatter, count, paramNames.index(countArg))
+                else:
+                    if paramType == "GLfloat" or paramType == "GLclampf":
+                        formatArgs += "\
+                    , Float.intBitsToFloat(msg.getArg%d())" % (paramIndex)
+                    elif paramType == "GLenum": 
+                        formatArgs += "\
+                    , GLEnum.valueOf(msg.getArg%d())" % (paramIndex)
+                    elif paramType.find("*") >= 0:
+                        formatArgs += '\
+                    , (code ? "arg%d" : "0x" + Integer.toHexString(msg.getArg%d()))' % (paramIndex, paramIndex)
+                    elif paramName in nameReplaces:
+                        formatArgs += '\
+                    , (code ? "%s_" : "") + msg.getArg%d()' % (paramName, paramIndex)
+                    else:
+                        formatArgs += "\
+                    , msg.getArg%d()" % (paramIndex)
+                if paramIndex < len(parameters) - 1:
+                    formatString += ", "
+                    formatArgs += '\n'
+                paramNames.append(paramName)
+                paramIndex += 1  
+
+                
+            formatString += ")"
+             
+            output.write("            case %s:\n" % (functionName))
+            if line.find("*") >= 0 and (line.find("*") < line.find(":") or line.find("*") > line.rfind(":")):
+                sys.stderr.write(line)
+                output.write("                // FIXME: this function uses pointers, debugger may send data in msg.data\n")
+            output.write('\
+                str = String.format("%s",\n%s);\n\
+                break;\n' % (formatString, formatArgs))
+
+
+    output.write("""            default:
+                str = msg.toString();
+        }
+        return str;
+    }
+}""")
+
+'''    print """/*
+package GLESv2Debugger;
+
+public class MessageFormatterCustom {
+
+    public static String format(final DebuggerMessage.Message msg) {
+        String str;
+        switch (msg.getFunction()) {"""
+
+    for extern in externs:
+        print "        case %s" % (extern)
+        print "            // TODO:"
+
+print """        default:
+            str = msg.toString();
+        }
+        return str;
+    }
+}
+*/"""    '''
+        
+        
diff --git a/tools/glesv2debugger/generate_MessageParser_java.py b/tools/glesv2debugger/generate_MessageParser_java.py
new file mode 100755
index 0000000..b6e8282
--- /dev/null
+++ b/tools/glesv2debugger/generate_MessageParser_java.py
@@ -0,0 +1,304 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+#
+# Copyright 2011, 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.
+#
+
+import os
+import sys
+
+def RemoveAnnotation(line):
+    if line.find(":") >= 0:
+        annotation = line[line.find(":"): line.find(" ", line.find(":"))]
+        return line.replace(annotation, "*")
+    else:
+        return line
+
+if __name__ == "__main__":
+    externs = []
+    lines = open("../../../frameworks/base/opengl/libs/GLES2_dbg/gl2_api_annotated.in").readlines()
+    output = open("src/com/android/glesv2debugger/MessageParser.java", "w")
+
+    i = 0
+    output.write("""\
+/*
+ ** Copyright 2011, 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.
+ */
+
+// auto generated by generate_MessageParser_java.py,
+//  which also prints skeleton code for MessageParserEx.java
+
+package com.android.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.google.protobuf.ByteString;
+
+import java.nio.ByteBuffer;
+
+public abstract class MessageParser {
+
+    String args;
+
+    String[] getList()
+    {
+        String arg = args;
+        args = args.substring(args.lastIndexOf('}') + 1);
+        final int comma = args.indexOf(',');
+        if (comma >= 0)
+            args = args.substring(comma + 1).trim();
+        else
+            args = null;
+
+        final int comment = arg.indexOf('=');
+        if (comment >= 0)
+            arg = arg.substring(comment + 1);
+        arg = arg.trim();
+        assert arg.charAt(0) == '{';
+        arg = arg.substring(1, arg.lastIndexOf('}')).trim();
+        return arg.split("\\s*,\\s*");
+    }
+
+    ByteString parseFloats(int count) {
+        ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
+        String [] arg = getList();
+        for (int i = 0; i < count; i++)
+            buffer.putFloat(Float.parseFloat(arg[i].trim()));
+        buffer.rewind();
+        return ByteString.copyFrom(buffer);
+    }
+
+    ByteString parseInts(int count) {
+        ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
+        String [] arg = getList();
+        for (int i = 0; i < count; i++)
+            buffer.putInt(Integer.parseInt(arg[i].trim()));
+        buffer.rewind();
+        return ByteString.copyFrom(buffer);
+    }
+
+    ByteString parseUInts(int count) {
+        ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
+        String [] arg = getList();
+        for (int i = 0; i < count; i++)
+            buffer.putInt((int)(Long.parseLong(arg[i].trim()) & 0xffffffff));
+        buffer.rewind();
+        return ByteString.copyFrom(buffer);
+    }
+
+    ByteString parseMatrix(int columns, int count) {
+        return parseFloats(columns * columns * count);
+    }
+
+    ByteString parseString() {
+        // TODO: escape sequence and proper string literal
+        String arg = args.substring(args.indexOf('"') + 1, args.lastIndexOf('"'));
+        args = args.substring(args.lastIndexOf('"'));
+        int comma = args.indexOf(',');
+        if (comma >= 0)
+            args = args.substring(comma + 1).trim();
+        else
+            args = null;
+        return ByteString.copyFromUtf8(arg);
+    }
+
+    String getArgument()
+    {
+        final int comma = args.indexOf(',');
+        String arg = null;
+        if (comma >= 0)
+        {
+            arg = args.substring(0, comma);
+            args = args.substring(comma + 1);
+        }
+        else
+        {
+            arg = args;
+            args = null;
+        }
+        final int comment = arg.indexOf('=');
+        if (comment >= 0)
+            arg = arg.substring(comment + 1);
+        return arg.trim();
+    }
+
+    int parseArgument()
+    {
+        String arg = getArgument();
+        if (arg.startsWith("GL_"))
+            return GLEnum.valueOf(arg).value;
+        else if (arg.toLowerCase().startsWith("0x"))
+            return Integer.parseInt(arg.substring(2), 16);
+        else
+            return Integer.parseInt(arg);
+    }
+
+    int parseFloat()
+    {
+        String arg = getArgument();
+        return Float.floatToRawIntBits(Float.parseFloat(arg));
+    }
+
+    public void parse(final Message.Builder builder, String string) {
+        int lparen = string.indexOf("("), rparen = string.lastIndexOf(")");
+        String s = string.substring(0, lparen).trim();
+        args = string.substring(lparen + 1, rparen);
+        String[] t = s.split(" ");
+        Function function = Function.valueOf(t[t.length - 1]);
+        builder.setFunction(function);
+        switch (function) {
+""")
+
+    abstractParsers = ""
+
+    for line in lines:
+        if line.find("API_ENTRY(") >= 0: # a function prototype
+            returnType = line[0: line.find(" API_ENTRY(")].replace("const ", "")
+            functionName = line[line.find("(") + 1: line.find(")")] #extract GL function name
+            parameterList = line[line.find(")(") + 2: line.find(") {")]
+
+            parameters = parameterList.split(',')
+            paramIndex = 0
+
+            #if returnType != "void":
+            #else:
+
+            if parameterList == "void":
+                parameters = []
+            inout = ""
+
+            paramNames = []
+            abstract = False
+            argumentSetters = ""
+            output.write("\
+            case %s:\n" % (functionName))
+
+            for parameter in parameters:
+                parameter = parameter.replace("const","")
+                parameter = parameter.strip()
+                paramType = parameter.split(' ')[0]
+                paramName = parameter.split(' ')[1]
+                annotation = ""
+
+                argumentParser = ""
+
+                if parameter.find(":") >= 0:
+                    dataSetter = ""
+                    assert inout == "" # only one parameter should be annotated
+                    inout = paramType.split(":")[2]
+                    annotation = paramType.split(":")[1]
+                    paramType = paramType.split(":")[0]
+                    count = 1
+                    countArg = ""
+                    if annotation.find("*") >= 0: # [1,n] * param
+                        count = int(annotation.split("*")[0])
+                        countArg = annotation.split("*")[1]
+                        assert countArg in paramNames
+                    elif annotation in paramNames:
+                        count = 1
+                        countArg = annotation
+                    elif annotation == "GLstring":
+                        annotation = annotation
+                    else:
+                        count = int(annotation)
+
+                    if paramType == "GLfloat":
+                        argumentParser = "parseFloats"
+                    elif paramType == "GLint":
+                        argumentParser = "parseInts"
+                    elif paramType == "GLuint":
+                        argumentParser = "parseUInts"
+                    elif annotation == "GLstring":
+                        assert paramType == 'GLchar'
+                    elif paramType.find("void") >= 0:
+                        assert 1
+                    else:
+                        assert 0
+
+                    if functionName.find('Matrix') >= 0:
+                        columns = int(functionName[functionName.find("fv") - 1: functionName.find("fv")])
+                        assert columns * columns == count
+                        assert countArg != ""
+                        assert paramType == "GLfloat"
+                        dataSetter = "builder.setData(parseMatrix(%d, builder.getArg%d()));" % (
+                            columns, paramNames.index(countArg))
+                    elif annotation == "GLstring":
+                        dataSetter = "builder.setData(parseString());"
+                    elif paramType.find("void") >= 0:
+                        dataSetter = "// TODO"
+                        abstract = True
+                    elif countArg == "":
+                        dataSetter = "builder.setData(%s(%d));" % (argumentParser, count)
+                    else:
+                        dataSetter = "builder.setData(%s(%d * builder.getArg%d()));" % (
+                            argumentParser, count, paramNames.index(countArg))
+                    argumentSetters += "\
+                %s // %s %s\n" % (dataSetter, paramType, paramName)
+                else:
+                    if paramType == "GLfloat" or paramType == "GLclampf":
+                        argumentSetters += "\
+                builder.setArg%d(parseFloat()); // %s %s\n" % (
+                    paramIndex, paramType, paramName)
+                    elif paramType.find("*") >= 0:
+                        argumentSetters += "\
+                // TODO: %s %s\n" % (paramType, paramName)
+                        abstract = True
+                    else:
+                        argumentSetters += "\
+                builder.setArg%d(parseArgument()); // %s %s\n" % (
+                    paramIndex, paramType, paramName)
+                paramNames.append(paramName)
+                paramIndex += 1
+
+            if not abstract:
+                output.write("%s" % argumentSetters)
+            else:
+                output.write("\
+                parse_%s(builder);\n" % functionName)
+                abstractParsers += "\
+    abstract void parse_%s(Message.Builder builder);\n" % functionName
+                print """\
+    @Override
+    void parse_%s(Message.Builder builder) {
+%s    }
+""" % (functionName, argumentSetters) # print skeleton code for MessageParserEx
+
+            output.write("\
+                break;\n")
+    output.write("""\
+            default:
+                assert false;
+        }
+    }
+""")
+    output.write(abstractParsers)
+    output.write("\
+}""")
diff --git a/tools/glesv2debugger/icons/sample.gif b/tools/glesv2debugger/icons/sample.gif
new file mode 100644
index 0000000..34fb3c9
--- /dev/null
+++ b/tools/glesv2debugger/icons/sample.gif
Binary files differ
diff --git a/tools/glesv2debugger/plugin.xml b/tools/glesv2debugger/plugin.xml
new file mode 100644
index 0000000..f1512a5
--- /dev/null
+++ b/tools/glesv2debugger/plugin.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<?eclipse version="3.4"?>

+<plugin>

+

+   <extension

+         point="org.eclipse.ui.views">

+      <view

+            name="OpenGL ES 2.0 Debugger"

+            icon="icons/sample.gif"

+            category="org.eclipse.debug.ui"

+            class="com.android.glesv2debugger.SampleView"

+            id="glesv2debuggerclient.views.SampleView">

+      </view>

+   </extension>

+   <extension

+         point="org.eclipse.ui.perspectiveExtensions">

+      <perspectiveExtension

+            targetID="org.eclipse.jdt.ui.JavaPerspective">

+         <view

+               ratio="0.5"

+               relative="org.eclipse.ui.views.TaskList"

+               relationship="right"

+               id="glesv2debuggerclient.views.SampleView">

+         </view>

+      </perspectiveExtension>

+   </extension>

+   <extension

+         point="org.eclipse.help.contexts">

+      <contexts

+            file="contexts.xml">

+      </contexts>

+   </extension>

+

+</plugin>

diff --git a/tools/glesv2debugger/setup.sh b/tools/glesv2debugger/setup.sh
new file mode 100755
index 0000000..839019d
--- /dev/null
+++ b/tools/glesv2debugger/setup.sh
@@ -0,0 +1,35 @@
+source ../../../build/envsetup.sh
+pushd ../../../
+
+# need lunch before building jars
+if [ -z "$TARGET_PRODUCT" ]; then
+    lunch
+fi
+
+pushd external/liblzf/
+mm
+popd
+
+pushd external/protobuf/
+mm
+popd
+
+pushd sdk/sdkmanager/libs/sdklib
+mm
+popd
+
+# glsl_compiler is optional
+# make glsl_compiler -j3
+
+popd
+
+mkdir -p lib
+cp "$ANDROID_HOST_OUT/framework/host-libprotobuf-java-2.3.0-lite.jar" lib/
+cp "$ANDROID_HOST_OUT/framework/liblzf.jar" lib/
+cp "$ANDROID_HOST_OUT/framework/sdklib.jar" lib/
+
+# optional; usually for linux
+#cp "$ANDROID_HOST_OUT/bin/glsl_compiler" ~/
+
+# optional; usually for mac, need to replace eclipse.app with actual path
+#cp "$ANDROID_HOST_OUT/bin/glsl_compiler" eclipse.app/Contents/MacOS
diff --git a/tools/glesv2debugger/src/META-INF/MANIFEST.MF b/tools/glesv2debugger/src/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..212b27a
--- /dev/null
+++ b/tools/glesv2debugger/src/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0

+Created-By: 1.6.0_22 (Sun Microsystems Inc.)

+

diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/Activator.java b/tools/glesv2debugger/src/com/android/glesv2debugger/Activator.java
new file mode 100644
index 0000000..6083c0f
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/Activator.java
@@ -0,0 +1,83 @@
+/*

+ ** Copyright 2011, 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.glesv2debugger;

+

+import org.eclipse.jface.resource.ImageDescriptor;

+import org.eclipse.ui.plugin.AbstractUIPlugin;

+import org.osgi.framework.BundleContext;

+

+/**

+ * The activator class controls the plug-in life cycle

+ */

+public class Activator extends AbstractUIPlugin {

+

+    // The plug-in ID

+    public static final String PLUGIN_ID = "GLESv2DebuggerClient"; //$NON-NLS-1$

+

+    // The shared instance

+    private static Activator plugin;

+

+    /**

+     * The constructor

+     */

+    public Activator() {

+    }

+

+    /*

+     * (non-Javadoc)

+     * @see

+     * org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext

+     * )

+     */

+    @Override

+    public void start(BundleContext context) throws Exception {

+        super.start(context);

+        plugin = this;

+    }

+

+    /*

+     * (non-Javadoc)

+     * @see

+     * org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext

+     * )

+     */

+    @Override

+    public void stop(BundleContext context) throws Exception {

+        plugin = null;

+        super.stop(context);

+    }

+

+    /**

+     * Returns the shared instance

+     * 

+     * @return the shared instance

+     */

+    public static Activator getDefault() {

+        return plugin;

+    }

+

+    /**

+     * Returns an image descriptor for the image file at the given plug-in

+     * relative path

+     * 

+     * @param path the path

+     * @return the image descriptor

+     */

+    public static ImageDescriptor getImageDescriptor(String path) {

+        return imageDescriptorFromPlugin(PLUGIN_ID, path);

+    }

+}

diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/BreakpointOption.java b/tools/glesv2debugger/src/com/android/glesv2debugger/BreakpointOption.java
new file mode 100644
index 0000000..e8405f9
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/BreakpointOption.java
@@ -0,0 +1,191 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.glesv2debugger.DebuggerMessage.Message.Prop;
+import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Shell;
+
+import java.io.IOException;
+
+public class BreakpointOption extends ScrolledComposite implements SelectionListener,
+        ProcessMessage {
+
+    SampleView sampleView;
+    Button[] buttonsBreak = new Button[Function.values().length];
+    /** cache of buttonsBreak[Function.getNumber()].getSelection */
+    boolean[] breakpoints = new boolean[Function.values().length];
+
+    BreakpointOption(SampleView sampleView, Composite parent) {
+        super(parent, SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_SCROLL);
+        this.sampleView = sampleView;
+
+        Composite composite = new Composite(this, 0);
+        GridLayout layout = new GridLayout();
+        layout.numColumns = 4;
+        composite.setLayout(layout);
+        this.setLayout(new FillLayout());
+
+        for (int i = 0; i < Function.values().length; i++) {
+            Group group = new Group(composite, 0);
+            group.setLayout(new RowLayout());
+            group.setText(Function.values()[i].toString());
+            Button btn = new Button(group, SWT.CHECK);
+            btn.addSelectionListener(this);
+            btn.setText("Break");
+            btn.setSelection(false);
+            breakpoints[Function.values()[i].getNumber()] = btn.getSelection();
+            buttonsBreak[Function.values()[i].getNumber()] = btn;
+        }
+
+        Point size = composite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+        composite.setSize(size);
+        this.setContent(composite);
+        this.setExpandHorizontal(true);
+        this.setExpandVertical(true);
+        this.setMinSize(size);
+        this.layout();
+    }
+
+    void setBreakpoint(final int contextId, final Function function, final boolean enabled) {
+        Message.Builder builder = Message.newBuilder();
+        builder.setContextId(contextId);
+        builder.setType(Type.Response);
+        builder.setExpectResponse(false);
+        builder.setFunction(Function.SETPROP);
+        builder.setProp(Prop.ExpectResponse);
+        builder.setArg0(function.getNumber());
+        builder.setArg1(enabled ? 1 : 0);
+        sampleView.messageQueue.addCommand(builder.build());
+        breakpoints[function.getNumber()] = enabled;
+    }
+
+    @Override
+    public void widgetSelected(SelectionEvent e) {
+        Button btn = (Button) e.widget;
+        Group group = (Group) btn.getParent();
+        int contextId = 0;
+        if (sampleView.current != null)
+            contextId = sampleView.current.contextId;
+        setBreakpoint(contextId, Function.valueOf(group.getText()), btn.getSelection());
+    }
+
+    @Override
+    public void widgetDefaultSelected(SelectionEvent e) {
+    }
+
+    private Function lastFunction = Function.NEG;
+
+    public boolean processMessage(final MessageQueue queue, final Message msg) throws IOException {
+        if (!breakpoints[msg.getFunction().getNumber()])
+            return false;
+        // use DefaultProcessMessage just to register the GL call
+        // but do not send response
+        final int contextId = msg.getContextId();
+        if (msg.getType() == Type.BeforeCall || msg.getType() == Type.AfterCall)
+            queue.defaultProcessMessage(msg, true, false);
+        final Message.Builder builder = Message.newBuilder();
+        builder.setContextId(contextId);
+        builder.setType(Type.Response);
+        builder.setExpectResponse(true);
+        final Shell shell = sampleView.getViewSite().getShell();
+        final boolean send[] = new boolean[1];
+        shell.getDisplay().syncExec(new Runnable() {
+            @Override
+            public void run() {
+                String call = MessageFormatter.format(msg, false);
+                call = call.substring(0, call.indexOf("(")) + ' ' +
+                        msg.getFunction() + call.substring(call.indexOf("("));
+                if (msg.hasData() && msg.getFunction() == Function.glShaderSource)
+                {
+                    int index = call.indexOf("string=") + 7;
+                    String ptr = call.substring(index, call.indexOf(',', index));
+                    call = call.replace(ptr, '"' + msg.getData().toStringUtf8() + '"');
+                }
+                if (msg.getType() == Type.AfterCall)
+                {
+                    call = "skip " + call;
+                    builder.setFunction(Function.SKIP);
+                }
+                else if (msg.getType() == Type.BeforeCall)
+                {
+                    call = "continue " + call;
+                    builder.setFunction(Function.CONTINUE);
+                }
+                else
+                {
+                    assert msg.getType() == Type.AfterGeneratedCall;
+                    assert msg.getFunction() == lastFunction;
+                    call = "skip " + call;
+                    builder.setFunction(Function.SKIP);
+                }
+                InputDialog inputDialog = new InputDialog(shell,
+                            msg.getFunction().toString() + " " + msg.getType().toString(),
+                        "(s)kip, (c)continue, (r)emove bp or glFunction(...)",
+                            call, null);
+                if (Window.OK == inputDialog.open())
+                {
+                    String s = inputDialog.getValue().substring(0, 1).toLowerCase();
+                    if (s.startsWith("s"))
+                    {
+                        builder.setFunction(Function.SKIP);
+                        // AfterCall is skipped, so push BeforeCall to complete
+                        if (queue.getPartialMessage(contextId) != null)
+                            queue.completePartialMessage(contextId);
+                    }
+                    else if (s.startsWith("c"))
+                        builder.setFunction(Function.CONTINUE);
+                    else if (s.startsWith("r"))
+                    {
+                        Button btn = buttonsBreak[msg.getFunction().getNumber()];
+                        btn.setSelection(false);
+                        setBreakpoint(msg.getContextId(), msg.getFunction(), false);
+                        builder.setExpectResponse(false);
+                    }
+                    else
+                    {
+                        MessageParserEx.instance.parse(builder, inputDialog.getValue());
+                        lastFunction = builder.getFunction();
+                        builder.setExpectResponse(true);
+                        // AfterCall is skipped, so push BeforeCall to complete
+                        if (queue.getPartialMessage(contextId) != null)
+                            queue.completePartialMessage(contextId);
+                    }
+                }
+                // else defaults to continue BeforeCall and skip AfterCall
+            }
+        });
+        queue.sendMessage(builder.build());
+        return true;
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java b/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java
new file mode 100644
index 0000000..28f3a54
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java
@@ -0,0 +1,1236 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.sdklib.util.SparseIntArray;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.swt.widgets.Shell;
+
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.ByteBuffer;
+
+public class CodeGen implements IRunnableWithProgress {
+    private FileWriter codeFile, makeFile, namesHeaderFile, namesSourceFile;
+    private PrintWriter code, make, namesHeader, namesSource;
+    private FileOutputStream dataOut;
+    private SparseIntArray bufferNames,
+            framebufferNames, programNames, textureNames, shaderNames, renderbufferNames;
+
+    /** return true if msg was a texture upload */
+    private boolean codeGenTextureUpload(final Message msg, final boolean replaceCopy) {
+        String s = null;
+        switch (msg.getFunction()) {
+            case glCompressedTexImage2D:
+                s = MessageFormatter.format(msg, true).replace("arg7", "texData");
+                break;
+            case glCompressedTexSubImage2D:
+            case glTexImage2D:
+            case glTexSubImage2D:
+                s = MessageFormatter.format(msg, true).replace("arg8", "texData");
+                break;
+            case glCopyTexImage2D:
+                if (!replaceCopy) {
+                    code.write(MessageFormatter.format(msg, true));
+                    code.write(";CHKERR;\n");
+                    return true;
+                }
+                assert msg.getArg2() == msg.getPixelFormat(); // TODO
+                s = "//" + MessageFormatter.format(msg, true) + "\n";
+                s += String.format("glTexImage2D(%s, %d, %s, %d, %d, %d, %s, %s, texData);CHKERR;",
+                        GLEnum.valueOf(msg.getArg0()), msg.getArg1(),
+                        GLEnum.valueOf(msg.getArg2()), msg.getArg5(), msg.getArg6(),
+                        msg.getArg7(), GLEnum.valueOf(msg.getPixelFormat()),
+                        GLEnum.valueOf(msg.getPixelType()));
+                break;
+            case glCopyTexSubImage2D:
+                if (!replaceCopy) {
+                    code.write(MessageFormatter.format(msg, true));
+                    code.write(";CHKERR;\n");
+                    return true;
+                }
+                // FIXME: check the texture format & type, and convert
+                s = "//" + MessageFormatter.format(msg, true) + "\n";
+                s += String.format(
+                        "glTexSubImage2D(%s, %d, %d, %d, %d, %d, %s, %s, texData);CHKERR;",
+                        GLEnum.valueOf(msg.getArg0()), msg.getArg1(), msg.getArg2(),
+                        msg.getArg3(), msg.getArg6(), msg.getArg7(),
+                        GLEnum.valueOf(msg.getPixelFormat()), GLEnum.valueOf(msg.getPixelType()));
+                break;
+            default:
+                return false;
+        }
+
+        if (msg.hasData()) {
+            final byte[] data = MessageProcessor.lzfDecompressChunks(msg.getData());
+            try {
+                code.write("{\n");
+                code.format("    void * texData = malloc(%d);CHKERR;\n", data.length);
+                code.format("    FILE * texFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
+                code.format("    assert(texFile);CHKERR;\n");
+                code.format("    fseek(texFile, %d, SEEK_SET);CHKERR;\n", dataOut.getChannel()
+                        .position());
+                dataOut.write(data);
+                code.format("    fread(texData, %d, 1, texFile);CHKERR;\n", data.length);
+                code.format("    fclose(texFile);CHKERR;\n");
+                code.format("    " + s + ";\n");
+                code.format("    free(texData);CHKERR;\n");
+                code.format("}\n");
+            } catch (IOException e) {
+                e.printStackTrace();
+                assert false;
+            }
+        } else
+            code.write(s.replace("texData", "NULL") + ";\n");
+        return true;
+    }
+
+    private void codeGenServerState(final GLServerState serverState) {
+        code.write("// CodeGenServerState\n");
+        for (int i = 0; i < serverState.enableDisables.size(); i++) {
+            final GLEnum key = GLEnum.valueOf(serverState.enableDisables.keyAt(i));
+            if (serverState.enableDisables.valueAt(i) == 0)
+                code.format("glDisable(%s);CHKERR;\n", key);
+            else
+                code.format("glEnable(%s);CHKERR;\n", key);
+        }
+        for (int i = 0; i < serverState.lastSetter.size(); i++) {
+            final Function key = Function.valueOf(serverState.lastSetter.keyAt(i));
+            final Message msg = serverState.lastSetter.valueAt(i);
+            if (msg == null) {
+                code.format("// %s is default\n", key);
+                continue;
+            }
+            final String s = MessageFormatter.format(msg, true);
+            code.write(s);
+            code.write(";\n");
+        }
+        // TODO: stencil and integers
+    }
+
+    private void codeGenServerShader(final GLServerShader serverShader) {
+        code.write("// CodeGenServerShader\n");
+        for (int i = 0; i < serverShader.shaders.size(); i++) {
+            final int name = serverShader.shaders.keyAt(i);
+            final GLShader shader = serverShader.shaders.valueAt(i);
+            final String id = "shader_" + name;
+            if (shaderNames.indexOfKey(name) < 0) {
+                namesSource.format("GLuint %s = 0;\n", id);
+                namesHeader.format("extern GLuint %s;\n", id);
+            }
+            code.format("%s = glCreateShader(%s);CHKERR;\n", id, shader.type);
+            shaderNames.put(name, name);
+
+            if (shader.source != null) {
+                final String src = shader.source.replace("\r", "").replace("\n", "\\n\\\n")
+                        .replace("\"", "\\\"");
+                code.format("glShaderSource(%s, 1, (const GLchar *[]){\"%s\"}, NULL);CHKERR;\n",
+                                id, src);
+                code.format("glCompileShader(%s);CHKERR;\n", id);
+            }
+        }
+
+        for (int i = 0; i < serverShader.programs.size(); i++) {
+            final int name = serverShader.programs.keyAt(i);
+            final GLProgram program = serverShader.programs.valueAt(i);
+            final String id = "program_" + name;
+            if (programNames.indexOfKey(name) < 0) {
+                namesSource.format("GLuint %s = 0;\n", id);
+                namesHeader.format("extern GLuint %s;\n", id);
+            }
+            code.format("%s = glCreateProgram();CHKERR;\n", id);
+            programNames.put(name, name);
+            code.format("glAttachShader(%s, shader_%d);CHKERR;\n", id,
+                    program.vert);
+            code.format("glAttachShader(%s, shader_%d);CHKERR;\n", id,
+                    program.frag);
+            code.format("glLinkProgram(%s);CHKERR;\n", id);
+            if (serverShader.current == program)
+                code.format("glUseProgram(%s);CHKERR;\n", id);
+        }
+    }
+
+    private void codeGenServerTexture(final GLServerTexture serverTexture, final boolean replaceCopy) {
+        code.write("// CodeGenServerTexture\n");
+        for (int i = 0; i < serverTexture.textures.size(); i++) {
+            final int name = serverTexture.textures.keyAt(i);
+            final GLTexture tex = serverTexture.textures.valueAt(i);
+            final String id = "texture_" + name;
+            if (textureNames.indexOfKey(name) < 0) {
+                namesHeader.format("extern GLuint %s;\n", id);
+                namesSource.format("GLuint %s = 0;\n", id);
+            }
+            code.format("%s = 0;\n", id);
+            textureNames.put(name, name);
+
+            if (name == 0)
+                continue;
+            code.format("glGenTextures(1, &%s);CHKERR;\n", id);
+            String s = String.format("glBindTexture(%s, texture_%d);CHKERR;\n", tex.target,
+                    tex.name);
+            code.write(s);
+            for (final Message msg : tex.contentChanges) {
+                if (codeGenTextureUpload(msg, replaceCopy))
+                    continue;
+                switch (msg.getFunction()) {
+                    case glGenerateMipmap:
+                        s = MessageFormatter.format(msg, true);
+                        break;
+                    default:
+                        assert false;
+                }
+                code.write(s + ";\n");
+            }
+            code.format("glTexParameteriv(%s, GL_TEXTURE_WRAP_S, (GLint[]){%s});CHKERR;\n",
+                    tex.target, tex.wrapS);
+            code.format("glTexParameteriv(%s, GL_TEXTURE_WRAP_T, (GLint[]){%s});CHKERR;\n",
+                    tex.target, tex.wrapT);
+            code.format("glTexParameteriv(%s, GL_TEXTURE_MIN_FILTER, (GLint[]){%s});CHKERR;\n",
+                    tex.target, tex.min);
+            code.format("glTexParameteriv(%s, GL_TEXTURE_MAG_FILTER, (GLint[]){%s});CHKERR;\n",
+                    tex.target, tex.mag);
+        }
+        for (int i = 0; i < serverTexture.tmu2D.length; i++) {
+            code.format("glActiveTexture(%s);CHKERR;\n",
+                    GLEnum.valueOf(GLEnum.GL_TEXTURE0.value + i));
+            code.format("glBindTexture(GL_TEXTURE_2D, texture_%d);CHKERR;\n",
+                    serverTexture.tmu2D[i]);
+        }
+        for (int i = 0; i < serverTexture.tmuCube.length; i++) {
+            code.format("glActiveTexture(%s);CHKERR;\n",
+                    GLEnum.valueOf(GLEnum.GL_TEXTURE0.value + i));
+            code.format("glBindTexture(GL_TEXTURE_CUBE_MAP, texture_%d);CHKERR;\n",
+                    serverTexture.tmuCube[i]);
+        }
+        code.format("glActiveTexture(%s);CHKERR;\n", serverTexture.activeTexture);
+        if (serverTexture.tex2D == null)
+            code.format("glBindTexture(GL_TEXTURE_2D, 0);CHKERR;\n");
+        else
+            code.format("glBindTexture(GL_TEXTURE_2D, texture_%d);CHKERR;\n",
+                    serverTexture.tex2D.name);
+        if (serverTexture.texCube == null)
+            code.format("glBindTexture(GL_TEXTURE_CUBE_MAP, 0);CHKERR;\n");
+        else
+            code.format("glBindTexture(GL_TEXTURE_CUBE_MAP, texture_%d);CHKERR;\n",
+                    serverTexture.texCube.name);
+    }
+
+    private void codeGenBufferData(final ByteBuffer buffer, final String call) {
+        ByteBuffer bfr = buffer;
+        if (buffer.isReadOnly()) {
+            bfr = ByteBuffer.allocate(buffer.capacity());
+            bfr.put(buffer);
+        }
+        final byte[] data = bfr.array();
+        try {
+            code.write("{\n");
+            code.format("    void * bufferData = malloc(%d);\n", data.length);
+            code.format("    FILE * bufferFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");\n");
+            code.format("    assert(bufferFile);\n");
+            code.format("    fseek(bufferFile, %d, SEEK_SET);\n", dataOut.getChannel()
+                    .position());
+            dataOut.write(data);
+            code.format("    fread(bufferData, %d, 1, bufferFile);\n", data.length);
+            code.format("    fclose(bufferFile);\n");
+            code.format("    " + call + ";CHKERR;\n");
+            code.format("    free(bufferData);\n");
+            code.format("}\n");
+        } catch (IOException e) {
+            e.printStackTrace();
+            assert false;
+        }
+    }
+
+    private void codeGenServerVertex(final GLServerVertex v) {
+        code.write("// CodeGenServerVertex\n");
+        for (int i = 0; i < v.buffers.size(); i++) {
+            final int name = v.buffers.keyAt(i);
+            final String id = "buffer_" + name;
+            final GLBuffer buffer = v.buffers.valueAt(i);
+            if (bufferNames.indexOfKey(name) < 0) {
+                namesHeader.format("extern GLuint %s;\n", id);
+                namesSource.format("GLuint %s = 0;\n", id);
+            }
+            code.format("%s = 0;\n", id);
+            bufferNames.put(name, name);
+            if (name == 0)
+                continue;
+            code.format("glGenBuffers(1, &%s);CHKERR;\n", id);
+            if (buffer.target != null) {
+                code.format("glBindBuffer(%s, %s);CHKERR;\n", buffer.target, id);
+                if (buffer.data != null) {
+                    String s = String.format("glBufferData(%s, %d, bufferData, %s)", buffer.target,
+                            buffer.data.capacity(), buffer.usage);
+                    codeGenBufferData(buffer.data, s);
+                }
+            }
+        }
+        // TODO: use MAX_VERTEX_ATTRIBS
+        for (int i = 0; i < v.defaultAttribs.length; i++)
+            code.format("glVertexAttrib4f(%d, %f, %f, %f, %f);CHKERR;\n", i,
+                    v.defaultAttribs[i][0],
+                    v.defaultAttribs[i][1], v.defaultAttribs[i][2], v.defaultAttribs[i][3]);
+        for (int i = 0; i < v.attribPointers.length; i++) {
+            final GLAttribPointer att = v.attribPointers[i];
+            if (att.type == null)
+                continue;
+            if (att.buffer != null)
+                code.format("glBindBuffer(GL_ARRAY_BUFFER, buffer_%d);CHKERR;\n", att.buffer.name);
+            else
+                code.format("glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
+            code.format("glVertexAttribPointer(%d, %d, %s, %b, %d, (const GLvoid *)%d);CHKERR;\n",
+                    i, att.size, att.type, att.normalized, att.stride, att.ptr);
+        }
+        if (v.attribBuffer != null)
+            code.format("glBindBuffer(GL_ARRAY_BUFFER, buffer_%d);CHKERR;\n", v.attribBuffer.name);
+        else
+            code.write("glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
+        if (v.indexBuffer != null)
+            code.format("glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_%d);CHKERR;\n",
+                    v.indexBuffer.name);
+        else
+            code.write("glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);CHKERR;\n");
+    }
+
+    private void codeGenGenNames(final Message msg) {
+        final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
+        names.order(SampleView.targetByteOrder);
+        SparseIntArray namesArray = null;
+        for (int i = 0; i < msg.getArg0(); i++) {
+            String id = "";
+            final int name = names.getInt();
+            switch (msg.getFunction()) {
+                case glGenBuffers:
+                    id = "buffer";
+                    namesArray = bufferNames;
+                    break;
+                case glGenFramebuffers:
+                    id = "framebuffer";
+                    namesArray = framebufferNames;
+                    break;
+                case glGenRenderbuffers:
+                    id = "renderbuffer";
+                    namesArray = renderbufferNames;
+                    break;
+                case glGenTextures:
+                    id = "texture";
+                    namesArray = textureNames;
+                    break;
+                default:
+                    assert false;
+            }
+            id += "_" + name;
+            if (namesArray.indexOfKey(name) < 0) {
+                namesHeader.format("extern GLuint %s;\n", id);
+                namesSource.format("GLuint %s = 0;\n", id);
+            }
+            code.format("%s = 0;\n", id);
+            namesArray.put(name, name);
+            code.format("%s(1, &%s);CHKERR;\n", msg.getFunction(), id);
+        }
+    }
+
+    private void codeGenDeleteNames(final Message msg) {
+        final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
+        names.order(SampleView.targetByteOrder);
+        SparseIntArray namesArray = null;
+        for (int i = 0; i < msg.getArg0(); i++) {
+            String id = null;
+            final int name = names.getInt();
+            switch (msg.getFunction()) {
+                case glDeleteBuffers:
+                    id = "buffer";
+                    namesArray = bufferNames;
+                    break;
+                case glDeleteFramebuffers:
+                    id = "framebuffer";
+                    namesArray = framebufferNames;
+                    break;
+                case glDeleteRenderbuffers:
+                    id = "renderbuffer";
+                    namesArray = renderbufferNames;
+                    break;
+                case glDeleteTextures:
+                    id = "texture";
+                    namesArray = textureNames;
+                    break;
+                default:
+                    assert false;
+            }
+            id += "_" + name;
+            code.format("%s = 0;\n", id);
+            namesArray.put(name, 0);
+            code.format("%s(1, &%s);CHKERR;\n", msg.getFunction(), id);
+        }
+    }
+
+    private void codeGenBindNames(final Message msg) {
+        String id = null;
+        SparseIntArray namesArray = null;
+        final int name = msg.getArg1();
+        switch (msg.getFunction()) {
+            case glBindBuffer:
+                id = "buffer";
+                namesArray = bufferNames;
+                break;
+            case glBindFramebuffer:
+                id = "framebuffer";
+                namesArray = framebufferNames;
+                break;
+            case glBindRenderbuffer:
+                id = "renderbuffer";
+                namesArray = renderbufferNames;
+                break;
+            case glBindTexture:
+                id = "texture";
+                namesArray = textureNames;
+                break;
+            default:
+                assert false;
+        }
+        id += "_" + name;
+        if (namesArray.indexOfKey(name) < 0) {
+            namesHeader.format("extern GLuint %s;\n", id);
+            namesSource.format("GLuint %s = 0;\n", id);
+        } else if (namesArray.get(name) != name)
+            code.format("%s = %d;\n", id, name); // name was deleted
+        namesArray.put(name, name);
+        code.write(MessageFormatter.format(msg, true));
+        code.write(";CHKERR;\n");
+    }
+
+    private void codeGenDrawArrays(final GLServerVertex v, final MessageData msgData)
+            throws IOException {
+        final int maxAttrib = msgData.msg.getArg7();
+        if (maxAttrib < 1) {
+            code.write("// no vertex data\n");
+            return;
+        }
+        final byte[] data = msgData.msg.getData().toByteArray();
+        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
+        final int first = msgData.msg.getArg1(), count = msgData.msg.getArg2();
+        int attribDataStride = 0;
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer att = v.attribPointers[i];
+            if (!att.enabled)
+                continue;
+            if (att.buffer != null)
+                continue;
+            attribDataStride += att.elemSize;
+        }
+        assert attribDataStride * count == data.length;
+        code.write("{\n");
+        if (attribDataStride > 0) {
+            code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
+            code.format("    assert(attribFile);CHKERR;\n");
+            code.format("    fseek(attribFile, %d, SEEK_SET);CHKERR;\n", dataOut.getChannel()
+                    .position());
+            dataOut.write(data);
+            code.format("    char * const attribData = (char *)malloc(%d);\n", first
+                    * attribDataStride + data.length);
+            code.format("    assert(attribData);\n");
+            code.format("    fread(attribData + %d, %d, 1, attribFile);\n",
+                    first * attribDataStride, data.length);
+            code.format("    fclose(attribFile);\n");
+            code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
+            int attribDataOffset = 0;
+            for (int i = 0; i < maxAttrib; i++) {
+                final GLAttribPointer att = v.attribPointers[i];
+                if (!att.enabled)
+                    continue;
+                if (att.buffer != null)
+                    continue;
+                code.format(
+                        "    glVertexAttribPointer(%d, %d, %s, %b, %d, attribData + %d);CHKERR;\n",
+                        i, att.size, att.type, att.normalized,
+                        attribDataStride, attribDataOffset);
+                attribDataOffset += att.elemSize;
+            }
+            if (v.attribBuffer != null)
+                code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
+                        v.attribBuffer.name);
+        }
+        code.format("    glDrawArrays(%s, %d, %d);CHKERR;\n", mode, first, count);
+        if (attribDataStride > 0)
+            code.format("    free(attribData);CHKERR;\n");
+        code.write("};\n");
+    }
+
+    private void codeGenDrawElements(final GLServerVertex v, final MessageData msgData)
+            throws IOException {
+        final int maxAttrib = msgData.msg.getArg7();
+        if (maxAttrib < 1) {
+            code.write("// no vertex data\n");
+            return;
+        }
+        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
+        final int count = msgData.msg.getArg1();
+        final GLEnum type = GLEnum.valueOf(msgData.msg.getArg2());
+        String typeName = "GLubyte";
+        if (type == GLEnum.GL_UNSIGNED_SHORT)
+            typeName = "GLushort";
+        int attribDataStride = 0;
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer att = v.attribPointers[i];
+            if (!att.enabled)
+                continue;
+            if (att.buffer != null)
+                continue;
+            attribDataStride += att.elemSize;
+        }
+        code.write("{\n");
+        if (v.indexBuffer == null || attribDataStride > 0) {
+            // need to load user pointer indices and/or attributes
+            final byte[] element = new byte[attribDataStride];
+            final ByteBuffer data = msgData.msg.getData().asReadOnlyByteBuffer();
+            data.order(SampleView.targetByteOrder);
+            final ByteBuffer indexData = ByteBuffer.allocate(count * GLServerVertex.typeSize(type));
+            indexData.order(SampleView.targetByteOrder);
+            final ByteBuffer attribData = ByteBuffer.allocate(count * attribDataStride);
+            attribData.order(SampleView.targetByteOrder);
+            int maxIndex = -1;
+            ByteBuffer indexSrc = data;
+            if (v.indexBuffer != null) {
+                indexSrc = v.indexBuffer.data;
+                indexSrc.position(msgData.msg.getArg3());
+            }
+            indexSrc.order(SampleView.targetByteOrder);
+            for (int i = 0; i < count; i++) {
+                int index = -1;
+                if (type == GLEnum.GL_UNSIGNED_BYTE) {
+                    byte idx = indexSrc.get();
+                    index = idx & 0xff;
+                    indexData.put(idx);
+                } else if (type == GLEnum.GL_UNSIGNED_SHORT) {
+                    short idx = indexSrc.getShort();
+                    index = idx & 0xffff;
+                    indexData.putShort(idx);
+                } else
+                    assert false;
+                data.get(element);
+                attribData.put(element);
+                if (index > maxIndex)
+                    maxIndex = index;
+            }
+            code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
+            code.format("    assert(attribFile);CHKERR;\n");
+            code.format("    fseek(attribFile, 0x%X, SEEK_SET);CHKERR;\n",
+                    dataOut.getChannel().position());
+            dataOut.write(indexData.array());
+            code.format("    %s * const indexData = (%s *)malloc(%d);\n", typeName, typeName,
+                    indexData.capacity());
+            code.format("    assert(indexData);\n");
+            code.format("    fread(indexData, %d, 1, attribFile);\n", indexData.capacity());
+            if (attribDataStride > 0) {
+                code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
+                for (int i = 0; i < maxAttrib; i++) {
+                    final GLAttribPointer att = v.attribPointers[i];
+                    if (!att.enabled)
+                        continue;
+                    if (att.buffer != null)
+                        continue;
+                    code.format("    char * const attrib%d = (char *)malloc(%d);\n",
+                            i, att.elemSize * (maxIndex + 1));
+                    code.format("    assert(attrib%d);\n", i);
+                    code.format(
+                            "    glVertexAttribPointer(%d, %d, %s, %b, %d, attrib%d);CHKERR;\n",
+                            i, att.size, att.type, att.normalized, att.elemSize, i);
+                }
+                dataOut.write(attribData.array());
+                code.format("    for (%s i = 0; i < %d; i++) {\n", typeName, count);
+                for (int i = 0; i < maxAttrib; i++) {
+                    final GLAttribPointer att = v.attribPointers[i];
+                    if (!att.enabled)
+                        continue;
+                    if (att.buffer != null)
+                        continue;
+                    code.format(
+                            "        fread(attrib%d + indexData[i] * %d, %d, 1, attribFile);\n",
+                            i, att.elemSize, att.elemSize);
+                }
+                code.format("    }\n");
+                if (v.attribBuffer != null)
+                    code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
+                            v.attribBuffer.name);
+            }
+            code.format("    fclose(attribFile);\n");
+        }
+        if (v.indexBuffer != null)
+            code.format("    glDrawElements(%s, %d, %s, (const void *)%d);CHKERR;\n",
+                    mode, count, type, msgData.msg.getArg3());
+        else {
+            code.format("    glDrawElements(%s, %d, %s, indexData);CHKERR;\n",
+                    mode, count, type);
+            code.format("    free(indexData);\n");
+        }
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer att = v.attribPointers[i];
+            if (!att.enabled)
+                continue;
+            if (att.buffer != null)
+                continue;
+            code.format("    free(attrib%d);\n", i);
+        }
+        code.write("};\n");
+    }
+
+    private void codeGenDraw(final GLServerVertex v, final MessageData msgData)
+            throws IOException {
+        final int maxAttrib = msgData.msg.getArg7();
+        if (maxAttrib < 1) {
+            code.write("// no vertex data\n");
+            return;
+        }
+        final int count = msgData.attribs[0].length / 4;
+        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
+        final ByteBuffer attribData = ByteBuffer.allocate(maxAttrib * count * 16);
+        attribData.order(SampleView.targetByteOrder);
+        for (int i = 0; i < count; i++)
+            for (int j = 0; j < maxAttrib; j++)
+                for (int k = 0; k < 4; k++)
+                    attribData.putFloat(msgData.attribs[j][i * 4 + k]);
+        assert attribData.remaining() == 0;
+        code.write("{\n");
+        code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
+        code.format("    assert(attribFile);CHKERR;\n");
+        code.format("    fseek(attribFile, 0x%X, SEEK_SET);CHKERR;\n",
+                dataOut.getChannel().position());
+        dataOut.write(attribData.array());
+        code.format("    char * const attribData = (char *)malloc(%d);\n", attribData.capacity());
+        code.format("    assert(attribData);\n");
+        code.format("    fread(attribData, %d, 1, attribFile);\n", attribData.capacity());
+        code.format("    fclose(attribFile);\n");
+        code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer att = v.attribPointers[i];
+            assert msgData.attribs[i].length == count * 4;
+            code.format(
+                    "    glVertexAttribPointer(%d, %d, GL_FLOAT, GL_FALSE, %d, attribData + %d);CHKERR;\n",
+                        i, att.size, maxAttrib * 16, i * 16);
+        }
+        code.format("    glDrawArrays(%s, 0, %d);CHKERR;\n", mode, count);
+        code.format("    free(attribData);\n");
+        if (v.attribBuffer != null)
+            code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
+                        v.attribBuffer.name);
+        code.write("};\n");
+    }
+
+    private void codeGenFunction(final Context ctx, final MessageData msgData)
+            throws IOException {
+        final Message msg = msgData.msg;
+        String call = MessageFormatter.format(msg, true);
+        switch (msg.getFunction()) {
+            case glActiveTexture:
+            case glAttachShader:
+            case glBindAttribLocation:
+                break;
+            case glBindBuffer:
+            case glBindFramebuffer:
+            case glBindRenderbuffer:
+            case glBindTexture:
+                codeGenBindNames(msg);
+                return;
+            case glBlendColor:
+            case glBlendEquation:
+            case glBlendEquationSeparate:
+            case glBlendFunc:
+            case glBlendFuncSeparate:
+                break;
+            case glBufferData:
+                call = MessageFormatter.format(msg, true).replace("arg2", "bufferData");
+                codeGenBufferData(msg.getData().asReadOnlyByteBuffer(), call);
+                return;
+            case glBufferSubData:
+                call = MessageFormatter.format(msg, true).replace("arg3", "bufferData");
+                codeGenBufferData(msg.getData().asReadOnlyByteBuffer(), call);
+                return;
+            case glCheckFramebufferStatus:
+            case glClear:
+            case glClearColor:
+            case glClearDepthf:
+            case glClearStencil:
+            case glColorMask:
+            case glCompileShader:
+                break;
+            case glCompressedTexImage2D:
+            case glCompressedTexSubImage2D:
+            case glCopyTexImage2D:
+            case glCopyTexSubImage2D:
+                codeGenTextureUpload(msg, false);
+                return;
+            case glCreateProgram:
+                namesHeader.format("extern GLuint program_%d;\n", msg.getRet());
+                namesSource.format("GLuint program_%d = 0;\n", msg.getRet());
+                code.format("program_%d = glCreateProgram();CHKERR;\n", msg.getRet());
+                return;
+            case glCreateShader:
+                namesHeader.format("extern GLuint shader_%d;\n", msg.getRet());
+                namesSource.format("GLuint shader_%d = 0;\n", msg.getRet());
+                code.format("shader_%d = %s;\n", msg.getRet(), call);
+                return;
+            case glCullFace:
+                break;
+            case glDeleteBuffers:
+            case glDeleteFramebuffers:
+            case glDeleteProgram:
+                programNames.put(msg.getArg0(), 0);
+                break;
+            case glDeleteRenderbuffers:
+                codeGenDeleteNames(msg);
+                return;
+            case glDeleteShader:
+                shaderNames.put(msg.getArg0(), 0);
+                return;
+            case glDeleteTextures:
+                codeGenDeleteNames(msg);
+                return;
+            case glDepthFunc:
+            case glDepthMask:
+            case glDepthRangef:
+            case glDetachShader:
+            case glDisable:
+            case glDisableVertexAttribArray:
+                break;
+            case glDrawArrays:
+                // CodeGenDraw(ctx.serverVertex, msgData);
+                codeGenDrawArrays(ctx.serverVertex, msgData);
+                return;
+            case glDrawElements:
+                // CodeGenDraw(ctx.serverVertex, msgData);
+                codeGenDrawElements(ctx.serverVertex, msgData);
+                return;
+            case glEnable:
+            case glEnableVertexAttribArray:
+            case glFinish:
+            case glFlush:
+            case glFramebufferRenderbuffer:
+            case glFramebufferTexture2D:
+            case glFrontFace:
+                break;
+            case glGenBuffers:
+                codeGenGenNames(msg);
+                return;
+            case glGenerateMipmap:
+                break;
+            case glGenFramebuffers:
+            case glGenRenderbuffers:
+            case glGenTextures:
+                codeGenGenNames(msg);
+                return;
+            case glGetActiveAttrib:
+            case glGetActiveUniform:
+            case glGetAttachedShaders:
+                break;
+            case glGetAttribLocation:
+                call = String.format("assert(%d == %s)", msg.getRet(), call);
+                break;
+            case glGetBooleanv:
+            case glGetBufferParameteriv:
+                return; // TODO
+            case glGetError:
+                code.write("CHKERR;\n");
+                return;
+            case glGetFloatv:
+            case glGetFramebufferAttachmentParameteriv:
+            case glGetIntegerv:
+            case glGetProgramiv:
+            case glGetProgramInfoLog:
+            case glGetRenderbufferParameteriv:
+            case glGetShaderiv:
+            case glGetShaderInfoLog:
+            case glGetShaderPrecisionFormat:
+            case glGetShaderSource:
+            case glGetString:
+            case glGetTexParameterfv:
+            case glGetTexParameteriv:
+            case glGetUniformfv:
+            case glGetUniformiv:
+                return;
+            case glGetUniformLocation:
+                call = String.format("assert(%d == %s)", msg.getRet(), call);
+                break;
+            case glGetVertexAttribfv:
+            case glGetVertexAttribiv:
+            case glGetVertexAttribPointerv:
+                return; // TODO
+            case glHint:
+            case glIsBuffer:
+            case glIsEnabled:
+            case glIsFramebuffer:
+            case glIsProgram:
+            case glIsRenderbuffer:
+            case glIsShader:
+            case glIsTexture:
+            case glLineWidth:
+            case glLinkProgram:
+            case glPixelStorei:
+            case glPolygonOffset:
+                break;
+            case glReadPixels:
+                return; // TODO
+            case glReleaseShaderCompiler:
+            case glRenderbufferStorage:
+            case glSampleCoverage:
+            case glScissor:
+                break;
+            case glShaderBinary:
+                return; // TODO
+            case glShaderSource:
+                call = String.format(
+                        "glShaderSource(shader_%d, 1, (const char * []){\"%s\"}, NULL)",
+                        msg.getArg0(),
+                        msg.getData().toStringUtf8().replace("\r", "").replace("\n", "\\n\\\n")
+                                .replace("\"", "\\\"")
+                                );
+                break;
+            case glStencilFunc:
+            case glStencilFuncSeparate:
+            case glStencilMask:
+            case glStencilMaskSeparate:
+            case glStencilOp:
+            case glStencilOpSeparate:
+                break;
+            case glTexImage2D:
+                codeGenTextureUpload(msg, false);
+                return;
+            case glTexParameterf:
+                break;
+            case glTexParameterfv:
+                return; // TODO
+            case glTexParameteri:
+                break;
+            case glTexParameteriv:
+                return; // TODO
+            case glTexSubImage2D:
+                codeGenTextureUpload(msg, false);
+                return;
+            case glUniform1f:
+            case glUniform1fv:
+            case glUniform1i:
+            case glUniform1iv:
+            case glUniform2f:
+            case glUniform2fv:
+            case glUniform2i:
+            case glUniform2iv:
+            case glUniform3f:
+            case glUniform3fv:
+            case glUniform3i:
+            case glUniform3iv:
+            case glUniform4f:
+            case glUniform4fv:
+            case glUniform4i:
+            case glUniform4iv:
+            case glUniformMatrix2fv:
+            case glUniformMatrix3fv:
+            case glUniformMatrix4fv:
+            case glUseProgram:
+            case glValidateProgram:
+            case glVertexAttrib1f:
+            case glVertexAttrib1fv:
+            case glVertexAttrib2f:
+            case glVertexAttrib2fv:
+            case glVertexAttrib3f:
+            case glVertexAttrib3fv:
+            case glVertexAttrib4f:
+            case glVertexAttrib4fv:
+                break;
+            case glVertexAttribPointer:
+                // if it's user pointer, then CodeGenDrawArrays/Elements will
+                // replace it with loaded data just before the draw
+                call = call.replace("arg5", "(const void *)0x" +
+                        Integer.toHexString(msg.getArg5()));
+                break;
+            case glViewport:
+                break;
+            case eglSwapBuffers:
+                return;
+            default:
+                assert false;
+                return;
+        }
+        if (call.indexOf("glEnable(/*cap*/ GL_TEXTURE_2D)") >= 0)
+            return;
+        else if (call.indexOf("glDisable(/*cap*/ GL_TEXTURE_2D)") >= 0)
+            return;
+        else if (call.indexOf("glActiveTexture(/*texture*/ GL_TEXTURE_2D)") >= 0)
+            return;
+        code.write(call + ";CHKERR;\n");
+    }
+
+    private void codeGenSetup(final Context ctx) {
+        try {
+            codeFile = new FileWriter("frame_setup.cpp", false);
+            code = new PrintWriter(codeFile);
+            dataOut = new FileOutputStream("frame_data.bin", false);
+            namesHeaderFile = new FileWriter("frame_names.h", false);
+            namesHeader = new PrintWriter(namesHeaderFile);
+            namesSourceFile = new FileWriter("frame_names.cpp", false);
+            namesSource = new PrintWriter(namesSourceFile);
+        } catch (IOException e) {
+            e.printStackTrace();
+            assert false;
+        }
+        bufferNames = new SparseIntArray();
+        framebufferNames = new SparseIntArray();
+        programNames = new SparseIntArray();
+        textureNames = new SparseIntArray();
+        shaderNames = new SparseIntArray();
+        renderbufferNames = new SparseIntArray();
+
+        namesHeader.write("#include <stdlib.h>\n");
+        namesHeader.write("#include <stdio.h>\n");
+        namesHeader.write("#include <assert.h>\n");
+        namesHeader.write("#include <GLES2/gl2.h>\n");
+        namesHeader.write("#include <GLES2/gl2ext.h>\n");
+        namesHeader.write("#define CHKERR assert(GL_NO_ERROR == glGetError());/**/\n");
+        namesHeader.write("void FrameSetup();\n");
+        namesHeader.write("extern const unsigned int FrameCount;\n");
+        namesHeader.write("extern const GLuint program_0;\n");
+
+        namesSource.write("/*\n" + 
+        " * Copyright (C) 2011 The Android Open Source Project\n" + 
+        " *\n" + 
+        " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" + 
+        " * you may not use this file except in compliance with the License.\n" + 
+        " * You may obtain a copy of the License at\n" + 
+        " *\n" + 
+        " *      http://www.apache.org/licenses/LICENSE-2.0\n" + 
+        " *\n" + 
+        " * Unless required by applicable law or agreed to in writing, software\n" + 
+        " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" + 
+        " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + 
+        " * See the License for the specific language governing permissions and\n" + 
+        " * limitations under the License.\n" + 
+        " */\n" + 
+        "\n" + 
+        "#include <stdlib.h>\n" + 
+        "#include <stdio.h>\n" + 
+        "\n" + 
+        "#include <EGL/egl.h>\n" + 
+        "#include <GLES2/gl2.h>\n" + 
+        "#include <GLES2/gl2ext.h>\n" + 
+        "\n" + 
+        "#include <ui/FramebufferNativeWindow.h>\n" + 
+        "#include <ui/EGLUtils.h>\n" + 
+        "\n" + 
+        "#include <private/ui/android_natives_priv.h>\n" + 
+        "\n" + 
+        "#include <surfaceflinger/Surface.h>\n" + 
+        "#include <surfaceflinger/ISurface.h>\n" + 
+        "#include <surfaceflinger/SurfaceComposerClient.h>\n" + 
+        "\n" + 
+        "using namespace android;\n" + 
+        "\n" + 
+        "static void checkEglError(const char* op, EGLBoolean returnVal = EGL_TRUE)\n" + 
+        "{\n" + 
+        "    if (returnVal != EGL_TRUE) {\n" + 
+        "        fprintf(stderr, \"%s() returned %d\\n\", op, returnVal);\n" + 
+        "    }\n" + 
+        "\n" + 
+        "    for (EGLint error = eglGetError(); error != EGL_SUCCESS; error\n" + 
+        "            = eglGetError()) {\n" + 
+        "        fprintf(stderr, \"after %s() eglError %s (0x%x)\\n\", op, EGLUtils::strerror(error),\n" + 
+        "                error);\n" + 
+        "    }\n" + 
+        "}\n" + 
+        "\n" + 
+        "static EGLDisplay dpy;\n" + 
+        "static EGLSurface surface;\n" + 
+        "\n" + 
+        "#include \"frame_names.h\"\n" + 
+        "const GLuint program_0 = 0;\n" + 
+        "int main(int argc, char** argv)\n" + 
+        "{\n" + 
+        "    EGLBoolean returnValue;\n" + 
+        "    EGLConfig myConfig = {0};\n" + 
+        "\n" + 
+        "    EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };\n" + 
+        "    EGLint majorVersion;\n" + 
+        "    EGLint minorVersion;\n" + 
+        "    EGLContext context;\n" + 
+        "    EGLint w, h;\n" + 
+        "\n" + 
+        "\n" + 
+        "    checkEglError(\"<init>\");\n" + 
+        "    dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n" + 
+        "    checkEglError(\"eglGetDisplay\");\n" + 
+        "    if (dpy == EGL_NO_DISPLAY) {\n" + 
+        "        printf(\"eglGetDisplay returned EGL_NO_DISPLAY.\\n\");\n" + 
+        "        return 0;\n" + 
+        "    }\n" + 
+        "\n" + 
+        "    returnValue = eglInitialize(dpy, &majorVersion, &minorVersion);\n" + 
+        "    checkEglError(\"eglInitialize\", returnValue);\n" + 
+        "    if (returnValue != EGL_TRUE) {\n" + 
+        "        printf(\"eglInitialize failed\\n\");\n" + 
+        "        return 0;\n" + 
+        "    }\n" + 
+        "\n" + 
+        "    sp<SurfaceComposerClient> spClient;\n" + 
+        "    sp<SurfaceControl> spControl;\n" + 
+        "    sp<Surface> spSurface;\n" + 
+        "\n" + 
+        "    // create a client to surfaceflinger\n" + 
+        "    spClient = new SurfaceComposerClient();\n" + 
+        "\n" + 
+        "    spControl = spClient->createSurface(getpid(), 0, 1280, 752, PIXEL_FORMAT_RGBX_8888);\n" + 
+        "    spClient->openTransaction();\n" + 
+        "    spControl->setLayer(350000);\n" + 
+        "    spControl->show();\n" + 
+        "    spClient->closeTransaction();\n" + 
+        "\n" + 
+        "    spSurface = spControl->getSurface();\n" + 
+        "    EGLNativeWindowType window = spSurface.get();\n" + 
+        "\n" + 
+        "    printf(\"window=%p\\n\", window);\n" + 
+        "    EGLint attrib_list[] = {\n" + 
+        "        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n" + 
+        "        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n" + 
+        "        EGL_BUFFER_SIZE, 32,\n" + 
+        "        EGL_RED_SIZE, 8,\n" + 
+        "        EGL_GREEN_SIZE, 8,\n" + 
+        "        EGL_BLUE_SIZE, 8,\n" + 
+        "        EGL_NONE\n" + 
+        "    };\n" + 
+        "\n" + 
+        "    EGLConfig configs[12] = {0};\n" + 
+        "    int num_config = -1;\n" + 
+        "    eglChooseConfig(dpy, attrib_list, configs, sizeof(configs) / sizeof(*configs), &num_config);\n" + 
+        "    printf(\"eglChooseConfig %d \\n\", num_config);\n" + 
+        "\n" + 
+        "    surface = eglCreateWindowSurface(dpy, configs[0], window, NULL);\n" + 
+        "    checkEglError(\"eglCreateWindowSurface\");\n" + 
+        "    if (surface == EGL_NO_SURFACE) {\n" + 
+        "        printf(\"gelCreateWindowSurface failed.\\n\");\n" + 
+        "        return 0;\n" + 
+        "    }\n" + 
+        "\n" + 
+        "    context = eglCreateContext(dpy, configs[0], EGL_NO_CONTEXT, context_attribs);\n" + 
+        "    checkEglError(\"eglCreateContext\");\n" + 
+        "    if (context == EGL_NO_CONTEXT) {\n" + 
+        "        printf(\"eglCreateContext failed\\n\");\n" + 
+        "        return 0;\n" + 
+        "    }\n" + 
+        "    printf(\"context=%p \\n\", context);\n" + 
+        "\n" + 
+        "    returnValue = eglMakeCurrent(dpy, surface, surface, context);\n" + 
+        "    checkEglError(\"eglMakeCurrent\", returnValue);\n" + 
+        "    if (returnValue != EGL_TRUE) {\n" + 
+        "        return 0;\n" + 
+        "    }\n" + 
+        "\n" + 
+        "    glClearColor(1,1,1,1);\n" + 
+        "    glClear(GL_COLOR_BUFFER_BIT);\n" + 
+        "\n" + 
+        "    FrameSetup();\n" + 
+        "    while (true)\n" + 
+        "        for (unsigned int i = 0; i < FrameCount; i++) {\n" + 
+        "            Frames[i]();\n" + 
+        "            eglSwapBuffers(dpy, surface);\n" + 
+        "            printf(\"press ENTER after Frame%d \\n\", i);\n" + 
+        "            getchar();\n" + 
+        "        }\n" + 
+        "\n" + 
+        "    return 0;\n" + 
+        "}");
+
+        code.write("#include \"frame_names.h\"\n");
+        code.write("void FrameSetup(){\n");
+
+        codeGenServerState(ctx.serverState);
+        codeGenServerShader(ctx.serverShader);
+        codeGenServerTexture(ctx.serverTexture, true);
+        codeGenServerVertex(ctx.serverVertex);
+
+        code.write("}\n");
+
+        try {
+            codeFile.close();
+            makeFile = new FileWriter("Android.mk", false);
+            make = new PrintWriter(makeFile);
+            make.write("LOCAL_PATH:= $(call my-dir)\n" +
+                    "include $(CLEAR_VARS)\n" +
+                    "LOCAL_SRC_FILES := \\\n");
+        } catch (IOException e) {
+            e.printStackTrace();
+            assert false;
+        }
+    }
+
+    private void codeGenCleanup() {
+        make.write("    frame_setup.cpp \\\n");
+        make.write("    frame_names.cpp \\\n");
+        make.write("#\n");
+        make.write(
+                "LOCAL_SHARED_LIBRARIES := \\\n" + 
+                "    libcutils \\\n" + 
+                "    libutils \\\n" + 
+                "    libEGL \\\n" + 
+                "    libGLESv2 \\\n" + 
+                "    libui \\\n" + 
+                "    libhardware \\\n" + 
+                "    libgui\n" + 
+                "\n" + 
+                "LOCAL_MODULE:= gles2dbg\n" + 
+                "\n" + 
+                "LOCAL_MODULE_TAGS := optional\n" + 
+                "\n" + 
+                "LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES -O0 -g -DDEBUG -UNDEBUG\n" + 
+                "\n" + 
+                "include $(BUILD_EXECUTABLE)");
+        try {
+            dataOut.flush();
+            dataOut.close();
+            codeFile.close();
+            makeFile.close();
+            namesHeaderFile.close();
+            namesSourceFile.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+            assert false;
+        }
+        dataOut = null;
+        code = null;
+        codeFile = null;
+        make = null;
+        makeFile = null;
+
+        bufferNames = null;
+        framebufferNames = null;
+        programNames = null;
+        textureNames = null;
+        shaderNames = null;
+        renderbufferNames = null;
+    }
+
+    private DebugContext dbgCtx;
+    private int count;
+    private IProgressMonitor progress;
+
+    @Override
+    public void run(IProgressMonitor monitor) {
+        progress.beginTask("CodeGenFrames", count + 2);
+        Context ctx = dbgCtx.getFrame(0).startContext.clone();
+        codeGenSetup(ctx);
+        progress.worked(1);
+        for (int i = 0; i < count; i++) {
+            try {
+                codeFile = new FileWriter("frame" + i + ".cpp", false);
+                code = new PrintWriter(codeFile);
+            } catch (IOException e1) {
+                e1.printStackTrace();
+                assert false;
+            }
+            make.format("    frame%d.cpp \\\n", i);
+
+            code.write("#include \"frame_names.h\"\n");
+            code.format("void Frame%d(){\n", i);
+            final Frame frame = dbgCtx.getFrame(i);
+            for (int j = 0; j < frame.size(); j++) {
+                final MessageData msgData = frame.get(j);
+                code.format("/* frame function %d: %s %s*/\n", j, msgData.msg.getFunction(),
+                        MessageFormatter.format(msgData.msg, false));
+                ctx.processMessage(msgData.msg);
+                try {
+                    codeGenFunction(ctx, msgData);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    assert false;
+                }
+            }
+            code.write("}\n");
+            try {
+                codeFile.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+                assert false;
+            }
+            progress.worked(1);
+        }
+        for (int i = 0; i < count; i++)
+            namesHeader.format("void Frame%d();\n", i);
+        namesHeader.format("extern void (* Frames[%d])();\n", count);
+        namesSource.format("void (* Frames[%d])() = {\n", count);
+        for (int i = 0; i < count; i++) {
+            namesSource.format("    Frame%d,\n", i);
+        }
+        namesSource.write("};\n");
+        namesSource.format("const unsigned int FrameCount = %d;\n", count);
+        codeGenCleanup();
+        progress.worked(1);
+    }
+
+    void codeGenFrames(final DebugContext dbgCtx, int count, final Shell shell) {
+        this.dbgCtx = dbgCtx;
+        this.count = count;
+        ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
+        this.progress = dialog.getProgressMonitor();
+        try {
+            dialog.run(false, true, this);
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+            assert false;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        this.dbgCtx = null;
+        this.count = 0;
+        progress = null;
+    }
+
+    void codeGenFrame(final Frame frame) {
+        Context ctx = frame.startContext.clone();
+        codeGenSetup(ctx);
+        try {
+            codeFile = new FileWriter("frame0.cpp", false);
+            code = new PrintWriter(codeFile);
+        } catch (IOException e1) {
+            e1.printStackTrace();
+            assert false;
+        }
+        make.format("    frame0.cpp \\\n");
+        code.write("#include \"frame_names.h\"\n");
+        code.format("void Frame0(){\n");
+        for (int i = 0; i < frame.size(); i++) {
+            final MessageData msgData = frame.get(i);
+            code.format("/* frame function %d: %s %s*/\n", i, msgData.msg.getFunction(),
+                    MessageFormatter.format(msgData.msg, false));
+            ctx.processMessage(msgData.msg);
+            try {
+                codeGenFunction(ctx, msgData);
+            } catch (IOException e) {
+                e.printStackTrace();
+                assert false;
+            }
+        }
+        code.write("}\n");
+        namesHeader.write("void Frame0();\n");
+        namesHeader.write("extern void (* Frames[1])();\n");
+        namesSource.write("void (* Frames[1])() = {Frame0};\n");
+        namesSource.write("const unsigned int FrameCount = 1;\n");
+        codeGenCleanup();
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java b/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
new file mode 100644
index 0000000..122695b
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
@@ -0,0 +1,532 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.DataType;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.glesv2debugger.DebuggerMessage.Message.Prop;
+import com.android.sdklib.util.SparseArray;
+import com.android.sdklib.util.SparseIntArray;
+import com.google.protobuf.ByteString;
+
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+class Frame {
+    public final long filePosition;
+    private int callsCount;
+
+    final Context startContext;
+    private ArrayList<MessageData> calls = new ArrayList<MessageData>();
+
+    Frame(final Context context, final long filePosition) {
+        this.startContext = context.clone();
+        this.filePosition = filePosition;
+    }
+
+    void add(final MessageData msgData) {
+        calls.add(msgData);
+    }
+
+    void increaseCallsCount() {
+        callsCount++;
+    }
+
+    Context computeContext(final MessageData call) {
+        Context ctx = startContext.clone();
+        for (int i = 0; i < calls.size(); i++)
+            if (call == calls.get(i))
+                return ctx;
+            else
+                ctx.processMessage(calls.get(i).msg);
+        assert false;
+        return ctx;
+    }
+
+    int size() {
+        return callsCount;
+    }
+
+    MessageData get(final int i) {
+        return calls.get(i);
+    }
+
+    ArrayList<MessageData> get() {
+        return calls;
+    }
+
+    void unload() {
+        if (calls == null)
+            return;
+        calls.clear();
+        calls = null;
+    }
+
+    void load(final RandomAccessFile file) {
+        if (calls != null && calls.size() == callsCount)
+            return;
+        try {
+            Context ctx = startContext.clone();
+            calls = new ArrayList<MessageData>(callsCount);
+            final long oriPosition = file.getFilePointer();
+            file.seek(filePosition);
+            for (int i = 0; i < callsCount; i++) {
+                int len = file.readInt();
+                if (SampleView.targetByteOrder == ByteOrder.LITTLE_ENDIAN)
+                    len = Integer.reverseBytes(len);
+                final byte[] data = new byte[len];
+                file.read(data);
+                Message msg = Message.parseFrom(data);
+                ctx.processMessage(msg);
+                final MessageData msgData = new MessageData(Display.getCurrent(), msg, ctx);
+                calls.add(msgData);
+            }
+            file.seek(oriPosition);
+        } catch (IOException e) {
+            e.printStackTrace();
+            assert false;
+        }
+    }
+}
+
+class DebugContext {
+    boolean uiUpdate = false;
+    final int contextId;
+    Context currentContext;
+    private ArrayList<Frame> frames = new ArrayList<Frame>(128);
+    private Frame lastFrame;
+    private Frame loadedFrame;
+    private RandomAccessFile file;
+
+    DebugContext(final int contextId) {
+        this.contextId = contextId;
+        currentContext = new Context(contextId);
+        try {
+            file = new RandomAccessFile("0x" + Integer.toHexString(contextId) +
+                    ".gles2dbg", "rw");
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            assert false;
+        }
+    }
+
+    /** write message to file; if frame not null, then increase its call count */
+    void saveMessage(final Message msg, final RandomAccessFile file, Frame frame) {
+        synchronized (file) {
+            if (frame != null)
+                frame.increaseCallsCount();
+            final byte[] data = msg.toByteArray();
+            final ByteBuffer len = ByteBuffer.allocate(4);
+            len.order(SampleView.targetByteOrder);
+            len.putInt(data.length);
+            try {
+                if (SampleView.targetByteOrder == ByteOrder.BIG_ENDIAN)
+                    file.writeInt(data.length);
+                else
+                    file.writeInt(Integer.reverseBytes(data.length));
+                file.write(data);
+            } catch (IOException e) {
+                e.printStackTrace();
+                assert false;
+            }
+        }
+    }
+
+    /**
+     * Caches new Message, and formats into MessageData for current frame; this
+     * function is called exactly once for each new Message
+     */
+    void processMessage(final Message newMsg) {
+        Message msg = newMsg;
+        if (msg.getFunction() == Function.SETPROP) {
+            // GL impl. consts should have been sent before any GL call messages
+            assert frames.size() == 0;
+            assert lastFrame == null;
+            assert msg.getProp() == Prop.GLConstant;
+            switch (GLEnum.valueOf(msg.getArg0())) {
+                case GL_MAX_VERTEX_ATTRIBS:
+                    currentContext.serverVertex = new GLServerVertex(msg.getArg1());
+                    break;
+                case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
+                    currentContext.serverTexture = new GLServerTexture(currentContext,
+                            msg.getArg1());
+                    break;
+                default:
+                    assert false;
+                    return;
+            }
+            saveMessage(msg, file, null);
+            return;
+        }
+
+        if (lastFrame == null) {
+            // first real message after the GL impl. consts
+            synchronized (file) {
+                try {
+                    lastFrame = new Frame(currentContext, file.getFilePointer());
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    assert false;
+                }
+            }
+            synchronized (frames) {
+                frames.add(lastFrame);
+            }
+            assert loadedFrame == null;
+            loadedFrame = lastFrame;
+        }
+        currentContext.processMessage(msg);
+        if (msg.hasDataType() && msg.getDataType() == DataType.ReferencedImage) {
+            // decode referenced image so it doesn't rely on context later on
+            final byte[] referenced = MessageProcessor.lzfDecompressChunks(msg.getData());
+            currentContext.readPixelRef = MessageProcessor.decodeReferencedImage(
+                        currentContext.readPixelRef, referenced);
+            final byte[] decoded = MessageProcessor.lzfCompressChunks(
+                        currentContext.readPixelRef, referenced.length);
+            msg = msg.toBuilder().setDataType(DataType.NonreferencedImage)
+                        .setData(ByteString.copyFrom(decoded)).build();
+        }
+        saveMessage(msg, file, lastFrame);
+        if (loadedFrame == lastFrame) {
+            // frame selected for view, so format MessageData
+            final MessageData msgData = new MessageData(Display.getCurrent(), msg, currentContext);
+            lastFrame.add(msgData);
+            uiUpdate = true;
+        }
+        if (msg.getFunction() != Function.eglSwapBuffers)
+            return;
+        synchronized (frames) {
+            if (loadedFrame != lastFrame)
+                lastFrame.unload();
+            try {
+                frames.add(lastFrame = new Frame(currentContext, file.getFilePointer()));
+                // file.getChannel().force(false);
+                uiUpdate = true;
+            } catch (IOException e) {
+                e.printStackTrace();
+                assert false;
+            }
+        }
+        return;
+    }
+
+    Frame getFrame(int index) {
+        synchronized (frames) {
+            Frame newFrame = frames.get(index);
+            if (loadedFrame != null && loadedFrame != lastFrame && newFrame != loadedFrame) {
+                loadedFrame.unload();
+                uiUpdate = true;
+            }
+            loadedFrame = newFrame;
+            synchronized (file) {
+                loadedFrame.load(file);
+            }
+            return loadedFrame;
+        }
+    }
+
+    int frameCount() {
+        synchronized (frames) {
+            return frames.size();
+        }
+    }
+}
+
+/** aggregate of GL states */
+public class Context implements Cloneable {
+    public final int contextId;
+    public ArrayList<Context> shares = new ArrayList<Context>(); // self too
+    public GLServerVertex serverVertex;
+    public GLServerShader serverShader = new GLServerShader(this);
+    public GLServerState serverState = new GLServerState(this);
+    public GLServerTexture serverTexture;
+
+    byte[] readPixelRef = new byte[0];
+
+    public Context(int contextId) {
+        this.contextId = contextId;
+        shares.add(this);
+    }
+
+    @Override
+    public Context clone() {
+        try {
+            Context copy = (Context) super.clone();
+            // FIXME: context sharing list clone
+            copy.shares = new ArrayList<Context>(1);
+            copy.shares.add(copy);
+            if (serverVertex != null)
+                copy.serverVertex = serverVertex.clone();
+            copy.serverShader = serverShader.clone(copy);
+            copy.serverState = serverState.clone();
+            if (serverTexture != null)
+                copy.serverTexture = serverTexture.clone(copy);
+            // don't need to clone readPixelsRef, since referenced images
+            // are decoded when they are encountered
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+
+    /** mainly updating states */
+    public void processMessage(Message msg) {
+        if (serverVertex.process(msg))
+            return;
+        if (serverShader.processMessage(msg))
+            return;
+        if (serverState.processMessage(msg))
+            return;
+        if (serverTexture.processMessage(msg))
+            return;
+    }
+}
+
+class ContextViewProvider extends LabelProvider implements ITreeContentProvider,
+        ISelectionChangedListener {
+    Context context;
+    final SampleView sampleView;
+
+    ContextViewProvider(final SampleView sampleView) {
+        this.sampleView = sampleView;
+    }
+
+    @Override
+    public void dispose() {
+    }
+
+    @Override
+    public String getText(Object obj) {
+        if (obj == null)
+            return "null";
+        if (obj instanceof Entry) {
+            Entry entry = (Entry) obj;
+            String objStr = "null (or default)";
+            if (entry.obj != null) {
+                objStr = entry.obj.toString();
+                if (entry.obj instanceof Message)
+                    objStr = MessageFormatter.format((Message) entry.obj, false);
+            }
+            return entry.name + " = " + objStr;
+        }
+        return obj.toString();
+    }
+
+    @Override
+    public Image getImage(Object obj) {
+        if (!(obj instanceof Entry))
+            return null;
+        final Entry entry = (Entry) obj;
+        if (!(entry.obj instanceof Message))
+            return null;
+        final Message msg = (Message) entry.obj;
+        switch (msg.getFunction()) {
+            case glTexImage2D:
+            case glTexSubImage2D:
+            case glCopyTexImage2D:
+            case glCopyTexSubImage2D: {
+                entry.image = new MessageData(Display.getCurrent(), msg, null).getImage();
+                if (entry.image == null)
+                    return null;
+                return new Image(Display.getCurrent(), entry.image.getImageData().scaledTo(96, 96));
+            }
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void selectionChanged(SelectionChangedEvent event) {
+        StructuredSelection selection = (StructuredSelection) event
+                .getSelection();
+        if (null == selection)
+            return;
+        final Object obj = selection.getFirstElement();
+        if (!(obj instanceof Entry))
+            return;
+        final Entry entry = (Entry) obj;
+        if (entry.image == null)
+            return;
+        sampleView.tabFolder.setSelection(sampleView.tabItemImage);
+        sampleView.canvas.setBackgroundImage(entry.image);
+        sampleView.canvas.redraw();
+    }
+
+    @Override
+    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+        context = (Context) newInput;
+    }
+
+    class Entry {
+        String name;
+        Object obj;
+        Image image;
+
+        Entry(String name, Object obj) {
+            this.name = name;
+            this.obj = obj;
+        }
+    }
+
+    @Override
+    public Object[] getElements(Object inputElement) {
+        if (inputElement != context)
+            return null;
+        return getChildren(new Entry("Context", inputElement));
+    }
+
+    @Override
+    public Object[] getChildren(Object parentElement) {
+        if (!(parentElement instanceof Entry))
+            return null;
+        Entry entry = (Entry) parentElement;
+        ArrayList<Object> children = new ArrayList<Object>();
+        if (entry.obj == context.serverState.enableDisables) {
+            for (int i = 0; i < context.serverState.enableDisables.size(); i++) {
+                final int key = context.serverState.enableDisables.keyAt(i);
+                final int value = context.serverState.enableDisables.valueAt(i);
+                children.add(GLEnum.valueOf(key).name() + " = " + value);
+            }
+        } else if (entry.obj == context.serverState.integers) {
+            for (int i = 0; i < context.serverState.integers.size(); i++) {
+                final int key = context.serverState.integers.keyAt(i);
+                final Message val = context.serverState.integers.valueAt(i);
+                if (val != null)
+                    children.add(GLEnum.valueOf(key).name() + " : " +
+                            MessageFormatter.format(val, false));
+                else
+                    children.add(GLEnum.valueOf(key).name() + " : default");
+            }
+        } else if (entry.obj == context.serverState.lastSetter) {
+            for (int i = 0; i < context.serverState.lastSetter.size(); i++) {
+                final int key = context.serverState.lastSetter.keyAt(i);
+                final Message msg = context.serverState.lastSetter.valueAt(i);
+                if (msg == null)
+                    children.add(Function.valueOf(key).name() + " : default");
+                else
+                    children.add(Function.valueOf(key).name() + " : "
+                            + MessageFormatter.format(msg, false));
+            }
+        } else if (entry.obj instanceof SparseArray) {
+            SparseArray<?> sa = (SparseArray<?>) entry.obj;
+            for (int i = 0; i < sa.size(); i++)
+                children.add(new Entry("[" + sa.keyAt(i) + "]", sa.valueAt(i)));
+        } else if (entry.obj instanceof Map) {
+            Set<?> set = ((Map<?, ?>) entry.obj).entrySet();
+            for (Object o : set) {
+                Map.Entry e = (Map.Entry) o;
+                children.add(new Entry(e.getKey().toString(), e.getValue()));
+            }
+        } else if (entry.obj instanceof SparseIntArray) {
+            SparseIntArray sa = (SparseIntArray) entry.obj;
+            for (int i = 0; i < sa.size(); i++)
+                children.add("[" + sa.keyAt(i) + "] = " + sa.valueAt(i));
+        } else if (entry.obj instanceof Collection) {
+            Collection<?> collection = (Collection<?>) entry.obj;
+            for (Object o : collection)
+                children.add(new Entry("[?]", o));
+        } else if (entry.obj.getClass().isArray()) {
+            for (int i = 0; i < Array.getLength(entry.obj); i++)
+                children.add(new Entry("[" + i + "]", Array.get(entry.obj, i)));
+        } else {
+            Field[] fields = entry.obj.getClass().getFields();
+            for (Field f : fields) {
+                try {
+                    children.add(new Entry(f.getName(), f.get(entry.obj)));
+                } catch (IllegalArgumentException e) {
+                    e.printStackTrace();
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return children.toArray();
+    }
+
+    @Override
+    public Object getParent(Object element) {
+        return null;
+    }
+
+    @Override
+    public boolean hasChildren(Object element) {
+        if (element == null)
+            return false;
+        if (!(element instanceof Entry))
+            return false;
+        Object obj = ((Entry) element).obj;
+        if (obj == null)
+            return false;
+        if (obj instanceof SparseArray)
+            return ((SparseArray<?>) obj).size() > 0;
+        else if (obj instanceof SparseIntArray)
+            return ((SparseIntArray) obj).size() > 0;
+        else if (obj instanceof Collection)
+            return ((Collection<?>) obj).size() > 0;
+        else if (obj instanceof Map)
+            return ((Map<?, ?>) obj).size() > 0;
+        else if (obj.getClass().isArray())
+            return Array.getLength(obj) > 0;
+        else if (obj instanceof Message)
+            return false;
+        else if (isPrimitive(obj))
+            return false;
+        else if (obj.getClass().equals(String.class))
+            return false;
+        else if (obj.getClass().equals(Message.class))
+            return false;
+        else if (obj instanceof GLEnum)
+            return false;
+        return obj.getClass().getFields().length > 0;
+    }
+
+    static boolean isPrimitive(final Object obj) {
+        final Class<? extends Object> c = obj.getClass();
+        if (c.isPrimitive())
+            return true;
+        if (c == Integer.class)
+            return true;
+        if (c == Boolean.class)
+            return true;
+        if (c == Float.class)
+            return true;
+        if (c == Short.class)
+            return true;
+        return false;
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java b/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java
new file mode 100644
index 0000000..94133f5
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java
@@ -0,0 +1,1713 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: debugger_message.proto
+
+package com.android.glesv2debugger;
+
+public final class DebuggerMessage {
+  private DebuggerMessage() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+  public static final class Message extends
+      com.google.protobuf.GeneratedMessageLite {
+    // Use Message.newBuilder() to construct.
+    private Message() {
+      initFields();
+    }
+    private Message(boolean noInit) {}
+    
+    private static final Message defaultInstance;
+    public static Message getDefaultInstance() {
+      return defaultInstance;
+    }
+    
+    public Message getDefaultInstanceForType() {
+      return defaultInstance;
+    }
+    
+    public enum Function
+        implements com.google.protobuf.Internal.EnumLite {
+      glActiveTexture(0, 0),
+      glAttachShader(1, 1),
+      glBindAttribLocation(2, 2),
+      glBindBuffer(3, 3),
+      glBindFramebuffer(4, 4),
+      glBindRenderbuffer(5, 5),
+      glBindTexture(6, 6),
+      glBlendColor(7, 7),
+      glBlendEquation(8, 8),
+      glBlendEquationSeparate(9, 9),
+      glBlendFunc(10, 10),
+      glBlendFuncSeparate(11, 11),
+      glBufferData(12, 12),
+      glBufferSubData(13, 13),
+      glCheckFramebufferStatus(14, 14),
+      glClear(15, 15),
+      glClearColor(16, 16),
+      glClearDepthf(17, 17),
+      glClearStencil(18, 18),
+      glColorMask(19, 19),
+      glCompileShader(20, 20),
+      glCompressedTexImage2D(21, 21),
+      glCompressedTexSubImage2D(22, 22),
+      glCopyTexImage2D(23, 23),
+      glCopyTexSubImage2D(24, 24),
+      glCreateProgram(25, 25),
+      glCreateShader(26, 26),
+      glCullFace(27, 27),
+      glDeleteBuffers(28, 28),
+      glDeleteFramebuffers(29, 29),
+      glDeleteProgram(30, 30),
+      glDeleteRenderbuffers(31, 31),
+      glDeleteShader(32, 32),
+      glDeleteTextures(33, 33),
+      glDepthFunc(34, 34),
+      glDepthMask(35, 35),
+      glDepthRangef(36, 36),
+      glDetachShader(37, 37),
+      glDisable(38, 38),
+      glDisableVertexAttribArray(39, 39),
+      glDrawArrays(40, 40),
+      glDrawElements(41, 41),
+      glEnable(42, 42),
+      glEnableVertexAttribArray(43, 43),
+      glFinish(44, 44),
+      glFlush(45, 45),
+      glFramebufferRenderbuffer(46, 46),
+      glFramebufferTexture2D(47, 47),
+      glFrontFace(48, 48),
+      glGenBuffers(49, 49),
+      glGenerateMipmap(50, 50),
+      glGenFramebuffers(51, 51),
+      glGenRenderbuffers(52, 52),
+      glGenTextures(53, 53),
+      glGetActiveAttrib(54, 54),
+      glGetActiveUniform(55, 55),
+      glGetAttachedShaders(56, 56),
+      glGetAttribLocation(57, 57),
+      glGetBooleanv(58, 58),
+      glGetBufferParameteriv(59, 59),
+      glGetError(60, 60),
+      glGetFloatv(61, 61),
+      glGetFramebufferAttachmentParameteriv(62, 62),
+      glGetIntegerv(63, 63),
+      glGetProgramiv(64, 64),
+      glGetProgramInfoLog(65, 65),
+      glGetRenderbufferParameteriv(66, 66),
+      glGetShaderiv(67, 67),
+      glGetShaderInfoLog(68, 68),
+      glGetShaderPrecisionFormat(69, 69),
+      glGetShaderSource(70, 70),
+      glGetString(71, 71),
+      glGetTexParameterfv(72, 72),
+      glGetTexParameteriv(73, 73),
+      glGetUniformfv(74, 74),
+      glGetUniformiv(75, 75),
+      glGetUniformLocation(76, 76),
+      glGetVertexAttribfv(77, 77),
+      glGetVertexAttribiv(78, 78),
+      glGetVertexAttribPointerv(79, 79),
+      glHint(80, 80),
+      glIsBuffer(81, 81),
+      glIsEnabled(82, 82),
+      glIsFramebuffer(83, 83),
+      glIsProgram(84, 84),
+      glIsRenderbuffer(85, 85),
+      glIsShader(86, 86),
+      glIsTexture(87, 87),
+      glLineWidth(88, 88),
+      glLinkProgram(89, 89),
+      glPixelStorei(90, 90),
+      glPolygonOffset(91, 91),
+      glReadPixels(92, 92),
+      glReleaseShaderCompiler(93, 93),
+      glRenderbufferStorage(94, 94),
+      glSampleCoverage(95, 95),
+      glScissor(96, 96),
+      glShaderBinary(97, 97),
+      glShaderSource(98, 98),
+      glStencilFunc(99, 99),
+      glStencilFuncSeparate(100, 100),
+      glStencilMask(101, 101),
+      glStencilMaskSeparate(102, 102),
+      glStencilOp(103, 103),
+      glStencilOpSeparate(104, 104),
+      glTexImage2D(105, 105),
+      glTexParameterf(106, 106),
+      glTexParameterfv(107, 107),
+      glTexParameteri(108, 108),
+      glTexParameteriv(109, 109),
+      glTexSubImage2D(110, 110),
+      glUniform1f(111, 111),
+      glUniform1fv(112, 112),
+      glUniform1i(113, 113),
+      glUniform1iv(114, 114),
+      glUniform2f(115, 115),
+      glUniform2fv(116, 116),
+      glUniform2i(117, 117),
+      glUniform2iv(118, 118),
+      glUniform3f(119, 119),
+      glUniform3fv(120, 120),
+      glUniform3i(121, 121),
+      glUniform3iv(122, 122),
+      glUniform4f(123, 123),
+      glUniform4fv(124, 124),
+      glUniform4i(125, 125),
+      glUniform4iv(126, 126),
+      glUniformMatrix2fv(127, 127),
+      glUniformMatrix3fv(128, 128),
+      glUniformMatrix4fv(129, 129),
+      glUseProgram(130, 130),
+      glValidateProgram(131, 131),
+      glVertexAttrib1f(132, 132),
+      glVertexAttrib1fv(133, 133),
+      glVertexAttrib2f(134, 134),
+      glVertexAttrib2fv(135, 135),
+      glVertexAttrib3f(136, 136),
+      glVertexAttrib3fv(137, 137),
+      glVertexAttrib4f(138, 138),
+      glVertexAttrib4fv(139, 139),
+      glVertexAttribPointer(140, 140),
+      glViewport(141, 141),
+      eglGetDisplay(142, 142),
+      eglInitialize(143, 143),
+      eglTerminate(144, 144),
+      eglGetConfigs(145, 145),
+      eglChooseConfig(146, 146),
+      eglGetConfigAttrib(147, 147),
+      eglCreateWindowSurface(148, 148),
+      eglCreatePixmapSurface(149, 149),
+      eglCreatePbufferSurface(150, 150),
+      eglDestroySurface(151, 151),
+      eglQuerySurface(152, 152),
+      eglCreateContext(153, 153),
+      eglDestroyContext(154, 154),
+      eglMakeCurrent(155, 155),
+      eglGetCurrentContext(156, 156),
+      eglGetCurrentSurface(157, 157),
+      eglGetCurrentDisplay(158, 158),
+      eglQueryContext(159, 159),
+      eglWaitGL(160, 160),
+      eglWaitNative(161, 161),
+      eglSwapBuffers(162, 162),
+      eglCopyBuffers(163, 163),
+      eglGetError(164, 164),
+      eglQueryString(165, 165),
+      eglGetProcAddress(166, 166),
+      eglSurfaceAttrib(167, 167),
+      eglBindTexImage(168, 168),
+      eglReleaseTexImage(169, 169),
+      eglSwapInterval(170, 170),
+      eglBindAPI(171, 171),
+      eglQueryAPI(172, 172),
+      eglWaitClient(173, 173),
+      eglReleaseThread(174, 174),
+      eglCreatePbufferFromClientBuffer(175, 175),
+      eglLockSurfaceKHR(176, 176),
+      eglUnlockSurfaceKHR(177, 177),
+      eglCreateImageKHR(178, 178),
+      eglDestroyImageKHR(179, 179),
+      eglCreateSyncKHR(180, 180),
+      eglDestroySyncKHR(181, 181),
+      eglClientWaitSyncKHR(182, 182),
+      eglGetSyncAttribKHR(183, 183),
+      eglSetSwapRectangleANDROID(184, 184),
+      eglGetRenderBufferANDROID(185, 185),
+      ACK(186, 186),
+      NEG(187, 187),
+      CONTINUE(188, 188),
+      SKIP(189, 189),
+      SETPROP(190, 190),
+      ;
+      
+      
+      public final int getNumber() { return value; }
+      
+      public static Function valueOf(int value) {
+        switch (value) {
+          case 0: return glActiveTexture;
+          case 1: return glAttachShader;
+          case 2: return glBindAttribLocation;
+          case 3: return glBindBuffer;
+          case 4: return glBindFramebuffer;
+          case 5: return glBindRenderbuffer;
+          case 6: return glBindTexture;
+          case 7: return glBlendColor;
+          case 8: return glBlendEquation;
+          case 9: return glBlendEquationSeparate;
+          case 10: return glBlendFunc;
+          case 11: return glBlendFuncSeparate;
+          case 12: return glBufferData;
+          case 13: return glBufferSubData;
+          case 14: return glCheckFramebufferStatus;
+          case 15: return glClear;
+          case 16: return glClearColor;
+          case 17: return glClearDepthf;
+          case 18: return glClearStencil;
+          case 19: return glColorMask;
+          case 20: return glCompileShader;
+          case 21: return glCompressedTexImage2D;
+          case 22: return glCompressedTexSubImage2D;
+          case 23: return glCopyTexImage2D;
+          case 24: return glCopyTexSubImage2D;
+          case 25: return glCreateProgram;
+          case 26: return glCreateShader;
+          case 27: return glCullFace;
+          case 28: return glDeleteBuffers;
+          case 29: return glDeleteFramebuffers;
+          case 30: return glDeleteProgram;
+          case 31: return glDeleteRenderbuffers;
+          case 32: return glDeleteShader;
+          case 33: return glDeleteTextures;
+          case 34: return glDepthFunc;
+          case 35: return glDepthMask;
+          case 36: return glDepthRangef;
+          case 37: return glDetachShader;
+          case 38: return glDisable;
+          case 39: return glDisableVertexAttribArray;
+          case 40: return glDrawArrays;
+          case 41: return glDrawElements;
+          case 42: return glEnable;
+          case 43: return glEnableVertexAttribArray;
+          case 44: return glFinish;
+          case 45: return glFlush;
+          case 46: return glFramebufferRenderbuffer;
+          case 47: return glFramebufferTexture2D;
+          case 48: return glFrontFace;
+          case 49: return glGenBuffers;
+          case 50: return glGenerateMipmap;
+          case 51: return glGenFramebuffers;
+          case 52: return glGenRenderbuffers;
+          case 53: return glGenTextures;
+          case 54: return glGetActiveAttrib;
+          case 55: return glGetActiveUniform;
+          case 56: return glGetAttachedShaders;
+          case 57: return glGetAttribLocation;
+          case 58: return glGetBooleanv;
+          case 59: return glGetBufferParameteriv;
+          case 60: return glGetError;
+          case 61: return glGetFloatv;
+          case 62: return glGetFramebufferAttachmentParameteriv;
+          case 63: return glGetIntegerv;
+          case 64: return glGetProgramiv;
+          case 65: return glGetProgramInfoLog;
+          case 66: return glGetRenderbufferParameteriv;
+          case 67: return glGetShaderiv;
+          case 68: return glGetShaderInfoLog;
+          case 69: return glGetShaderPrecisionFormat;
+          case 70: return glGetShaderSource;
+          case 71: return glGetString;
+          case 72: return glGetTexParameterfv;
+          case 73: return glGetTexParameteriv;
+          case 74: return glGetUniformfv;
+          case 75: return glGetUniformiv;
+          case 76: return glGetUniformLocation;
+          case 77: return glGetVertexAttribfv;
+          case 78: return glGetVertexAttribiv;
+          case 79: return glGetVertexAttribPointerv;
+          case 80: return glHint;
+          case 81: return glIsBuffer;
+          case 82: return glIsEnabled;
+          case 83: return glIsFramebuffer;
+          case 84: return glIsProgram;
+          case 85: return glIsRenderbuffer;
+          case 86: return glIsShader;
+          case 87: return glIsTexture;
+          case 88: return glLineWidth;
+          case 89: return glLinkProgram;
+          case 90: return glPixelStorei;
+          case 91: return glPolygonOffset;
+          case 92: return glReadPixels;
+          case 93: return glReleaseShaderCompiler;
+          case 94: return glRenderbufferStorage;
+          case 95: return glSampleCoverage;
+          case 96: return glScissor;
+          case 97: return glShaderBinary;
+          case 98: return glShaderSource;
+          case 99: return glStencilFunc;
+          case 100: return glStencilFuncSeparate;
+          case 101: return glStencilMask;
+          case 102: return glStencilMaskSeparate;
+          case 103: return glStencilOp;
+          case 104: return glStencilOpSeparate;
+          case 105: return glTexImage2D;
+          case 106: return glTexParameterf;
+          case 107: return glTexParameterfv;
+          case 108: return glTexParameteri;
+          case 109: return glTexParameteriv;
+          case 110: return glTexSubImage2D;
+          case 111: return glUniform1f;
+          case 112: return glUniform1fv;
+          case 113: return glUniform1i;
+          case 114: return glUniform1iv;
+          case 115: return glUniform2f;
+          case 116: return glUniform2fv;
+          case 117: return glUniform2i;
+          case 118: return glUniform2iv;
+          case 119: return glUniform3f;
+          case 120: return glUniform3fv;
+          case 121: return glUniform3i;
+          case 122: return glUniform3iv;
+          case 123: return glUniform4f;
+          case 124: return glUniform4fv;
+          case 125: return glUniform4i;
+          case 126: return glUniform4iv;
+          case 127: return glUniformMatrix2fv;
+          case 128: return glUniformMatrix3fv;
+          case 129: return glUniformMatrix4fv;
+          case 130: return glUseProgram;
+          case 131: return glValidateProgram;
+          case 132: return glVertexAttrib1f;
+          case 133: return glVertexAttrib1fv;
+          case 134: return glVertexAttrib2f;
+          case 135: return glVertexAttrib2fv;
+          case 136: return glVertexAttrib3f;
+          case 137: return glVertexAttrib3fv;
+          case 138: return glVertexAttrib4f;
+          case 139: return glVertexAttrib4fv;
+          case 140: return glVertexAttribPointer;
+          case 141: return glViewport;
+          case 142: return eglGetDisplay;
+          case 143: return eglInitialize;
+          case 144: return eglTerminate;
+          case 145: return eglGetConfigs;
+          case 146: return eglChooseConfig;
+          case 147: return eglGetConfigAttrib;
+          case 148: return eglCreateWindowSurface;
+          case 149: return eglCreatePixmapSurface;
+          case 150: return eglCreatePbufferSurface;
+          case 151: return eglDestroySurface;
+          case 152: return eglQuerySurface;
+          case 153: return eglCreateContext;
+          case 154: return eglDestroyContext;
+          case 155: return eglMakeCurrent;
+          case 156: return eglGetCurrentContext;
+          case 157: return eglGetCurrentSurface;
+          case 158: return eglGetCurrentDisplay;
+          case 159: return eglQueryContext;
+          case 160: return eglWaitGL;
+          case 161: return eglWaitNative;
+          case 162: return eglSwapBuffers;
+          case 163: return eglCopyBuffers;
+          case 164: return eglGetError;
+          case 165: return eglQueryString;
+          case 166: return eglGetProcAddress;
+          case 167: return eglSurfaceAttrib;
+          case 168: return eglBindTexImage;
+          case 169: return eglReleaseTexImage;
+          case 170: return eglSwapInterval;
+          case 171: return eglBindAPI;
+          case 172: return eglQueryAPI;
+          case 173: return eglWaitClient;
+          case 174: return eglReleaseThread;
+          case 175: return eglCreatePbufferFromClientBuffer;
+          case 176: return eglLockSurfaceKHR;
+          case 177: return eglUnlockSurfaceKHR;
+          case 178: return eglCreateImageKHR;
+          case 179: return eglDestroyImageKHR;
+          case 180: return eglCreateSyncKHR;
+          case 181: return eglDestroySyncKHR;
+          case 182: return eglClientWaitSyncKHR;
+          case 183: return eglGetSyncAttribKHR;
+          case 184: return eglSetSwapRectangleANDROID;
+          case 185: return eglGetRenderBufferANDROID;
+          case 186: return ACK;
+          case 187: return NEG;
+          case 188: return CONTINUE;
+          case 189: return SKIP;
+          case 190: return SETPROP;
+          default: return null;
+        }
+      }
+      
+      public static com.google.protobuf.Internal.EnumLiteMap<Function>
+          internalGetValueMap() {
+        return internalValueMap;
+      }
+      private static com.google.protobuf.Internal.EnumLiteMap<Function>
+          internalValueMap =
+            new com.google.protobuf.Internal.EnumLiteMap<Function>() {
+              public Function findValueByNumber(int number) {
+                return Function.valueOf(number)
+      ;        }
+            };
+      
+      private final int index;
+      private final int value;
+      private Function(int index, int value) {
+        this.index = index;
+        this.value = value;
+      }
+      
+      // @@protoc_insertion_point(enum_scope:com.android.glesv2debugger.Message.Function)
+    }
+    
+    public enum Type
+        implements com.google.protobuf.Internal.EnumLite {
+      BeforeCall(0, 0),
+      AfterCall(1, 1),
+      AfterGeneratedCall(2, 2),
+      Response(3, 3),
+      CompleteCall(4, 4),
+      ;
+      
+      
+      public final int getNumber() { return value; }
+      
+      public static Type valueOf(int value) {
+        switch (value) {
+          case 0: return BeforeCall;
+          case 1: return AfterCall;
+          case 2: return AfterGeneratedCall;
+          case 3: return Response;
+          case 4: return CompleteCall;
+          default: return null;
+        }
+      }
+      
+      public static com.google.protobuf.Internal.EnumLiteMap<Type>
+          internalGetValueMap() {
+        return internalValueMap;
+      }
+      private static com.google.protobuf.Internal.EnumLiteMap<Type>
+          internalValueMap =
+            new com.google.protobuf.Internal.EnumLiteMap<Type>() {
+              public Type findValueByNumber(int number) {
+                return Type.valueOf(number)
+      ;        }
+            };
+      
+      private final int index;
+      private final int value;
+      private Type(int index, int value) {
+        this.index = index;
+        this.value = value;
+      }
+      
+      // @@protoc_insertion_point(enum_scope:com.android.glesv2debugger.Message.Type)
+    }
+    
+    public enum DataType
+        implements com.google.protobuf.Internal.EnumLite {
+      ReferencedImage(0, 0),
+      NonreferencedImage(1, 1),
+      ;
+      
+      
+      public final int getNumber() { return value; }
+      
+      public static DataType valueOf(int value) {
+        switch (value) {
+          case 0: return ReferencedImage;
+          case 1: return NonreferencedImage;
+          default: return null;
+        }
+      }
+      
+      public static com.google.protobuf.Internal.EnumLiteMap<DataType>
+          internalGetValueMap() {
+        return internalValueMap;
+      }
+      private static com.google.protobuf.Internal.EnumLiteMap<DataType>
+          internalValueMap =
+            new com.google.protobuf.Internal.EnumLiteMap<DataType>() {
+              public DataType findValueByNumber(int number) {
+                return DataType.valueOf(number)
+      ;        }
+            };
+      
+      private final int index;
+      private final int value;
+      private DataType(int index, int value) {
+        this.index = index;
+        this.value = value;
+      }
+      
+      // @@protoc_insertion_point(enum_scope:com.android.glesv2debugger.Message.DataType)
+    }
+    
+    public enum Prop
+        implements com.google.protobuf.Internal.EnumLite {
+      CaptureDraw(0, 0),
+      TimeMode(1, 1),
+      ExpectResponse(2, 2),
+      CaptureSwap(3, 3),
+      GLConstant(4, 4),
+      ;
+      
+      
+      public final int getNumber() { return value; }
+      
+      public static Prop valueOf(int value) {
+        switch (value) {
+          case 0: return CaptureDraw;
+          case 1: return TimeMode;
+          case 2: return ExpectResponse;
+          case 3: return CaptureSwap;
+          case 4: return GLConstant;
+          default: return null;
+        }
+      }
+      
+      public static com.google.protobuf.Internal.EnumLiteMap<Prop>
+          internalGetValueMap() {
+        return internalValueMap;
+      }
+      private static com.google.protobuf.Internal.EnumLiteMap<Prop>
+          internalValueMap =
+            new com.google.protobuf.Internal.EnumLiteMap<Prop>() {
+              public Prop findValueByNumber(int number) {
+                return Prop.valueOf(number)
+      ;        }
+            };
+      
+      private final int index;
+      private final int value;
+      private Prop(int index, int value) {
+        this.index = index;
+        this.value = value;
+      }
+      
+      // @@protoc_insertion_point(enum_scope:com.android.glesv2debugger.Message.Prop)
+    }
+    
+    // required int32 context_id = 1;
+    public static final int CONTEXT_ID_FIELD_NUMBER = 1;
+    private boolean hasContextId;
+    private int contextId_ = 0;
+    public boolean hasContextId() { return hasContextId; }
+    public int getContextId() { return contextId_; }
+    
+    // required .com.android.glesv2debugger.Message.Function function = 2 [default = NEG];
+    public static final int FUNCTION_FIELD_NUMBER = 2;
+    private boolean hasFunction;
+    private com.android.glesv2debugger.DebuggerMessage.Message.Function function_;
+    public boolean hasFunction() { return hasFunction; }
+    public com.android.glesv2debugger.DebuggerMessage.Message.Function getFunction() { return function_; }
+    
+    // required .com.android.glesv2debugger.Message.Type type = 3;
+    public static final int TYPE_FIELD_NUMBER = 3;
+    private boolean hasType;
+    private com.android.glesv2debugger.DebuggerMessage.Message.Type type_;
+    public boolean hasType() { return hasType; }
+    public com.android.glesv2debugger.DebuggerMessage.Message.Type getType() { return type_; }
+    
+    // required bool expect_response = 4;
+    public static final int EXPECT_RESPONSE_FIELD_NUMBER = 4;
+    private boolean hasExpectResponse;
+    private boolean expectResponse_ = false;
+    public boolean hasExpectResponse() { return hasExpectResponse; }
+    public boolean getExpectResponse() { return expectResponse_; }
+    
+    // optional int32 ret = 5;
+    public static final int RET_FIELD_NUMBER = 5;
+    private boolean hasRet;
+    private int ret_ = 0;
+    public boolean hasRet() { return hasRet; }
+    public int getRet() { return ret_; }
+    
+    // optional int32 arg0 = 6;
+    public static final int ARG0_FIELD_NUMBER = 6;
+    private boolean hasArg0;
+    private int arg0_ = 0;
+    public boolean hasArg0() { return hasArg0; }
+    public int getArg0() { return arg0_; }
+    
+    // optional int32 arg1 = 7;
+    public static final int ARG1_FIELD_NUMBER = 7;
+    private boolean hasArg1;
+    private int arg1_ = 0;
+    public boolean hasArg1() { return hasArg1; }
+    public int getArg1() { return arg1_; }
+    
+    // optional int32 arg2 = 8;
+    public static final int ARG2_FIELD_NUMBER = 8;
+    private boolean hasArg2;
+    private int arg2_ = 0;
+    public boolean hasArg2() { return hasArg2; }
+    public int getArg2() { return arg2_; }
+    
+    // optional int32 arg3 = 9;
+    public static final int ARG3_FIELD_NUMBER = 9;
+    private boolean hasArg3;
+    private int arg3_ = 0;
+    public boolean hasArg3() { return hasArg3; }
+    public int getArg3() { return arg3_; }
+    
+    // optional int32 arg4 = 16;
+    public static final int ARG4_FIELD_NUMBER = 16;
+    private boolean hasArg4;
+    private int arg4_ = 0;
+    public boolean hasArg4() { return hasArg4; }
+    public int getArg4() { return arg4_; }
+    
+    // optional int32 arg5 = 17;
+    public static final int ARG5_FIELD_NUMBER = 17;
+    private boolean hasArg5;
+    private int arg5_ = 0;
+    public boolean hasArg5() { return hasArg5; }
+    public int getArg5() { return arg5_; }
+    
+    // optional int32 arg6 = 18;
+    public static final int ARG6_FIELD_NUMBER = 18;
+    private boolean hasArg6;
+    private int arg6_ = 0;
+    public boolean hasArg6() { return hasArg6; }
+    public int getArg6() { return arg6_; }
+    
+    // optional int32 arg7 = 19;
+    public static final int ARG7_FIELD_NUMBER = 19;
+    private boolean hasArg7;
+    private int arg7_ = 0;
+    public boolean hasArg7() { return hasArg7; }
+    public int getArg7() { return arg7_; }
+    
+    // optional int32 arg8 = 20;
+    public static final int ARG8_FIELD_NUMBER = 20;
+    private boolean hasArg8;
+    private int arg8_ = 0;
+    public boolean hasArg8() { return hasArg8; }
+    public int getArg8() { return arg8_; }
+    
+    // optional bytes data = 10;
+    public static final int DATA_FIELD_NUMBER = 10;
+    private boolean hasData;
+    private com.google.protobuf.ByteString data_ = com.google.protobuf.ByteString.EMPTY;
+    public boolean hasData() { return hasData; }
+    public com.google.protobuf.ByteString getData() { return data_; }
+    
+    // optional .com.android.glesv2debugger.Message.DataType data_type = 23;
+    public static final int DATA_TYPE_FIELD_NUMBER = 23;
+    private boolean hasDataType;
+    private com.android.glesv2debugger.DebuggerMessage.Message.DataType dataType_;
+    public boolean hasDataType() { return hasDataType; }
+    public com.android.glesv2debugger.DebuggerMessage.Message.DataType getDataType() { return dataType_; }
+    
+    // optional int32 pixel_format = 24;
+    public static final int PIXEL_FORMAT_FIELD_NUMBER = 24;
+    private boolean hasPixelFormat;
+    private int pixelFormat_ = 0;
+    public boolean hasPixelFormat() { return hasPixelFormat; }
+    public int getPixelFormat() { return pixelFormat_; }
+    
+    // optional int32 pixel_type = 25;
+    public static final int PIXEL_TYPE_FIELD_NUMBER = 25;
+    private boolean hasPixelType;
+    private int pixelType_ = 0;
+    public boolean hasPixelType() { return hasPixelType; }
+    public int getPixelType() { return pixelType_; }
+    
+    // optional int32 image_width = 26;
+    public static final int IMAGE_WIDTH_FIELD_NUMBER = 26;
+    private boolean hasImageWidth;
+    private int imageWidth_ = 0;
+    public boolean hasImageWidth() { return hasImageWidth; }
+    public int getImageWidth() { return imageWidth_; }
+    
+    // optional int32 image_height = 27;
+    public static final int IMAGE_HEIGHT_FIELD_NUMBER = 27;
+    private boolean hasImageHeight;
+    private int imageHeight_ = 0;
+    public boolean hasImageHeight() { return hasImageHeight; }
+    public int getImageHeight() { return imageHeight_; }
+    
+    // optional float time = 11;
+    public static final int TIME_FIELD_NUMBER = 11;
+    private boolean hasTime;
+    private float time_ = 0F;
+    public boolean hasTime() { return hasTime; }
+    public float getTime() { return time_; }
+    
+    // optional .com.android.glesv2debugger.Message.Prop prop = 21;
+    public static final int PROP_FIELD_NUMBER = 21;
+    private boolean hasProp;
+    private com.android.glesv2debugger.DebuggerMessage.Message.Prop prop_;
+    public boolean hasProp() { return hasProp; }
+    public com.android.glesv2debugger.DebuggerMessage.Message.Prop getProp() { return prop_; }
+    
+    // optional float clock = 22;
+    public static final int CLOCK_FIELD_NUMBER = 22;
+    private boolean hasClock;
+    private float clock_ = 0F;
+    public boolean hasClock() { return hasClock; }
+    public float getClock() { return clock_; }
+    
+    private void initFields() {
+      function_ = com.android.glesv2debugger.DebuggerMessage.Message.Function.NEG;
+      type_ = com.android.glesv2debugger.DebuggerMessage.Message.Type.BeforeCall;
+      dataType_ = com.android.glesv2debugger.DebuggerMessage.Message.DataType.ReferencedImage;
+      prop_ = com.android.glesv2debugger.DebuggerMessage.Message.Prop.CaptureDraw;
+    }
+    public final boolean isInitialized() {
+      if (!hasContextId) return false;
+      if (!hasFunction) return false;
+      if (!hasType) return false;
+      if (!hasExpectResponse) return false;
+      return true;
+    }
+    
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      if (hasContextId()) {
+        output.writeInt32(1, getContextId());
+      }
+      if (hasFunction()) {
+        output.writeEnum(2, getFunction().getNumber());
+      }
+      if (hasType()) {
+        output.writeEnum(3, getType().getNumber());
+      }
+      if (hasExpectResponse()) {
+        output.writeBool(4, getExpectResponse());
+      }
+      if (hasRet()) {
+        output.writeInt32(5, getRet());
+      }
+      if (hasArg0()) {
+        output.writeInt32(6, getArg0());
+      }
+      if (hasArg1()) {
+        output.writeInt32(7, getArg1());
+      }
+      if (hasArg2()) {
+        output.writeInt32(8, getArg2());
+      }
+      if (hasArg3()) {
+        output.writeInt32(9, getArg3());
+      }
+      if (hasData()) {
+        output.writeBytes(10, getData());
+      }
+      if (hasTime()) {
+        output.writeFloat(11, getTime());
+      }
+      if (hasArg4()) {
+        output.writeInt32(16, getArg4());
+      }
+      if (hasArg5()) {
+        output.writeInt32(17, getArg5());
+      }
+      if (hasArg6()) {
+        output.writeInt32(18, getArg6());
+      }
+      if (hasArg7()) {
+        output.writeInt32(19, getArg7());
+      }
+      if (hasArg8()) {
+        output.writeInt32(20, getArg8());
+      }
+      if (hasProp()) {
+        output.writeEnum(21, getProp().getNumber());
+      }
+      if (hasClock()) {
+        output.writeFloat(22, getClock());
+      }
+      if (hasDataType()) {
+        output.writeEnum(23, getDataType().getNumber());
+      }
+      if (hasPixelFormat()) {
+        output.writeInt32(24, getPixelFormat());
+      }
+      if (hasPixelType()) {
+        output.writeInt32(25, getPixelType());
+      }
+      if (hasImageWidth()) {
+        output.writeInt32(26, getImageWidth());
+      }
+      if (hasImageHeight()) {
+        output.writeInt32(27, getImageHeight());
+      }
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public int getSerializedSize() {
+      int size = memoizedSerializedSize;
+      if (size != -1) return size;
+    
+      size = 0;
+      if (hasContextId()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, getContextId());
+      }
+      if (hasFunction()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(2, getFunction().getNumber());
+      }
+      if (hasType()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(3, getType().getNumber());
+      }
+      if (hasExpectResponse()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(4, getExpectResponse());
+      }
+      if (hasRet()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(5, getRet());
+      }
+      if (hasArg0()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(6, getArg0());
+      }
+      if (hasArg1()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(7, getArg1());
+      }
+      if (hasArg2()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(8, getArg2());
+      }
+      if (hasArg3()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(9, getArg3());
+      }
+      if (hasData()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(10, getData());
+      }
+      if (hasTime()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeFloatSize(11, getTime());
+      }
+      if (hasArg4()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(16, getArg4());
+      }
+      if (hasArg5()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(17, getArg5());
+      }
+      if (hasArg6()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(18, getArg6());
+      }
+      if (hasArg7()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(19, getArg7());
+      }
+      if (hasArg8()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(20, getArg8());
+      }
+      if (hasProp()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(21, getProp().getNumber());
+      }
+      if (hasClock()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeFloatSize(22, getClock());
+      }
+      if (hasDataType()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(23, getDataType().getNumber());
+      }
+      if (hasPixelFormat()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(24, getPixelFormat());
+      }
+      if (hasPixelType()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(25, getPixelType());
+      }
+      if (hasImageWidth()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(26, getImageWidth());
+      }
+      if (hasImageHeight()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(27, getImageHeight());
+      }
+      memoizedSerializedSize = size;
+      return size;
+    }
+    
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return newBuilder().mergeFrom(data).buildParsed();
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return newBuilder().mergeFrom(data, extensionRegistry)
+               .buildParsed();
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return newBuilder().mergeFrom(data).buildParsed();
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return newBuilder().mergeFrom(data, extensionRegistry)
+               .buildParsed();
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return newBuilder().mergeFrom(input).buildParsed();
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return newBuilder().mergeFrom(input, extensionRegistry)
+               .buildParsed();
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      Builder builder = newBuilder();
+      if (builder.mergeDelimitedFrom(input)) {
+        return builder.buildParsed();
+      } else {
+        return null;
+      }
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      Builder builder = newBuilder();
+      if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+        return builder.buildParsed();
+      } else {
+        return null;
+      }
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return newBuilder().mergeFrom(input).buildParsed();
+    }
+    public static com.android.glesv2debugger.DebuggerMessage.Message parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return newBuilder().mergeFrom(input, extensionRegistry)
+               .buildParsed();
+    }
+    
+    public static Builder newBuilder() { return Builder.create(); }
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder(com.android.glesv2debugger.DebuggerMessage.Message prototype) {
+      return newBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() { return newBuilder(this); }
+    
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageLite.Builder<
+          com.android.glesv2debugger.DebuggerMessage.Message, Builder> {
+      private com.android.glesv2debugger.DebuggerMessage.Message result;
+      
+      // Construct using com.android.glesv2debugger.DebuggerMessage.Message.newBuilder()
+      private Builder() {}
+      
+      private static Builder create() {
+        Builder builder = new Builder();
+        builder.result = new com.android.glesv2debugger.DebuggerMessage.Message();
+        return builder;
+      }
+      
+      protected com.android.glesv2debugger.DebuggerMessage.Message internalGetResult() {
+        return result;
+      }
+      
+      public Builder clear() {
+        if (result == null) {
+          throw new IllegalStateException(
+            "Cannot call clear() after build().");
+        }
+        result = new com.android.glesv2debugger.DebuggerMessage.Message();
+        return this;
+      }
+      
+      public Builder clone() {
+        return create().mergeFrom(result);
+      }
+      
+      public com.android.glesv2debugger.DebuggerMessage.Message getDefaultInstanceForType() {
+        return com.android.glesv2debugger.DebuggerMessage.Message.getDefaultInstance();
+      }
+      
+      public boolean isInitialized() {
+        return result.isInitialized();
+      }
+      public com.android.glesv2debugger.DebuggerMessage.Message build() {
+        if (result != null && !isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return buildPartial();
+      }
+      
+      private com.android.glesv2debugger.DebuggerMessage.Message buildParsed()
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        if (!isInitialized()) {
+          throw newUninitializedMessageException(
+            result).asInvalidProtocolBufferException();
+        }
+        return buildPartial();
+      }
+      
+      public com.android.glesv2debugger.DebuggerMessage.Message buildPartial() {
+        if (result == null) {
+          throw new IllegalStateException(
+            "build() has already been called on this Builder.");
+        }
+        com.android.glesv2debugger.DebuggerMessage.Message returnMe = result;
+        result = null;
+        return returnMe;
+      }
+      
+      public Builder mergeFrom(com.android.glesv2debugger.DebuggerMessage.Message other) {
+        if (other == com.android.glesv2debugger.DebuggerMessage.Message.getDefaultInstance()) return this;
+        if (other.hasContextId()) {
+          setContextId(other.getContextId());
+        }
+        if (other.hasFunction()) {
+          setFunction(other.getFunction());
+        }
+        if (other.hasType()) {
+          setType(other.getType());
+        }
+        if (other.hasExpectResponse()) {
+          setExpectResponse(other.getExpectResponse());
+        }
+        if (other.hasRet()) {
+          setRet(other.getRet());
+        }
+        if (other.hasArg0()) {
+          setArg0(other.getArg0());
+        }
+        if (other.hasArg1()) {
+          setArg1(other.getArg1());
+        }
+        if (other.hasArg2()) {
+          setArg2(other.getArg2());
+        }
+        if (other.hasArg3()) {
+          setArg3(other.getArg3());
+        }
+        if (other.hasArg4()) {
+          setArg4(other.getArg4());
+        }
+        if (other.hasArg5()) {
+          setArg5(other.getArg5());
+        }
+        if (other.hasArg6()) {
+          setArg6(other.getArg6());
+        }
+        if (other.hasArg7()) {
+          setArg7(other.getArg7());
+        }
+        if (other.hasArg8()) {
+          setArg8(other.getArg8());
+        }
+        if (other.hasData()) {
+          setData(other.getData());
+        }
+        if (other.hasDataType()) {
+          setDataType(other.getDataType());
+        }
+        if (other.hasPixelFormat()) {
+          setPixelFormat(other.getPixelFormat());
+        }
+        if (other.hasPixelType()) {
+          setPixelType(other.getPixelType());
+        }
+        if (other.hasImageWidth()) {
+          setImageWidth(other.getImageWidth());
+        }
+        if (other.hasImageHeight()) {
+          setImageHeight(other.getImageHeight());
+        }
+        if (other.hasTime()) {
+          setTime(other.getTime());
+        }
+        if (other.hasProp()) {
+          setProp(other.getProp());
+        }
+        if (other.hasClock()) {
+          setClock(other.getClock());
+        }
+        return this;
+      }
+      
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        while (true) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              return this;
+            default: {
+              if (!parseUnknownField(input, extensionRegistry, tag)) {
+                return this;
+              }
+              break;
+            }
+            case 8: {
+              setContextId(input.readInt32());
+              break;
+            }
+            case 16: {
+              int rawValue = input.readEnum();
+              com.android.glesv2debugger.DebuggerMessage.Message.Function value = com.android.glesv2debugger.DebuggerMessage.Message.Function.valueOf(rawValue);
+              if (value != null) {
+                setFunction(value);
+              }
+              break;
+            }
+            case 24: {
+              int rawValue = input.readEnum();
+              com.android.glesv2debugger.DebuggerMessage.Message.Type value = com.android.glesv2debugger.DebuggerMessage.Message.Type.valueOf(rawValue);
+              if (value != null) {
+                setType(value);
+              }
+              break;
+            }
+            case 32: {
+              setExpectResponse(input.readBool());
+              break;
+            }
+            case 40: {
+              setRet(input.readInt32());
+              break;
+            }
+            case 48: {
+              setArg0(input.readInt32());
+              break;
+            }
+            case 56: {
+              setArg1(input.readInt32());
+              break;
+            }
+            case 64: {
+              setArg2(input.readInt32());
+              break;
+            }
+            case 72: {
+              setArg3(input.readInt32());
+              break;
+            }
+            case 82: {
+              setData(input.readBytes());
+              break;
+            }
+            case 93: {
+              setTime(input.readFloat());
+              break;
+            }
+            case 128: {
+              setArg4(input.readInt32());
+              break;
+            }
+            case 136: {
+              setArg5(input.readInt32());
+              break;
+            }
+            case 144: {
+              setArg6(input.readInt32());
+              break;
+            }
+            case 152: {
+              setArg7(input.readInt32());
+              break;
+            }
+            case 160: {
+              setArg8(input.readInt32());
+              break;
+            }
+            case 168: {
+              int rawValue = input.readEnum();
+              com.android.glesv2debugger.DebuggerMessage.Message.Prop value = com.android.glesv2debugger.DebuggerMessage.Message.Prop.valueOf(rawValue);
+              if (value != null) {
+                setProp(value);
+              }
+              break;
+            }
+            case 181: {
+              setClock(input.readFloat());
+              break;
+            }
+            case 184: {
+              int rawValue = input.readEnum();
+              com.android.glesv2debugger.DebuggerMessage.Message.DataType value = com.android.glesv2debugger.DebuggerMessage.Message.DataType.valueOf(rawValue);
+              if (value != null) {
+                setDataType(value);
+              }
+              break;
+            }
+            case 192: {
+              setPixelFormat(input.readInt32());
+              break;
+            }
+            case 200: {
+              setPixelType(input.readInt32());
+              break;
+            }
+            case 208: {
+              setImageWidth(input.readInt32());
+              break;
+            }
+            case 216: {
+              setImageHeight(input.readInt32());
+              break;
+            }
+          }
+        }
+      }
+      
+      
+      // required int32 context_id = 1;
+      public boolean hasContextId() {
+        return result.hasContextId();
+      }
+      public int getContextId() {
+        return result.getContextId();
+      }
+      public Builder setContextId(int value) {
+        result.hasContextId = true;
+        result.contextId_ = value;
+        return this;
+      }
+      public Builder clearContextId() {
+        result.hasContextId = false;
+        result.contextId_ = 0;
+        return this;
+      }
+      
+      // required .com.android.glesv2debugger.Message.Function function = 2 [default = NEG];
+      public boolean hasFunction() {
+        return result.hasFunction();
+      }
+      public com.android.glesv2debugger.DebuggerMessage.Message.Function getFunction() {
+        return result.getFunction();
+      }
+      public Builder setFunction(com.android.glesv2debugger.DebuggerMessage.Message.Function value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        result.hasFunction = true;
+        result.function_ = value;
+        return this;
+      }
+      public Builder clearFunction() {
+        result.hasFunction = false;
+        result.function_ = com.android.glesv2debugger.DebuggerMessage.Message.Function.NEG;
+        return this;
+      }
+      
+      // required .com.android.glesv2debugger.Message.Type type = 3;
+      public boolean hasType() {
+        return result.hasType();
+      }
+      public com.android.glesv2debugger.DebuggerMessage.Message.Type getType() {
+        return result.getType();
+      }
+      public Builder setType(com.android.glesv2debugger.DebuggerMessage.Message.Type value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        result.hasType = true;
+        result.type_ = value;
+        return this;
+      }
+      public Builder clearType() {
+        result.hasType = false;
+        result.type_ = com.android.glesv2debugger.DebuggerMessage.Message.Type.BeforeCall;
+        return this;
+      }
+      
+      // required bool expect_response = 4;
+      public boolean hasExpectResponse() {
+        return result.hasExpectResponse();
+      }
+      public boolean getExpectResponse() {
+        return result.getExpectResponse();
+      }
+      public Builder setExpectResponse(boolean value) {
+        result.hasExpectResponse = true;
+        result.expectResponse_ = value;
+        return this;
+      }
+      public Builder clearExpectResponse() {
+        result.hasExpectResponse = false;
+        result.expectResponse_ = false;
+        return this;
+      }
+      
+      // optional int32 ret = 5;
+      public boolean hasRet() {
+        return result.hasRet();
+      }
+      public int getRet() {
+        return result.getRet();
+      }
+      public Builder setRet(int value) {
+        result.hasRet = true;
+        result.ret_ = value;
+        return this;
+      }
+      public Builder clearRet() {
+        result.hasRet = false;
+        result.ret_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg0 = 6;
+      public boolean hasArg0() {
+        return result.hasArg0();
+      }
+      public int getArg0() {
+        return result.getArg0();
+      }
+      public Builder setArg0(int value) {
+        result.hasArg0 = true;
+        result.arg0_ = value;
+        return this;
+      }
+      public Builder clearArg0() {
+        result.hasArg0 = false;
+        result.arg0_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg1 = 7;
+      public boolean hasArg1() {
+        return result.hasArg1();
+      }
+      public int getArg1() {
+        return result.getArg1();
+      }
+      public Builder setArg1(int value) {
+        result.hasArg1 = true;
+        result.arg1_ = value;
+        return this;
+      }
+      public Builder clearArg1() {
+        result.hasArg1 = false;
+        result.arg1_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg2 = 8;
+      public boolean hasArg2() {
+        return result.hasArg2();
+      }
+      public int getArg2() {
+        return result.getArg2();
+      }
+      public Builder setArg2(int value) {
+        result.hasArg2 = true;
+        result.arg2_ = value;
+        return this;
+      }
+      public Builder clearArg2() {
+        result.hasArg2 = false;
+        result.arg2_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg3 = 9;
+      public boolean hasArg3() {
+        return result.hasArg3();
+      }
+      public int getArg3() {
+        return result.getArg3();
+      }
+      public Builder setArg3(int value) {
+        result.hasArg3 = true;
+        result.arg3_ = value;
+        return this;
+      }
+      public Builder clearArg3() {
+        result.hasArg3 = false;
+        result.arg3_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg4 = 16;
+      public boolean hasArg4() {
+        return result.hasArg4();
+      }
+      public int getArg4() {
+        return result.getArg4();
+      }
+      public Builder setArg4(int value) {
+        result.hasArg4 = true;
+        result.arg4_ = value;
+        return this;
+      }
+      public Builder clearArg4() {
+        result.hasArg4 = false;
+        result.arg4_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg5 = 17;
+      public boolean hasArg5() {
+        return result.hasArg5();
+      }
+      public int getArg5() {
+        return result.getArg5();
+      }
+      public Builder setArg5(int value) {
+        result.hasArg5 = true;
+        result.arg5_ = value;
+        return this;
+      }
+      public Builder clearArg5() {
+        result.hasArg5 = false;
+        result.arg5_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg6 = 18;
+      public boolean hasArg6() {
+        return result.hasArg6();
+      }
+      public int getArg6() {
+        return result.getArg6();
+      }
+      public Builder setArg6(int value) {
+        result.hasArg6 = true;
+        result.arg6_ = value;
+        return this;
+      }
+      public Builder clearArg6() {
+        result.hasArg6 = false;
+        result.arg6_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg7 = 19;
+      public boolean hasArg7() {
+        return result.hasArg7();
+      }
+      public int getArg7() {
+        return result.getArg7();
+      }
+      public Builder setArg7(int value) {
+        result.hasArg7 = true;
+        result.arg7_ = value;
+        return this;
+      }
+      public Builder clearArg7() {
+        result.hasArg7 = false;
+        result.arg7_ = 0;
+        return this;
+      }
+      
+      // optional int32 arg8 = 20;
+      public boolean hasArg8() {
+        return result.hasArg8();
+      }
+      public int getArg8() {
+        return result.getArg8();
+      }
+      public Builder setArg8(int value) {
+        result.hasArg8 = true;
+        result.arg8_ = value;
+        return this;
+      }
+      public Builder clearArg8() {
+        result.hasArg8 = false;
+        result.arg8_ = 0;
+        return this;
+      }
+      
+      // optional bytes data = 10;
+      public boolean hasData() {
+        return result.hasData();
+      }
+      public com.google.protobuf.ByteString getData() {
+        return result.getData();
+      }
+      public Builder setData(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  result.hasData = true;
+        result.data_ = value;
+        return this;
+      }
+      public Builder clearData() {
+        result.hasData = false;
+        result.data_ = getDefaultInstance().getData();
+        return this;
+      }
+      
+      // optional .com.android.glesv2debugger.Message.DataType data_type = 23;
+      public boolean hasDataType() {
+        return result.hasDataType();
+      }
+      public com.android.glesv2debugger.DebuggerMessage.Message.DataType getDataType() {
+        return result.getDataType();
+      }
+      public Builder setDataType(com.android.glesv2debugger.DebuggerMessage.Message.DataType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        result.hasDataType = true;
+        result.dataType_ = value;
+        return this;
+      }
+      public Builder clearDataType() {
+        result.hasDataType = false;
+        result.dataType_ = com.android.glesv2debugger.DebuggerMessage.Message.DataType.ReferencedImage;
+        return this;
+      }
+      
+      // optional int32 pixel_format = 24;
+      public boolean hasPixelFormat() {
+        return result.hasPixelFormat();
+      }
+      public int getPixelFormat() {
+        return result.getPixelFormat();
+      }
+      public Builder setPixelFormat(int value) {
+        result.hasPixelFormat = true;
+        result.pixelFormat_ = value;
+        return this;
+      }
+      public Builder clearPixelFormat() {
+        result.hasPixelFormat = false;
+        result.pixelFormat_ = 0;
+        return this;
+      }
+      
+      // optional int32 pixel_type = 25;
+      public boolean hasPixelType() {
+        return result.hasPixelType();
+      }
+      public int getPixelType() {
+        return result.getPixelType();
+      }
+      public Builder setPixelType(int value) {
+        result.hasPixelType = true;
+        result.pixelType_ = value;
+        return this;
+      }
+      public Builder clearPixelType() {
+        result.hasPixelType = false;
+        result.pixelType_ = 0;
+        return this;
+      }
+      
+      // optional int32 image_width = 26;
+      public boolean hasImageWidth() {
+        return result.hasImageWidth();
+      }
+      public int getImageWidth() {
+        return result.getImageWidth();
+      }
+      public Builder setImageWidth(int value) {
+        result.hasImageWidth = true;
+        result.imageWidth_ = value;
+        return this;
+      }
+      public Builder clearImageWidth() {
+        result.hasImageWidth = false;
+        result.imageWidth_ = 0;
+        return this;
+      }
+      
+      // optional int32 image_height = 27;
+      public boolean hasImageHeight() {
+        return result.hasImageHeight();
+      }
+      public int getImageHeight() {
+        return result.getImageHeight();
+      }
+      public Builder setImageHeight(int value) {
+        result.hasImageHeight = true;
+        result.imageHeight_ = value;
+        return this;
+      }
+      public Builder clearImageHeight() {
+        result.hasImageHeight = false;
+        result.imageHeight_ = 0;
+        return this;
+      }
+      
+      // optional float time = 11;
+      public boolean hasTime() {
+        return result.hasTime();
+      }
+      public float getTime() {
+        return result.getTime();
+      }
+      public Builder setTime(float value) {
+        result.hasTime = true;
+        result.time_ = value;
+        return this;
+      }
+      public Builder clearTime() {
+        result.hasTime = false;
+        result.time_ = 0F;
+        return this;
+      }
+      
+      // optional .com.android.glesv2debugger.Message.Prop prop = 21;
+      public boolean hasProp() {
+        return result.hasProp();
+      }
+      public com.android.glesv2debugger.DebuggerMessage.Message.Prop getProp() {
+        return result.getProp();
+      }
+      public Builder setProp(com.android.glesv2debugger.DebuggerMessage.Message.Prop value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        result.hasProp = true;
+        result.prop_ = value;
+        return this;
+      }
+      public Builder clearProp() {
+        result.hasProp = false;
+        result.prop_ = com.android.glesv2debugger.DebuggerMessage.Message.Prop.CaptureDraw;
+        return this;
+      }
+      
+      // optional float clock = 22;
+      public boolean hasClock() {
+        return result.hasClock();
+      }
+      public float getClock() {
+        return result.getClock();
+      }
+      public Builder setClock(float value) {
+        result.hasClock = true;
+        result.clock_ = value;
+        return this;
+      }
+      public Builder clearClock() {
+        result.hasClock = false;
+        result.clock_ = 0F;
+        return this;
+      }
+      
+      // @@protoc_insertion_point(builder_scope:com.android.glesv2debugger.Message)
+    }
+    
+    static {
+      defaultInstance = new Message(true);
+      com.android.glesv2debugger.DebuggerMessage.internalForceInit();
+      defaultInstance.initFields();
+    }
+    
+    // @@protoc_insertion_point(class_scope:com.android.glesv2debugger.Message)
+  }
+  
+  
+  static {
+  }
+  
+  public static void internalForceInit() {}
+  
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLEnum.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLEnum.java
new file mode 100644
index 0000000..898c6e9
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLEnum.java
@@ -0,0 +1,632 @@
+/*
+ ** Copyright 2011, 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.
+ */
+
+// auto generated by generate_GLEnum_java.py"
+
+package com.android.glesv2debugger;
+
+public enum GLEnum {
+    GL_POINTS(0x0000),
+    GL_LINES(0x0001),
+    GL_LINE_LOOP(0x0002),
+    GL_LINE_STRIP(0x0003),
+    GL_TRIANGLES(0x0004),
+    GL_TRIANGLE_STRIP(0x0005),
+    GL_TRIANGLE_FAN(0x0006),
+    GL_ADD(0x0104),
+    GL_NEVER(0x0200),
+    GL_LESS(0x0201),
+    GL_EQUAL(0x0202),
+    GL_LEQUAL(0x0203),
+    GL_GREATER(0x0204),
+    GL_NOTEQUAL(0x0205),
+    GL_GEQUAL(0x0206),
+    GL_ALWAYS(0x0207),
+    GL_SRC_COLOR(0x0300),
+    GL_ONE_MINUS_SRC_COLOR(0x0301),
+    GL_SRC_ALPHA(0x0302),
+    GL_ONE_MINUS_SRC_ALPHA(0x0303),
+    GL_DST_ALPHA(0x0304),
+    GL_ONE_MINUS_DST_ALPHA(0x0305),
+    GL_DST_COLOR(0x0306),
+    GL_ONE_MINUS_DST_COLOR(0x0307),
+    GL_SRC_ALPHA_SATURATE(0x0308),
+    GL_FRONT(0x0404),
+    GL_BACK(0x0405),
+    GL_FRONT_AND_BACK(0x0408),
+    GL_INVALID_ENUM(0x0500),
+    GL_INVALID_VALUE(0x0501),
+    GL_INVALID_OPERATION(0x0502),
+    GL_STACK_OVERFLOW(0x0503),
+    GL_STACK_UNDERFLOW(0x0504),
+    GL_OUT_OF_MEMORY(0x0505),
+    GL_INVALID_FRAMEBUFFER_OPERATION(0x0506),
+    GL_EXP(0x0800),
+    GL_EXP2(0x0801),
+    GL_CW(0x0900),
+    GL_CCW(0x0901),
+    GL_CURRENT_COLOR(0x0B00),
+    GL_CURRENT_NORMAL(0x0B02),
+    GL_CURRENT_TEXTURE_COORDS(0x0B03),
+    GL_POINT_SMOOTH(0x0B10),
+    GL_POINT_SIZE(0x0B11),
+    GL_SMOOTH_POINT_SIZE_RANGE(0x0B12),
+    GL_LINE_SMOOTH(0x0B20),
+    GL_LINE_WIDTH(0x0B21),
+    GL_SMOOTH_LINE_WIDTH_RANGE(0x0B22),
+    GL_CULL_FACE(0x0B44),
+    GL_CULL_FACE_MODE(0x0B45),
+    GL_FRONT_FACE(0x0B46),
+    GL_LIGHTING(0x0B50),
+    GL_LIGHT_MODEL_TWO_SIDE(0x0B52),
+    GL_LIGHT_MODEL_AMBIENT(0x0B53),
+    GL_SHADE_MODEL(0x0B54),
+    GL_COLOR_MATERIAL(0x0B57),
+    GL_FOG(0x0B60),
+    GL_FOG_DENSITY(0x0B62),
+    GL_FOG_START(0x0B63),
+    GL_FOG_END(0x0B64),
+    GL_FOG_MODE(0x0B65),
+    GL_FOG_COLOR(0x0B66),
+    GL_DEPTH_RANGE(0x0B70),
+    GL_DEPTH_TEST(0x0B71),
+    GL_DEPTH_WRITEMASK(0x0B72),
+    GL_DEPTH_CLEAR_VALUE(0x0B73),
+    GL_DEPTH_FUNC(0x0B74),
+    GL_STENCIL_TEST(0x0B90),
+    GL_STENCIL_CLEAR_VALUE(0x0B91),
+    GL_STENCIL_FUNC(0x0B92),
+    GL_STENCIL_VALUE_MASK(0x0B93),
+    GL_STENCIL_FAIL(0x0B94),
+    GL_STENCIL_PASS_DEPTH_FAIL(0x0B95),
+    GL_STENCIL_PASS_DEPTH_PASS(0x0B96),
+    GL_STENCIL_REF(0x0B97),
+    GL_STENCIL_WRITEMASK(0x0B98),
+    GL_MATRIX_MODE(0x0BA0),
+    GL_NORMALIZE(0x0BA1),
+    GL_VIEWPORT(0x0BA2),
+    GL_MODELVIEW_STACK_DEPTH(0x0BA3),
+    GL_PROJECTION_STACK_DEPTH(0x0BA4),
+    GL_TEXTURE_STACK_DEPTH(0x0BA5),
+    GL_MODELVIEW_MATRIX(0x0BA6),
+    GL_PROJECTION_MATRIX(0x0BA7),
+    GL_TEXTURE_MATRIX(0x0BA8),
+    GL_ALPHA_TEST(0x0BC0),
+    GL_ALPHA_TEST_FUNC(0x0BC1),
+    GL_ALPHA_TEST_REF(0x0BC2),
+    GL_DITHER(0x0BD0),
+    GL_BLEND_DST(0x0BE0),
+    GL_BLEND_SRC(0x0BE1),
+    GL_BLEND(0x0BE2),
+    GL_LOGIC_OP_MODE(0x0BF0),
+    GL_COLOR_LOGIC_OP(0x0BF2),
+    GL_SCISSOR_BOX(0x0C10),
+    GL_SCISSOR_TEST(0x0C11),
+    GL_COLOR_CLEAR_VALUE(0x0C22),
+    GL_COLOR_WRITEMASK(0x0C23),
+    GL_PERSPECTIVE_CORRECTION_HINT(0x0C50),
+    GL_POINT_SMOOTH_HINT(0x0C51),
+    GL_LINE_SMOOTH_HINT(0x0C52),
+    GL_FOG_HINT(0x0C54),
+    GL_UNPACK_ALIGNMENT(0x0CF5),
+    GL_PACK_ALIGNMENT(0x0D05),
+    GL_ALPHA_SCALE(0x0D1C),
+    GL_MAX_LIGHTS(0x0D31),
+    GL_MAX_CLIP_PLANES(0x0D32),
+    GL_MAX_TEXTURE_SIZE(0x0D33),
+    GL_MAX_MODELVIEW_STACK_DEPTH(0x0D36),
+    GL_MAX_PROJECTION_STACK_DEPTH(0x0D38),
+    GL_MAX_TEXTURE_STACK_DEPTH(0x0D39),
+    GL_MAX_VIEWPORT_DIMS(0x0D3A),
+    GL_SUBPIXEL_BITS(0x0D50),
+    GL_RED_BITS(0x0D52),
+    GL_GREEN_BITS(0x0D53),
+    GL_BLUE_BITS(0x0D54),
+    GL_ALPHA_BITS(0x0D55),
+    GL_DEPTH_BITS(0x0D56),
+    GL_STENCIL_BITS(0x0D57),
+    GL_TEXTURE_2D(0x0DE1),
+    GL_DONT_CARE(0x1100),
+    GL_FASTEST(0x1101),
+    GL_NICEST(0x1102),
+    GL_AMBIENT(0x1200),
+    GL_DIFFUSE(0x1201),
+    GL_SPECULAR(0x1202),
+    GL_POSITION(0x1203),
+    GL_SPOT_DIRECTION(0x1204),
+    GL_SPOT_EXPONENT(0x1205),
+    GL_SPOT_CUTOFF(0x1206),
+    GL_CONSTANT_ATTENUATION(0x1207),
+    GL_LINEAR_ATTENUATION(0x1208),
+    GL_QUADRATIC_ATTENUATION(0x1209),
+    GL_BYTE(0x1400),
+    GL_UNSIGNED_BYTE(0x1401),
+    GL_SHORT(0x1402),
+    GL_UNSIGNED_SHORT(0x1403),
+    GL_INT(0x1404),
+    GL_UNSIGNED_INT(0x1405),
+    GL_FLOAT(0x1406),
+    GL_FIXED(0x140C),
+    GL_CLEAR(0x1500),
+    GL_AND(0x1501),
+    GL_AND_REVERSE(0x1502),
+    GL_COPY(0x1503),
+    GL_AND_INVERTED(0x1504),
+    GL_NOOP(0x1505),
+    GL_XOR(0x1506),
+    GL_OR(0x1507),
+    GL_NOR(0x1508),
+    GL_EQUIV(0x1509),
+    GL_INVERT(0x150A),
+    GL_OR_REVERSE(0x150B),
+    GL_COPY_INVERTED(0x150C),
+    GL_OR_INVERTED(0x150D),
+    GL_NAND(0x150E),
+    GL_SET(0x150F),
+    GL_EMISSION(0x1600),
+    GL_SHININESS(0x1601),
+    GL_AMBIENT_AND_DIFFUSE(0x1602),
+    GL_MODELVIEW(0x1700),
+    GL_PROJECTION(0x1701),
+    GL_TEXTURE(0x1702),
+    GL_COLOR_EXT(0x1800),
+    GL_DEPTH_EXT(0x1801),
+    GL_STENCIL_EXT(0x1802),
+    GL_STENCIL_INDEX(0x1901),
+    GL_DEPTH_COMPONENT(0x1902),
+    GL_ALPHA(0x1906),
+    GL_RGB(0x1907),
+    GL_RGBA(0x1908),
+    GL_LUMINANCE(0x1909),
+    GL_LUMINANCE_ALPHA(0x190A),
+    GL_FLAT(0x1D00),
+    GL_SMOOTH(0x1D01),
+    GL_KEEP(0x1E00),
+    GL_REPLACE(0x1E01),
+    GL_INCR(0x1E02),
+    GL_DECR(0x1E03),
+    GL_VENDOR(0x1F00),
+    GL_RENDERER(0x1F01),
+    GL_VERSION(0x1F02),
+    GL_EXTENSIONS(0x1F03),
+    GL_MODULATE(0x2100),
+    GL_DECAL(0x2101),
+    GL_TEXTURE_ENV_MODE(0x2200),
+    GL_TEXTURE_ENV_COLOR(0x2201),
+    GL_TEXTURE_ENV(0x2300),
+    GL_TEXTURE_GEN_MODE(0x2500),
+    GL_NEAREST(0x2600),
+    GL_LINEAR(0x2601),
+    GL_NEAREST_MIPMAP_NEAREST(0x2700),
+    GL_LINEAR_MIPMAP_NEAREST(0x2701),
+    GL_NEAREST_MIPMAP_LINEAR(0x2702),
+    GL_LINEAR_MIPMAP_LINEAR(0x2703),
+    GL_TEXTURE_MAG_FILTER(0x2800),
+    GL_TEXTURE_MIN_FILTER(0x2801),
+    GL_TEXTURE_WRAP_S(0x2802),
+    GL_TEXTURE_WRAP_T(0x2803),
+    GL_REPEAT(0x2901),
+    GL_POLYGON_OFFSET_UNITS(0x2A00),
+    GL_CLIP_PLANE0(0x3000),
+    GL_CLIP_PLANE1(0x3001),
+    GL_CLIP_PLANE2(0x3002),
+    GL_CLIP_PLANE3(0x3003),
+    GL_CLIP_PLANE4(0x3004),
+    GL_CLIP_PLANE5(0x3005),
+    GL_LIGHT0(0x4000),
+    GL_LIGHT1(0x4001),
+    GL_LIGHT2(0x4002),
+    GL_LIGHT3(0x4003),
+    GL_LIGHT4(0x4004),
+    GL_LIGHT5(0x4005),
+    GL_LIGHT6(0x4006),
+    GL_LIGHT7(0x4007),
+    GL_COVERAGE_BUFFER_BIT_NV(0x8000),
+    GL_CONSTANT_COLOR(0x8001),
+    GL_ONE_MINUS_CONSTANT_COLOR(0x8002),
+    GL_CONSTANT_ALPHA(0x8003),
+    GL_ONE_MINUS_CONSTANT_ALPHA(0x8004),
+    GL_BLEND_COLOR(0x8005),
+    GL_FUNC_ADD(0x8006),
+    GL_MIN_EXT(0x8007),
+    GL_MAX_EXT(0x8008),
+    GL_BLEND_EQUATION_RGB(0x8009),
+    GL_FUNC_SUBTRACT(0x800A),
+    GL_FUNC_REVERSE_SUBTRACT(0x800B),
+    GL_UNSIGNED_SHORT_4_4_4_4(0x8033),
+    GL_UNSIGNED_SHORT_5_5_5_1(0x8034),
+    GL_POLYGON_OFFSET_FILL(0x8037),
+    GL_POLYGON_OFFSET_FACTOR(0x8038),
+    GL_RESCALE_NORMAL(0x803A),
+    GL_RGB8(0x8051),
+    GL_RGBA4(0x8056),
+    GL_RGB5_A1(0x8057),
+    GL_RGBA8(0x8058),
+    GL_TEXTURE_BINDING_2D(0x8069),
+    GL_TEXTURE_BINDING_3D(0x806A),
+    GL_TEXTURE_3D(0x806F),
+    GL_TEXTURE_WRAP_R(0x8072),
+    GL_MAX_3D_TEXTURE_SIZE(0x8073),
+    GL_VERTEX_ARRAY(0x8074),
+    GL_NORMAL_ARRAY(0x8075),
+    GL_COLOR_ARRAY(0x8076),
+    GL_TEXTURE_COORD_ARRAY(0x8078),
+    GL_VERTEX_ARRAY_SIZE(0x807A),
+    GL_VERTEX_ARRAY_TYPE(0x807B),
+    GL_VERTEX_ARRAY_STRIDE(0x807C),
+    GL_NORMAL_ARRAY_TYPE(0x807E),
+    GL_NORMAL_ARRAY_STRIDE(0x807F),
+    GL_COLOR_ARRAY_SIZE(0x8081),
+    GL_COLOR_ARRAY_TYPE(0x8082),
+    GL_COLOR_ARRAY_STRIDE(0x8083),
+    GL_TEXTURE_COORD_ARRAY_SIZE(0x8088),
+    GL_TEXTURE_COORD_ARRAY_TYPE(0x8089),
+    GL_TEXTURE_COORD_ARRAY_STRIDE(0x808A),
+    GL_VERTEX_ARRAY_POINTER(0x808E),
+    GL_NORMAL_ARRAY_POINTER(0x808F),
+    GL_COLOR_ARRAY_POINTER(0x8090),
+    GL_TEXTURE_COORD_ARRAY_POINTER(0x8092),
+    GL_MULTISAMPLE(0x809D),
+    GL_SAMPLE_ALPHA_TO_COVERAGE(0x809E),
+    GL_SAMPLE_ALPHA_TO_ONE(0x809F),
+    GL_SAMPLE_COVERAGE(0x80A0),
+    GL_SAMPLE_BUFFERS(0x80A8),
+    GL_SAMPLES(0x80A9),
+    GL_SAMPLE_COVERAGE_VALUE(0x80AA),
+    GL_SAMPLE_COVERAGE_INVERT(0x80AB),
+    GL_BLEND_DST_RGB(0x80C8),
+    GL_BLEND_SRC_RGB(0x80C9),
+    GL_BLEND_DST_ALPHA(0x80CA),
+    GL_BLEND_SRC_ALPHA(0x80CB),
+    GL_BGRA_EXT(0x80E1),
+    GL_POINT_SIZE_MIN(0x8126),
+    GL_POINT_SIZE_MAX(0x8127),
+    GL_POINT_FADE_THRESHOLD_SIZE(0x8128),
+    GL_POINT_DISTANCE_ATTENUATION(0x8129),
+    GL_CLAMP_TO_EDGE(0x812F),
+    GL_GENERATE_MIPMAP(0x8191),
+    GL_GENERATE_MIPMAP_HINT(0x8192),
+    GL_DEPTH_COMPONENT16(0x81A5),
+    GL_DEPTH_COMPONENT24(0x81A6),
+    GL_DEPTH_COMPONENT32(0x81A7),
+    GL_UNSIGNED_SHORT_5_6_5(0x8363),
+    GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT(0x8365),
+    GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT(0x8366),
+    GL_UNSIGNED_INT_2_10_10_10_REV_EXT(0x8368),
+    GL_MIRRORED_REPEAT(0x8370),
+    GL_COMPRESSED_RGB_S3TC_DXT1_EXT(0x83F0),
+    GL_COMPRESSED_RGBA_S3TC_DXT1_EXT(0x83F1),
+    GL_ALIASED_POINT_SIZE_RANGE(0x846D),
+    GL_ALIASED_LINE_WIDTH_RANGE(0x846E),
+    GL_TEXTURE0(0x84C0),
+    GL_TEXTURE1(0x84C1),
+    GL_TEXTURE2(0x84C2),
+    GL_TEXTURE3(0x84C3),
+    GL_TEXTURE4(0x84C4),
+    GL_TEXTURE5(0x84C5),
+    GL_TEXTURE6(0x84C6),
+    GL_TEXTURE7(0x84C7),
+    GL_TEXTURE8(0x84C8),
+    GL_TEXTURE9(0x84C9),
+    GL_TEXTURE10(0x84CA),
+    GL_TEXTURE11(0x84CB),
+    GL_TEXTURE12(0x84CC),
+    GL_TEXTURE13(0x84CD),
+    GL_TEXTURE14(0x84CE),
+    GL_TEXTURE15(0x84CF),
+    GL_TEXTURE16(0x84D0),
+    GL_TEXTURE17(0x84D1),
+    GL_TEXTURE18(0x84D2),
+    GL_TEXTURE19(0x84D3),
+    GL_TEXTURE20(0x84D4),
+    GL_TEXTURE21(0x84D5),
+    GL_TEXTURE22(0x84D6),
+    GL_TEXTURE23(0x84D7),
+    GL_TEXTURE24(0x84D8),
+    GL_TEXTURE25(0x84D9),
+    GL_TEXTURE26(0x84DA),
+    GL_TEXTURE27(0x84DB),
+    GL_TEXTURE28(0x84DC),
+    GL_TEXTURE29(0x84DD),
+    GL_TEXTURE30(0x84DE),
+    GL_TEXTURE31(0x84DF),
+    GL_ACTIVE_TEXTURE(0x84E0),
+    GL_CLIENT_ACTIVE_TEXTURE(0x84E1),
+    GL_MAX_TEXTURE_UNITS(0x84E2),
+    GL_SUBTRACT(0x84E7),
+    GL_MAX_RENDERBUFFER_SIZE(0x84E8),
+    GL_ALL_COMPLETED_NV(0x84F2),
+    GL_FENCE_STATUS_NV(0x84F3),
+    GL_FENCE_CONDITION_NV(0x84F4),
+    GL_DEPTH_STENCIL(0x84F9),
+    GL_UNSIGNED_INT_24_8(0x84FA),
+    GL_MAX_TEXTURE_LOD_BIAS_EXT(0x84FD),
+    GL_TEXTURE_MAX_ANISOTROPY_EXT(0x84FE),
+    GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT(0x84FF),
+    GL_TEXTURE_FILTER_CONTROL_EXT(0x8500),
+    GL_TEXTURE_LOD_BIAS_EXT(0x8501),
+    GL_INCR_WRAP(0x8507),
+    GL_DECR_WRAP(0x8508),
+    GL_NORMAL_MAP(0x8511),
+    GL_REFLECTION_MAP(0x8512),
+    GL_TEXTURE_CUBE_MAP(0x8513),
+    GL_TEXTURE_BINDING_CUBE_MAP(0x8514),
+    GL_TEXTURE_CUBE_MAP_POSITIVE_X(0x8515),
+    GL_TEXTURE_CUBE_MAP_NEGATIVE_X(0x8516),
+    GL_TEXTURE_CUBE_MAP_POSITIVE_Y(0x8517),
+    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y(0x8518),
+    GL_TEXTURE_CUBE_MAP_POSITIVE_Z(0x8519),
+    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z(0x851A),
+    GL_MAX_CUBE_MAP_TEXTURE_SIZE(0x851C),
+    GL_COMBINE(0x8570),
+    GL_COMBINE_RGB(0x8571),
+    GL_COMBINE_ALPHA(0x8572),
+    GL_RGB_SCALE(0x8573),
+    GL_ADD_SIGNED(0x8574),
+    GL_INTERPOLATE(0x8575),
+    GL_CONSTANT(0x8576),
+    GL_PRIMARY_COLOR(0x8577),
+    GL_PREVIOUS(0x8578),
+    GL_SRC0_RGB(0x8580),
+    GL_SRC1_RGB(0x8581),
+    GL_SRC2_RGB(0x8582),
+    GL_SRC0_ALPHA(0x8588),
+    GL_SRC1_ALPHA(0x8589),
+    GL_SRC2_ALPHA(0x858A),
+    GL_OPERAND0_RGB(0x8590),
+    GL_OPERAND1_RGB(0x8591),
+    GL_OPERAND2_RGB(0x8592),
+    GL_OPERAND0_ALPHA(0x8598),
+    GL_OPERAND1_ALPHA(0x8599),
+    GL_OPERAND2_ALPHA(0x859A),
+    GL_VERTEX_ARRAY_BINDING(0x85B5),
+    GL_VERTEX_ATTRIB_ARRAY_ENABLED(0x8622),
+    GL_VERTEX_ATTRIB_ARRAY_SIZE(0x8623),
+    GL_VERTEX_ATTRIB_ARRAY_STRIDE(0x8624),
+    GL_VERTEX_ATTRIB_ARRAY_TYPE(0x8625),
+    GL_CURRENT_VERTEX_ATTRIB(0x8626),
+    GL_VERTEX_ATTRIB_ARRAY_POINTER(0x8645),
+    GL_NUM_COMPRESSED_TEXTURE_FORMATS(0x86A2),
+    GL_COMPRESSED_TEXTURE_FORMATS(0x86A3),
+    GL_MAX_VERTEX_UNITS(0x86A4),
+    GL_WEIGHT_ARRAY_TYPE(0x86A9),
+    GL_WEIGHT_ARRAY_STRIDE(0x86AA),
+    GL_WEIGHT_ARRAY_SIZE(0x86AB),
+    GL_WEIGHT_ARRAY_POINTER(0x86AC),
+    GL_WEIGHT_ARRAY(0x86AD),
+    GL_DOT3_RGB(0x86AE),
+    GL_DOT3_RGBA(0x86AF),
+    GL_Z400_BINARY_AMD(0x8740),
+    GL_PROGRAM_BINARY_LENGTH(0x8741),
+    GL_BUFFER_SIZE(0x8764),
+    GL_BUFFER_USAGE(0x8765),
+    GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD(0x87EE),
+    GL_3DC_X_AMD(0x87F9),
+    GL_3DC_XY_AMD(0x87FA),
+    GL_NUM_PROGRAM_BINARY_FORMATS(0x87FE),
+    GL_PROGRAM_BINARY_FORMATS(0x87FF),
+    GL_STENCIL_BACK_FUNC(0x8800),
+    GL_STENCIL_BACK_FAIL(0x8801),
+    GL_STENCIL_BACK_PASS_DEPTH_FAIL(0x8802),
+    GL_STENCIL_BACK_PASS_DEPTH_PASS(0x8803),
+    GL_WRITEONLY_RENDERING_QCOM(0x8823),
+    GL_BLEND_EQUATION_ALPHA(0x883D),
+    GL_MATRIX_PALETTE(0x8840),
+    GL_MAX_PALETTE_MATRICES(0x8842),
+    GL_CURRENT_PALETTE_MATRIX(0x8843),
+    GL_MATRIX_INDEX_ARRAY(0x8844),
+    GL_MATRIX_INDEX_ARRAY_SIZE(0x8846),
+    GL_MATRIX_INDEX_ARRAY_TYPE(0x8847),
+    GL_MATRIX_INDEX_ARRAY_STRIDE(0x8848),
+    GL_MATRIX_INDEX_ARRAY_POINTER(0x8849),
+    GL_POINT_SPRITE(0x8861),
+    GL_COORD_REPLACE(0x8862),
+    GL_MAX_VERTEX_ATTRIBS(0x8869),
+    GL_VERTEX_ATTRIB_ARRAY_NORMALIZED(0x886A),
+    GL_MAX_TEXTURE_IMAGE_UNITS(0x8872),
+    GL_ARRAY_BUFFER(0x8892),
+    GL_ELEMENT_ARRAY_BUFFER(0x8893),
+    GL_ARRAY_BUFFER_BINDING(0x8894),
+    GL_ELEMENT_ARRAY_BUFFER_BINDING(0x8895),
+    GL_VERTEX_ARRAY_BUFFER_BINDING(0x8896),
+    GL_NORMAL_ARRAY_BUFFER_BINDING(0x8897),
+    GL_COLOR_ARRAY_BUFFER_BINDING(0x8898),
+    GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING(0x889A),
+    GL_WEIGHT_ARRAY_BUFFER_BINDING(0x889E),
+    GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING(0x889F),
+    GL_WRITE_ONLY(0x88B9),
+    GL_BUFFER_ACCESS(0x88BB),
+    GL_BUFFER_MAPPED(0x88BC),
+    GL_BUFFER_MAP_POINTER(0x88BD),
+    GL_STREAM_DRAW(0x88E0),
+    GL_STATIC_DRAW(0x88E4),
+    GL_DYNAMIC_DRAW(0x88E8),
+    GL_DEPTH24_STENCIL8(0x88F0),
+    GL_POINT_SIZE_ARRAY_TYPE(0x898A),
+    GL_POINT_SIZE_ARRAY_STRIDE(0x898B),
+    GL_POINT_SIZE_ARRAY_POINTER(0x898C),
+    GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS(0x898D),
+    GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS(0x898E),
+    GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS(0x898F),
+    GL_FRAGMENT_SHADER(0x8B30),
+    GL_VERTEX_SHADER(0x8B31),
+    GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS(0x8B4C),
+    GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS(0x8B4D),
+    GL_SHADER_TYPE(0x8B4F),
+    GL_FLOAT_VEC2(0x8B50),
+    GL_FLOAT_VEC3(0x8B51),
+    GL_FLOAT_VEC4(0x8B52),
+    GL_INT_VEC2(0x8B53),
+    GL_INT_VEC3(0x8B54),
+    GL_INT_VEC4(0x8B55),
+    GL_BOOL(0x8B56),
+    GL_BOOL_VEC2(0x8B57),
+    GL_BOOL_VEC3(0x8B58),
+    GL_BOOL_VEC4(0x8B59),
+    GL_FLOAT_MAT2(0x8B5A),
+    GL_FLOAT_MAT3(0x8B5B),
+    GL_FLOAT_MAT4(0x8B5C),
+    GL_SAMPLER_2D(0x8B5E),
+    GL_SAMPLER_3D(0x8B5F),
+    GL_SAMPLER_CUBE(0x8B60),
+    GL_DELETE_STATUS(0x8B80),
+    GL_COMPILE_STATUS(0x8B81),
+    GL_LINK_STATUS(0x8B82),
+    GL_VALIDATE_STATUS(0x8B83),
+    GL_INFO_LOG_LENGTH(0x8B84),
+    GL_ATTACHED_SHADERS(0x8B85),
+    GL_ACTIVE_UNIFORMS(0x8B86),
+    GL_ACTIVE_UNIFORM_MAX_LENGTH(0x8B87),
+    GL_SHADER_SOURCE_LENGTH(0x8B88),
+    GL_ACTIVE_ATTRIBUTES(0x8B89),
+    GL_ACTIVE_ATTRIBUTE_MAX_LENGTH(0x8B8A),
+    GL_FRAGMENT_SHADER_DERIVATIVE_HINT(0x8B8B),
+    GL_SHADING_LANGUAGE_VERSION(0x8B8C),
+    GL_CURRENT_PROGRAM(0x8B8D),
+    GL_PALETTE4_RGB8(0x8B90),
+    GL_PALETTE4_RGBA8(0x8B91),
+    GL_PALETTE4_R5_G6_B5(0x8B92),
+    GL_PALETTE4_RGBA4(0x8B93),
+    GL_PALETTE4_RGB5_A1(0x8B94),
+    GL_PALETTE8_RGB8(0x8B95),
+    GL_PALETTE8_RGBA8(0x8B96),
+    GL_PALETTE8_R5_G6_B5(0x8B97),
+    GL_PALETTE8_RGBA4(0x8B98),
+    GL_PALETTE8_RGB5_A1(0x8B99),
+    GL_IMPLEMENTATION_COLOR_READ_TYPE(0x8B9A),
+    GL_IMPLEMENTATION_COLOR_READ_FORMAT(0x8B9B),
+    GL_POINT_SIZE_ARRAY(0x8B9C),
+    GL_TEXTURE_CROP_RECT(0x8B9D),
+    GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING(0x8B9E),
+    GL_POINT_SIZE_ARRAY_BUFFER_BINDING(0x8B9F),
+    GL_COUNTER_TYPE_AMD(0x8BC0),
+    GL_COUNTER_RANGE_AMD(0x8BC1),
+    GL_UNSIGNED_INT64_AMD(0x8BC2),
+    GL_PERCENTAGE_AMD(0x8BC3),
+    GL_PERFMON_RESULT_AVAILABLE_AMD(0x8BC4),
+    GL_PERFMON_RESULT_SIZE_AMD(0x8BC5),
+    GL_PERFMON_RESULT_AMD(0x8BC6),
+    GL_TEXTURE_WIDTH_QCOM(0x8BD2),
+    GL_TEXTURE_HEIGHT_QCOM(0x8BD3),
+    GL_TEXTURE_DEPTH_QCOM(0x8BD4),
+    GL_TEXTURE_INTERNAL_FORMAT_QCOM(0x8BD5),
+    GL_TEXTURE_FORMAT_QCOM(0x8BD6),
+    GL_TEXTURE_TYPE_QCOM(0x8BD7),
+    GL_TEXTURE_IMAGE_VALID_QCOM(0x8BD8),
+    GL_TEXTURE_NUM_LEVELS_QCOM(0x8BD9),
+    GL_TEXTURE_TARGET_QCOM(0x8BDA),
+    GL_TEXTURE_OBJECT_VALID_QCOM(0x8BDB),
+    GL_STATE_RESTORE(0x8BDC),
+    GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG(0x8C00),
+    GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG(0x8C01),
+    GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG(0x8C02),
+    GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG(0x8C03),
+    GL_MODULATE_COLOR_IMG(0x8C04),
+    GL_RECIP_ADD_SIGNED_ALPHA_IMG(0x8C05),
+    GL_TEXTURE_ALPHA_MODULATE_IMG(0x8C06),
+    GL_FACTOR_ALPHA_MODULATE_IMG(0x8C07),
+    GL_FRAGMENT_ALPHA_MODULATE_IMG(0x8C08),
+    GL_ADD_BLEND_IMG(0x8C09),
+    GL_SGX_BINARY_IMG(0x8C0A),
+    GL_ATC_RGB_AMD(0x8C92),
+    GL_ATC_RGBA_EXPLICIT_ALPHA_AMD(0x8C93),
+    GL_STENCIL_BACK_REF(0x8CA3),
+    GL_STENCIL_BACK_VALUE_MASK(0x8CA4),
+    GL_STENCIL_BACK_WRITEMASK(0x8CA5),
+    GL_FRAMEBUFFER_BINDING(0x8CA6),
+    GL_RENDERBUFFER_BINDING(0x8CA7),
+    GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE(0x8CD0),
+    GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME(0x8CD1),
+    GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL(0x8CD2),
+    GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE(0x8CD3),
+    GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET(0x8CD4),
+    GL_FRAMEBUFFER_COMPLETE(0x8CD5),
+    GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT(0x8CD6),
+    GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT(0x8CD7),
+    GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS(0x8CD9),
+    GL_FRAMEBUFFER_INCOMPLETE_FORMATS(0x8CDA),
+    GL_FRAMEBUFFER_UNSUPPORTED(0x8CDD),
+    GL_COLOR_ATTACHMENT0(0x8CE0),
+    GL_DEPTH_ATTACHMENT(0x8D00),
+    GL_STENCIL_ATTACHMENT(0x8D20),
+    GL_FRAMEBUFFER(0x8D40),
+    GL_RENDERBUFFER(0x8D41),
+    GL_RENDERBUFFER_WIDTH(0x8D42),
+    GL_RENDERBUFFER_HEIGHT(0x8D43),
+    GL_RENDERBUFFER_INTERNAL_FORMAT(0x8D44),
+    GL_STENCIL_INDEX1(0x8D46),
+    GL_STENCIL_INDEX4(0x8D47),
+    GL_STENCIL_INDEX8(0x8D48),
+    GL_RENDERBUFFER_RED_SIZE(0x8D50),
+    GL_RENDERBUFFER_GREEN_SIZE(0x8D51),
+    GL_RENDERBUFFER_BLUE_SIZE(0x8D52),
+    GL_RENDERBUFFER_ALPHA_SIZE(0x8D53),
+    GL_RENDERBUFFER_DEPTH_SIZE(0x8D54),
+    GL_RENDERBUFFER_STENCIL_SIZE(0x8D55),
+    GL_TEXTURE_GEN_STR(0x8D60),
+    GL_HALF_FLOAT(0x8D61),
+    GL_RGB565(0x8D62),
+    GL_ETC1_RGB8(0x8D64),
+    GL_TEXTURE_EXTERNAL(0x8D65),
+    GL_SAMPLER_EXTERNAL(0x8D66),
+    GL_TEXTURE_BINDING_EXTERNAL(0x8D67),
+    GL_REQUIRED_TEXTURE_IMAGE_UNITS(0x8D68),
+    GL_LOW_FLOAT(0x8DF0),
+    GL_MEDIUM_FLOAT(0x8DF1),
+    GL_HIGH_FLOAT(0x8DF2),
+    GL_LOW_INT(0x8DF3),
+    GL_MEDIUM_INT(0x8DF4),
+    GL_HIGH_INT(0x8DF5),
+    GL_UNSIGNED_INT_10_10_10_2(0x8DF6),
+    GL_INT_10_10_10_2(0x8DF7),
+    GL_SHADER_BINARY_FORMATS(0x8DF8),
+    GL_NUM_SHADER_BINARY_FORMATS(0x8DF9),
+    GL_SHADER_COMPILER(0x8DFA),
+    GL_MAX_VERTEX_UNIFORM_VECTORS(0x8DFB),
+    GL_MAX_VARYING_VECTORS(0x8DFC),
+    GL_MAX_FRAGMENT_UNIFORM_VECTORS(0x8DFD),
+    GL_DEPTH_COMPONENT16_NONLINEAR_NV(0x8E2C),
+    GL_COVERAGE_COMPONENT_NV(0x8ED0),
+    GL_COVERAGE_COMPONENT4_NV(0x8ED1),
+    GL_COVERAGE_ATTACHMENT_NV(0x8ED2),
+    GL_COVERAGE_BUFFERS_NV(0x8ED3),
+    GL_COVERAGE_SAMPLES_NV(0x8ED4),
+    GL_COVERAGE_ALL_FRAGMENTS_NV(0x8ED5),
+    GL_COVERAGE_EDGE_FRAGMENTS_NV(0x8ED6),
+    GL_COVERAGE_AUTOMATIC_NV(0x8ED7),
+    GL_PERFMON_GLOBAL_MODE_QCOM(0x8FA0),
+    GL_SGX_PROGRAM_BINARY_IMG(0x9130),
+    GL_RENDERBUFFER_SAMPLES_IMG(0x9133),
+    GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_IMG(0x9134),
+    GL_MAX_SAMPLES_IMG(0x9135),
+    GL_TEXTURE_SAMPLES_IMG(0x9136),
+    ;
+
+    public final int value;
+    GLEnum(final int value) {
+        this.value = value;
+    }
+
+    private static final java.util.HashMap<Integer, GLEnum> reverseMap = new java.util.HashMap<Integer, GLEnum>();
+    static {
+        for (GLEnum e : GLEnum.values())
+        reverseMap.put(e.value, e);
+    }
+
+    public static GLEnum valueOf(final int value) {
+        return reverseMap.get(value);
+    }
+}
\ No newline at end of file
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerShader.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerShader.java
new file mode 100644
index 0000000..f13c465
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerShader.java
@@ -0,0 +1,259 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.sdklib.util.SparseArray;
+
+import java.util.ArrayList;
+
+class GLShader implements Cloneable {
+    public final int name;
+    GLServerShader context; // the context this was created in
+    public final GLEnum type;
+    public boolean delete;
+    public ArrayList<Integer> programs = new ArrayList<Integer>();
+    public String source, originalSource;
+
+    GLShader(final int name, final GLServerShader context, final GLEnum type) {
+        this.name = name;
+        this.context = context;
+        this.type = type;
+    }
+
+    /** deep copy */
+    public GLShader clone(final GLServerShader copyContext) {
+        try {
+            GLShader shader = (GLShader) super.clone();
+            shader.programs = (ArrayList<Integer>) programs.clone();
+            shader.context = copyContext;
+            return shader;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+}
+
+class GLProgram implements Cloneable {
+    public final int name;
+    GLServerShader context; // the context this was created in
+    public boolean delete;
+    public int vert, frag;
+
+    GLProgram(final int name, final GLServerShader context) {
+        this.name = name;
+        this.context = context;
+    }
+
+    /** deep copy */
+    public GLProgram clone(final GLServerShader copyContext) {
+        try {
+            GLProgram copy = (GLProgram) super.clone();
+            copy.context = copyContext;
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+}
+
+public class GLServerShader implements Cloneable {
+    Context context;
+    public SparseArray<GLShader> shaders = new SparseArray<GLShader>();
+    public SparseArray<GLProgram> programs = new SparseArray<GLProgram>();
+    public GLProgram current = null;
+    boolean uiUpdate = false;
+
+    GLServerShader(Context context) {
+        this.context = context;
+    }
+
+    /** deep copy */
+    public GLServerShader clone(final Context copyContext) {
+        try {
+            GLServerShader copy = (GLServerShader) super.clone();
+            copy.context = copyContext;
+
+            copy.shaders = new SparseArray<GLShader>(shaders.size());
+            for (int i = 0; i < shaders.size(); i++)
+                copy.shaders.append(shaders.keyAt(i), shaders.valueAt(i).clone(copy));
+
+            copy.programs = new SparseArray<GLProgram>(programs.size());
+            for (int i = 0; i < programs.size(); i++)
+                copy.programs.append(programs.keyAt(i), programs.valueAt(i).clone(copy));
+
+            if (current != null)
+                copy.current = copy.programs.get(current.name);
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+
+    /** returns true if processed */
+    public boolean processMessage(final Message msg) {
+        boolean oldUiUpdate = uiUpdate;
+        uiUpdate = true;
+        switch (msg.getFunction()) {
+            case glAttachShader:
+                glAttachShader(msg);
+                return true;
+            case glCreateProgram:
+                glCreateProgram(msg);
+                return true;
+            case glCreateShader:
+                glCreateShader(msg);
+                return true;
+            case glDeleteProgram:
+                glDeleteProgram(msg);
+                return true;
+            case glDeleteShader:
+                glDeleteShader(msg);
+                return true;
+            case glDetachShader:
+                glDetachShader(msg);
+                return true;
+            case glShaderSource:
+                glShaderSource(msg);
+                return true;
+            case glUseProgram:
+                glUseProgram(msg);
+                return true;
+            default:
+                uiUpdate = oldUiUpdate;
+                return false;
+        }
+    }
+
+    GLShader getShader(int name) {
+        if (name == 0)
+            return null;
+        for (Context ctx : context.shares) {
+            GLShader shader = ctx.serverShader.shaders.get(name);
+            if (shader != null)
+                return shader;
+        }
+        assert false;
+        return null;
+    }
+
+    GLProgram getProgram(int name) {
+        if (name == 0)
+            return null;
+        for (Context ctx : context.shares) {
+            GLProgram program = ctx.serverShader.programs.get(name);
+            if (program != null)
+                return program;
+        }
+        assert false;
+        return null;
+    }
+
+    // void API_ENTRY(glAttachShader)(GLuint program, GLuint shader)
+    void glAttachShader(final Message msg) {
+        GLProgram program = getProgram(msg.getArg0());
+        assert program != null;
+        GLShader shader = getShader(msg.getArg1());
+        assert program != null;
+        if (GLEnum.GL_VERTEX_SHADER == shader.type)
+            program.vert = shader.name;
+        else
+            program.frag = shader.name;
+        shader.programs.add(program.name);
+    }
+
+    // GLuint API_ENTRY(glCreateProgram)(void)
+    void glCreateProgram(final Message msg) {
+        programs.put(msg.getRet(), new GLProgram(msg.getRet(), this));
+    }
+
+    // GLuint API_ENTRY(glCreateShader)(GLenum type)
+    void glCreateShader(final Message msg) {
+        shaders.put(msg.getRet(),
+                new GLShader(msg.getRet(), this, GLEnum.valueOf(msg.getArg0())));
+    }
+
+    // void API_ENTRY(glDeleteProgram)
+    void glDeleteProgram(final Message msg) {
+        if (msg.getArg0() == 0)
+            return;
+        GLProgram program = getProgram(msg.getArg0());
+        program.delete = true;
+        for (Context ctx : context.shares)
+            if (ctx.serverShader.current == program)
+                return;
+        glDetachShader(program, getShader(program.vert));
+        glDetachShader(program, getShader(program.frag));
+        programs.remove(program.name);
+    }
+
+    // void API_ENTRY(glDeleteShader)(GLuint shader)
+    void glDeleteShader(final Message msg) {
+        if (msg.getArg0() == 0)
+            return;
+        GLShader shader = getShader(msg.getArg0());
+        shader.delete = true;
+        if (shader.programs.size() == 0)
+            shaders.remove(shader.name);
+    }
+
+    // void API_ENTRY(glDetachShader)(GLuint program, GLuint shader)
+    void glDetachShader(final Message msg) {
+        glDetachShader(getProgram(msg.getArg0()), getShader(msg.getArg1()));
+    }
+
+    void glDetachShader(final GLProgram program, final GLShader shader) {
+        if (program == null)
+            return;
+        if (program.vert == shader.name)
+            program.vert = 0;
+        else if (program.frag == shader.name)
+            program.frag = 0;
+        else
+            return;
+        shader.programs.remove(new Integer(program.name));
+        if (shader.delete && shader.programs.size() == 0)
+            shaders.remove(shader.name);
+    }
+
+    // void API_ENTRY(glShaderSource)(GLuint shader, GLsizei count, const
+    // GLchar** string, const GLint* length)
+    void glShaderSource(final Message msg) {
+        if (!msg.hasData())
+            return; // TODO: distinguish between generated calls
+        GLShader shader = getShader(msg.getArg0());
+        shader.source = shader.originalSource = msg.getData().toStringUtf8();
+    }
+
+    // void API_ENTRY(glUseProgram)(GLuint program)
+    void glUseProgram(final Message msg) {
+        GLProgram oldCurrent = current;
+        current = getProgram(msg.getArg0());
+        if (null != oldCurrent && oldCurrent.delete && oldCurrent != current) {
+            for (Context ctx : context.shares)
+                if (ctx.serverShader.current == oldCurrent)
+                    return;
+            oldCurrent.context.programs.remove(new Integer(oldCurrent.name));
+        }
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerState.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerState.java
new file mode 100644
index 0000000..addf277
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerState.java
@@ -0,0 +1,271 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.sdklib.util.SparseArray;
+import com.android.sdklib.util.SparseIntArray;
+
+class GLStencilState implements Cloneable {
+    public int ref, mask;
+    public GLEnum func;
+    public GLEnum sf, df, dp; // operation
+
+    @Override
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}
+
+public class GLServerState implements Cloneable {
+    final Context context;
+    public GLStencilState front = new GLStencilState(), back = new GLStencilState();
+    public SparseIntArray enableDisables;
+
+    /** integer states set via a GL function and GLEnum; keyed by GLEnum.value */
+    public SparseArray<Message> integers;
+
+    /** states set only via a GL function; keyed by Function.getNumber() */
+    public SparseArray<Message> lastSetter;
+
+    GLServerState(final Context context) {
+        this.context = context;
+        enableDisables = new SparseIntArray();
+        enableDisables.put(GLEnum.GL_BLEND.value, 0);
+        enableDisables.put(GLEnum.GL_DITHER.value, 1);
+        enableDisables.put(GLEnum.GL_DEPTH_TEST.value, 0);
+        enableDisables.put(GLEnum.GL_STENCIL_TEST.value, 0);
+        enableDisables.put(GLEnum.GL_SCISSOR_TEST.value, 0);
+        enableDisables.put(GLEnum.GL_SAMPLE_COVERAGE.value, 0);
+        enableDisables.put(GLEnum.GL_SAMPLE_ALPHA_TO_COVERAGE.value, 0);
+        enableDisables.put(GLEnum.GL_POLYGON_OFFSET_FILL.value, 0);
+        enableDisables.put(GLEnum.GL_CULL_FACE.value, 0);
+        // enableDisables.put(GLEnum.GL_TEXTURE_2D.value, 1);
+
+        lastSetter = new SparseArray<Message>();
+        lastSetter.put(Function.glBlendColor.getNumber(), null);
+        // glBlendEquation overwrites glBlendEquationSeparate
+        lastSetter.put(Function.glBlendEquationSeparate.getNumber(), null);
+        // glBlendFunc overwrites glBlendFuncSeparate
+        lastSetter.put(Function.glBlendFuncSeparate.getNumber(), null);
+        lastSetter.put(Function.glClearColor.getNumber(), null);
+        lastSetter.put(Function.glClearDepthf.getNumber(), null);
+        lastSetter.put(Function.glClearStencil.getNumber(), null);
+        lastSetter.put(Function.glColorMask.getNumber(), null);
+        lastSetter.put(Function.glCullFace.getNumber(), null);
+        lastSetter.put(Function.glDepthMask.getNumber(), null);
+        lastSetter.put(Function.glDepthFunc.getNumber(), null);
+        lastSetter.put(Function.glDepthRangef.getNumber(), null);
+        lastSetter.put(Function.glFrontFace.getNumber(), null);
+        lastSetter.put(Function.glLineWidth.getNumber(), null);
+        lastSetter.put(Function.glPolygonOffset.getNumber(), null);
+        lastSetter.put(Function.glSampleCoverage.getNumber(), null);
+        lastSetter.put(Function.glScissor.getNumber(), null);
+        lastSetter.put(Function.glStencilMaskSeparate.getNumber(), null);
+        lastSetter.put(Function.glViewport.getNumber(), null);
+
+        integers = new SparseArray<Message>();
+        integers.put(GLEnum.GL_PACK_ALIGNMENT.value, null);
+        integers.put(GLEnum.GL_UNPACK_ALIGNMENT.value, null);
+    }
+
+    /** returns true if processed */
+    public boolean processMessage(final Message msg) {
+        switch (msg.getFunction()) {
+            case glBlendColor:
+            case glBlendEquation:
+            case glBlendEquationSeparate:
+            case glBlendFunc:
+            case glBlendFuncSeparate:
+            case glClearColor:
+            case glClearDepthf:
+            case glClearStencil:
+            case glColorMask:
+            case glCullFace:
+            case glDepthMask:
+            case glDepthFunc:
+            case glDepthRangef:
+                return setter(msg);
+            case glDisable:
+                return enableDisable(false, msg);
+            case glEnable:
+                return enableDisable(true, msg);
+            case glFrontFace:
+            case glLineWidth:
+                return setter(msg);
+            case glPixelStorei:
+                if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_PACK_ALIGNMENT)
+                    integers.put(msg.getArg0(), msg);
+                else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_UNPACK_ALIGNMENT)
+                    integers.put(msg.getArg0(), msg);
+                else
+                    assert false;
+                return true;
+            case glPolygonOffset:
+            case glSampleCoverage:
+            case glScissor:
+                return setter(msg);
+            case glStencilFunc: {
+                Message.Builder builder = msg.toBuilder();
+                builder.setArg2(msg.getArg1());
+                builder.setArg1(msg.getArg0());
+                builder.setArg0(GLEnum.GL_FRONT_AND_BACK.value);
+                return glStencilFuncSeparate(builder.build());
+            }
+            case glStencilFuncSeparate:
+                return glStencilFuncSeparate(msg);
+            case glStencilMask:
+            case glStencilMaskSeparate:
+                return setter(msg);
+            case glStencilOp: {
+                Message.Builder builder = msg.toBuilder();
+                builder.setArg3(msg.getArg2());
+                builder.setArg2(msg.getArg1());
+                builder.setArg1(msg.getArg0());
+                builder.setArg0(GLEnum.GL_FRONT_AND_BACK.value);
+                return glStencilOpSeparate(builder.build());
+            }
+            case glStencilOpSeparate:
+                return glStencilOpSeparate(msg);
+            case glViewport:
+                return setter(msg);
+            default:
+                return false;
+        }
+    }
+
+    boolean setter(final Message msg) {
+        switch (msg.getFunction()) {
+            case glBlendFunc:
+                lastSetter.put(Function.glBlendFuncSeparate.getNumber(), msg);
+                break;
+            case glBlendEquation:
+                lastSetter.put(Function.glBlendEquationSeparate.getNumber(), msg);
+                break;
+            case glStencilMask:
+                lastSetter.put(Function.glStencilMaskSeparate.getNumber(), msg);
+                break;
+            default:
+                lastSetter.put(msg.getFunction().getNumber(), msg);
+                break;
+        }
+        return true;
+    }
+
+    boolean enableDisable(boolean enable, final Message msg) {
+        int index = enableDisables.indexOfKey(msg.getArg0());
+        if (index < 0) {
+            System.out.print("invalid glDisable/Enable: ");
+            System.out.println(MessageFormatter.format(msg, false));
+            return true;
+        }
+        if ((enableDisables.valueAt(index) != 0) == enable)
+            return true; // TODO: redundant
+        enableDisables.put(msg.getArg0(), enable ? 1 : 0);
+        return true;
+    }
+
+    // void StencilFuncSeparate( enum face, enum func, int ref, uint mask )
+    boolean glStencilFuncSeparate(final Message msg) {
+        GLEnum ff = front.func, bf = back.func;
+        int fr = front.ref, br = back.ref;
+        int fm = front.mask, bm = back.mask;
+        final GLEnum face = GLEnum.valueOf(msg.getArg0());
+        if (face == GLEnum.GL_FRONT || face == GLEnum.GL_FRONT_AND_BACK) {
+            ff = GLEnum.valueOf(msg.getArg1());
+            fr = msg.getArg2();
+            fm = msg.getArg3();
+        }
+        if (face == GLEnum.GL_BACK || face == GLEnum.GL_FRONT_AND_BACK) {
+            bf = GLEnum.valueOf(msg.getArg1());
+            br = msg.getArg2();
+            bm = msg.getArg3();
+        }
+        if (ff == front.func && fr == front.ref && fm == front.mask)
+            if (bf == back.func && br == back.ref && bm == back.mask)
+                return true; // TODO: redundant
+        front.func = ff;
+        front.ref = fr;
+        front.mask = fm;
+        back.func = bf;
+        back.ref = br;
+        back.mask = bm;
+        return true;
+    }
+
+    // void StencilOpSeparate( enum face, enum sfail, enum dpfail, enum dppass )
+    boolean glStencilOpSeparate(final Message msg) {
+        GLEnum fsf = front.sf, fdf = front.df, fdp = front.dp;
+        GLEnum bsf = back.sf, bdf = back.df, bdp = back.dp;
+        final GLEnum face = GLEnum.valueOf(msg.getArg0());
+        if (face == GLEnum.GL_FRONT || face == GLEnum.GL_FRONT_AND_BACK) {
+            fsf = GLEnum.valueOf(msg.getArg1());
+            fdf = GLEnum.valueOf(msg.getArg2());
+            fdp = GLEnum.valueOf(msg.getArg3());
+        }
+        if (face == GLEnum.GL_BACK || face == GLEnum.GL_FRONT_AND_BACK) {
+            bsf = GLEnum.valueOf(msg.getArg1());
+            bdf = GLEnum.valueOf(msg.getArg2());
+            bdp = GLEnum.valueOf(msg.getArg3());
+        }
+        if (fsf == front.sf && fdf == front.df && fdp == front.dp)
+            if (bsf == back.sf && bdf == back.df && bdp == back.dp)
+                return true; // TODO: redundant
+        front.sf = fsf;
+        front.df = fdf;
+        front.dp = fdp;
+        back.sf = bsf;
+        back.df = bdf;
+        back.dp = bdp;
+        return true;
+    }
+
+    /** deep copy */
+    @Override
+    public GLServerState clone() {
+        try {
+            GLServerState newState = (GLServerState) super.clone();
+            newState.front = (GLStencilState) front.clone();
+            newState.back = (GLStencilState) back.clone();
+
+            newState.enableDisables = new SparseIntArray(enableDisables.size());
+            for (int i = 0; i < enableDisables.size(); i++)
+                newState.enableDisables.append(enableDisables.keyAt(i),
+                        enableDisables.valueAt(i));
+
+            newState.integers = new SparseArray<Message>(integers.size());
+            for (int i = 0; i < integers.size(); i++)
+                newState.integers.append(integers.keyAt(i), integers.valueAt(i));
+
+            newState.lastSetter = new SparseArray<Message>(lastSetter.size());
+            for (int i = 0; i < lastSetter.size(); i++)
+                newState.lastSetter.append(lastSetter.keyAt(i), lastSetter.valueAt(i));
+
+            return newState;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java
new file mode 100644
index 0000000..27676dd
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java
@@ -0,0 +1,235 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.sdklib.util.SparseArray;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+class GLTexture implements Cloneable {
+    public final int name;
+    public final GLEnum target;
+    public ArrayList<Message> contentChanges = new ArrayList<Message>();
+    public GLEnum wrapS = GLEnum.GL_REPEAT, wrapT = GLEnum.GL_REPEAT;
+    public GLEnum min = GLEnum.GL_NEAREST_MIPMAP_LINEAR;
+    public GLEnum mag = GLEnum.GL_LINEAR;
+    public GLEnum format;
+    public int width, height;
+
+    GLTexture(final int name, final GLEnum target) {
+        this.name = name;
+        this.target = target;
+    }
+
+    @Override
+    public GLTexture clone() {
+        try {
+            GLTexture copy = (GLTexture) super.clone();
+            copy.contentChanges = (ArrayList<Message>) contentChanges.clone();
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+
+    boolean processMessage(final Message msg) {
+        switch (msg.getFunction()) {
+            case glCompressedTexImage2D:
+            case glCopyTexImage2D:
+            case glTexImage2D:
+                if (msg.getArg1() == 0) { // level 0
+                    format = GLEnum.valueOf(msg.getArg2());
+                    width = msg.getArg3();
+                    height = msg.getArg4();
+                }
+                //$FALL-THROUGH$
+            case glCompressedTexSubImage2D:
+            case glCopyTexSubImage2D:
+            case glTexSubImage2D:
+            case glGenerateMipmap:
+                contentChanges.add(msg);
+                break;
+            default:
+                assert false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s %s %d*%d %d change(s)", target, format, width, height,
+                contentChanges.size());
+    }
+}
+
+public class GLServerTexture implements Cloneable {
+    Context context;
+
+    public GLEnum activeTexture = GLEnum.GL_TEXTURE0;
+    public int[] tmu2D;
+    public int[] tmuCube;
+    public SparseArray<GLTexture> textures = new SparseArray<GLTexture>();
+    public GLTexture tex2D = null, texCube = null;
+
+    GLServerTexture(final Context context, final int MAX_COMBINED_TEXTURE_IMAGE_UNITS) {
+        this.context = context;
+        textures.append(0, null);
+        tmu2D = new int[MAX_COMBINED_TEXTURE_IMAGE_UNITS];
+        tmuCube = new int[MAX_COMBINED_TEXTURE_IMAGE_UNITS];
+    }
+
+    public GLServerTexture clone(final Context copyContext) {
+        try {
+            GLServerTexture copy = (GLServerTexture) super.clone();
+            copy.context = copyContext;
+
+            copy.tmu2D = tmu2D.clone();
+            copy.tmuCube = tmuCube.clone();
+
+            copy.textures = new SparseArray<GLTexture>(textures.size());
+            for (int i = 0; i < textures.size(); i++)
+                if (textures.valueAt(i) != null)
+                    copy.textures.append(textures.keyAt(i), textures.valueAt(i).clone());
+                else
+                    copy.textures.append(textures.keyAt(i), null);
+
+            if (tex2D != null)
+                copy.tex2D = copy.textures.get(tex2D.name);
+            if (texCube != null)
+                copy.texCube = copy.textures.get(texCube.name);
+
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+
+    public boolean processMessage(final Message msg) {
+        switch (msg.getFunction()) {
+            case glActiveTexture:
+                activeTexture = GLEnum.valueOf(msg.getArg0());
+                return true;
+            case glBindTexture:
+                return bindTexture(msg.getArg0(), msg.getArg1());
+            case glCompressedTexImage2D:
+            case glCompressedTexSubImage2D:
+            case glCopyTexImage2D:
+            case glCopyTexSubImage2D:
+            case glTexImage2D:
+            case glTexSubImage2D:
+                switch (GLEnum.valueOf(msg.getArg0())) {
+                    case GL_TEXTURE_2D:
+                        if (tex2D != null)
+                            return tex2D.processMessage(msg);
+                        return true;
+                    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
+                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
+                    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
+                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
+                    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
+                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
+                        if (texCube != null)
+                            return texCube.processMessage(msg);
+                        return true;
+                    default:
+                        return true;
+                }
+            case glDeleteTextures: {
+                final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
+                names.order(SampleView.targetByteOrder);
+                for (int i = 0; i < msg.getArg0(); i++) {
+                    final int name = names.getInt();
+                    if (tex2D != null && tex2D.name == name)
+                        bindTexture(GLEnum.GL_TEXTURE_2D.value, 0);
+                    if (texCube != null && texCube.name == name)
+                        bindTexture(GLEnum.GL_TEXTURE_CUBE_MAP.value, 0);
+                    if (name != 0)
+                        textures.remove(name);
+                }
+                return true;
+            }
+            case glGenerateMipmap:
+                if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_TEXTURE_2D && tex2D != null)
+                    return tex2D.processMessage(msg);
+                else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_TEXTURE_CUBE_MAP
+                        && texCube != null)
+                    return texCube.processMessage(msg);
+                return true;
+            case glTexParameteri:
+                return texParameter(msg.getArg0(), msg.getArg1(), msg.getArg2());
+            case glTexParameterf:
+                return texParameter(msg.getArg0(), msg.getArg1(),
+                        (int) Float.intBitsToFloat(msg.getArg2()));
+            default:
+                return false;
+        }
+    }
+
+    boolean bindTexture(final int target, final int name) {
+        final int index = activeTexture.value - GLEnum.GL_TEXTURE0.value;
+        if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_2D) {
+            tex2D = textures.get(name);
+            if (name != 0 && tex2D == null)
+                textures.put(name, tex2D = new GLTexture(name,
+                        GLEnum.GL_TEXTURE_2D));
+            if (index >= 0 && index < tmu2D.length)
+                tmu2D[index] = name;
+        } else if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_CUBE_MAP) {
+            texCube = textures.get(name);
+            if (name != 0 && texCube == null)
+                textures.put(name, texCube = new GLTexture(name,
+                        GLEnum.GL_TEXTURE_CUBE_MAP));
+            if (index >= 0 && index < tmu2D.length)
+                tmu2D[index] = name;
+        } else
+            assert false;
+        return true;
+    }
+
+    boolean texParameter(final int target, final int pname, final int param) {
+        GLTexture tex = null;
+        if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_2D)
+            tex = tex2D;
+        else if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_CUBE_MAP)
+            tex = texCube;
+        if (tex == null)
+            return true;
+        final GLEnum p = GLEnum.valueOf(param);
+        switch (GLEnum.valueOf(pname)) {
+            case GL_TEXTURE_WRAP_S:
+                tex.wrapS = p;
+                return true;
+            case GL_TEXTURE_WRAP_T:
+                tex.wrapT = p;
+                return true;
+            case GL_TEXTURE_MIN_FILTER:
+                tex.min = p;
+                return true;
+            case GL_TEXTURE_MAG_FILTER:
+                tex.mag = p;
+                return true;
+            default:
+                return true;
+        }
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
new file mode 100644
index 0000000..5f9d513
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
@@ -0,0 +1,542 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.sdklib.util.SparseArray;
+
+import java.nio.ByteBuffer;
+
+class GLBuffer implements Cloneable {
+    public final int name;
+    public GLEnum usage;
+    public GLEnum target;
+    /** in SampleView.targetByteOrder */
+    public ByteBuffer data;
+
+    public GLBuffer(final int name) {
+        this.name = name;
+    }
+
+    /** deep copy */
+    @Override
+    public GLBuffer clone() {
+        try {
+            GLBuffer copy = (GLBuffer) super.clone();
+            if (data != null) {
+                copy.data = ByteBuffer.allocate(data.capacity());
+                copy.data.order(SampleView.targetByteOrder);
+                data.position(0);
+                copy.data.put(data);
+            }
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+}
+
+class GLAttribPointer implements Cloneable {
+    public int size; // number of values per vertex
+    public GLEnum type; // data type
+    public int stride; // bytes
+    /**
+     * element stride in bytes, used when fetching from buffer; not for fetching
+     * from user pointer since server already packed elements
+     */
+    int elemStride; // in bytes
+    /** element size in bytes */
+    int elemSize;
+    public int ptr; // pointer in debugger server or byte offset into buffer
+    public GLBuffer buffer;
+    public boolean normalized;
+    public boolean enabled;
+
+    /** deep copy, re-maps buffer into copyBuffers */
+    public GLAttribPointer clone(SparseArray<GLBuffer> copyBuffers) {
+        try {
+            GLAttribPointer copy = (GLAttribPointer) super.clone();
+            if (buffer != null)
+                copy.buffer = copyBuffers.get(buffer.name);
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+}
+
+public class GLServerVertex implements Cloneable {
+    public SparseArray<GLBuffer> buffers = new SparseArray<GLBuffer>();
+    public GLBuffer attribBuffer, indexBuffer; // current binding
+    public GLAttribPointer attribPointers[];
+    public float defaultAttribs[][];
+
+    public GLServerVertex(final int MAX_VERTEX_ATTRIBS) {
+        buffers.append(0, null);
+        attribPointers = new GLAttribPointer[MAX_VERTEX_ATTRIBS];
+        for (int i = 0; i < attribPointers.length; i++)
+            attribPointers[i] = new GLAttribPointer();
+        defaultAttribs = new float[MAX_VERTEX_ATTRIBS][4];
+        for (int i = 0; i < defaultAttribs.length; i++) {
+            defaultAttribs[i][0] = 0;
+            defaultAttribs[i][1] = 0;
+            defaultAttribs[i][2] = 0;
+            defaultAttribs[i][3] = 1;
+        }
+    }
+
+    /** deep copy */
+    @Override
+    public GLServerVertex clone() {
+        try {
+            GLServerVertex copy = (GLServerVertex) super.clone();
+
+            copy.buffers = new SparseArray<GLBuffer>(buffers.size());
+            for (int i = 0; i < buffers.size(); i++)
+                if (buffers.valueAt(i) != null)
+                    copy.buffers.append(buffers.keyAt(i), buffers.valueAt(i).clone());
+                else
+                    copy.buffers.append(buffers.keyAt(i), null);
+
+            if (attribBuffer != null)
+                copy.attribBuffer = copy.buffers.get(attribBuffer.name);
+            if (indexBuffer != null)
+                copy.indexBuffer = copy.buffers.get(indexBuffer.name);
+
+            copy.attribPointers = new GLAttribPointer[attribPointers.length];
+            for (int i = 0; i < attribPointers.length; i++)
+                copy.attribPointers[i] = attribPointers[i].clone(copy.buffers);
+
+            copy.defaultAttribs = defaultAttribs.clone();
+
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+
+    /** returns true if processed */
+    public boolean process(final Message msg) {
+        switch (msg.getFunction()) {
+            case glBindBuffer:
+                glBindBuffer(msg);
+                return true;
+            case glBufferData:
+                glBufferData(msg);
+                return true;
+            case glBufferSubData:
+                glBufferSubData(msg);
+                return true;
+            case glDeleteBuffers:
+                glDeleteBuffers(msg);
+                return true;
+            case glDrawArrays:
+            case glDrawElements:
+                return true;
+            case glDisableVertexAttribArray:
+                glDisableVertexAttribArray(msg);
+                return true;
+            case glEnableVertexAttribArray:
+                glEnableVertexAttribArray(msg);
+                return true;
+            case glGenBuffers:
+                glGenBuffers(msg);
+                return true;
+            case glVertexAttribPointer:
+                glVertexAttribPointer(msg);
+                return true;
+            case glVertexAttrib1f:
+                glVertexAttrib1f(msg);
+                return true;
+            case glVertexAttrib1fv:
+                glVertexAttrib1fv(msg);
+                return true;
+            case glVertexAttrib2f:
+                glVertexAttrib2f(msg);
+                return true;
+            case glVertexAttrib2fv:
+                glVertexAttrib2fv(msg);
+                return true;
+            case glVertexAttrib3f:
+                glVertexAttrib3f(msg);
+                return true;
+            case glVertexAttrib3fv:
+                glVertexAttrib3fv(msg);
+                return true;
+            case glVertexAttrib4f:
+                glVertexAttrib4f(msg);
+                return true;
+            case glVertexAttrib4fv:
+                glVertexAttrib4fv(msg);
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    // void API_ENTRY(glBindBuffer)(GLenum target, GLuint buffer)
+    public void glBindBuffer(Message msg) {
+        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
+            attribBuffer = buffers.get(msg.getArg1());
+            if (null != attribBuffer)
+                attribBuffer.target = GLEnum.GL_ARRAY_BUFFER;
+        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
+            indexBuffer = buffers.get(msg.getArg1());
+            if (null != indexBuffer)
+                indexBuffer.target = GLEnum.GL_ELEMENT_ARRAY_BUFFER;
+        } else
+            assert false;
+    }
+
+    // void API_ENTRY(glBufferData)(GLenum target, GLsizeiptr size, const
+    // GLvoid:size:in data, GLenum usage)
+    public void glBufferData(Message msg) {
+        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
+            attribBuffer.usage = GLEnum.valueOf(msg.getArg3());
+            attribBuffer.data = msg.getData().asReadOnlyByteBuffer();
+            attribBuffer.data.order(SampleView.targetByteOrder);
+        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
+            indexBuffer.usage = GLEnum.valueOf(msg.getArg3());
+            indexBuffer.data = msg.getData().asReadOnlyByteBuffer();
+            indexBuffer.data.order(SampleView.targetByteOrder);
+        } else
+            assert false;
+    }
+
+    // void API_ENTRY(glBufferSubData)(GLenum target, GLintptr offset,
+    // GLsizeiptr size, const GLvoid:size:in data)
+    public void glBufferSubData(Message msg) {
+        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
+            if (attribBuffer.data.isReadOnly()) {
+                ByteBuffer buffer = ByteBuffer.allocate(attribBuffer.data.capacity());
+                buffer.order(SampleView.targetByteOrder);
+                buffer.put(attribBuffer.data);
+                attribBuffer.data = buffer;
+            }
+            attribBuffer.data.position(msg.getArg1());
+            attribBuffer.data.put(msg.getData().asReadOnlyByteBuffer());
+        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
+            if (indexBuffer.data.isReadOnly()) {
+                ByteBuffer buffer = ByteBuffer.allocate(indexBuffer.data.capacity());
+                buffer.order(SampleView.targetByteOrder);
+                buffer.put(indexBuffer.data);
+                indexBuffer.data = buffer;
+            }
+            indexBuffer.data.position(msg.getArg1());
+            indexBuffer.data.put(msg.getData().asReadOnlyByteBuffer());
+        } else
+            assert false;
+    }
+
+    // void glDeleteBuffers(GLsizei n, const GLuint* buffers)
+    public void glDeleteBuffers(Message msg) {
+        final int n = msg.getArg0();
+        final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
+        names.order(SampleView.targetByteOrder);
+        for (int i = 0; i < n; i++) {
+            final int name = names.getInt();
+            final GLBuffer buffer = buffers.get(name);
+            for (int j = 0; j < attribPointers.length; j++)
+                if (attribPointers[j].buffer == buffer) {
+                    attribPointers[j].buffer = null;
+                    attribPointers[j].enabled = false;
+                }
+            if (attribBuffer == buffer)
+                attribBuffer = null;
+            if (indexBuffer == buffer)
+                indexBuffer = null;
+            buffers.remove(name);
+        }
+    }
+
+    // void glDisableVertexAttribArray(GLuint index)
+    public void glDisableVertexAttribArray(Message msg) {
+        if (msg.getArg0() >= 0 && msg.getArg0() < attribPointers.length)
+            attribPointers[msg.getArg0()].enabled = false;
+    }
+
+    float fetchConvert(final ByteBuffer src, final GLEnum type, final boolean normalized) {
+        if (GLEnum.GL_FLOAT == type)
+            return Float.intBitsToFloat(src.getInt());
+        else if (GLEnum.GL_UNSIGNED_INT == type)
+            if (normalized)
+                return (src.getInt() & 0xffffffffL) / (2e32f - 1);
+            else
+                return src.getInt() & 0xffffffffL;
+        else if (GLEnum.GL_INT == type)
+            if (normalized)
+                return (src.getInt() * 2 + 1) / (2e32f - 1);
+            else
+                return src.getInt();
+        else if (GLEnum.GL_UNSIGNED_SHORT == type)
+            if (normalized)
+                return (src.getShort() & 0xffff) / (2e16f - 1);
+            else
+                return src.getShort() & 0xffff;
+        else if (GLEnum.GL_SHORT == type)
+            if (normalized)
+                return (src.getShort() * 2 + 1) / (2e16f - 1);
+            else
+                return src.getShort();
+        else if (GLEnum.GL_UNSIGNED_BYTE == type)
+            if (normalized)
+                return (src.get() & 0xff) / (2e8f - 1);
+            else
+                return src.get() & 0xff;
+        else if (GLEnum.GL_BYTE == type)
+            if (normalized)
+                return (src.get() * 2 + 1) / (2e8f - 1);
+            else
+                return src.get();
+        else if (GLEnum.GL_FIXED == type)
+            if (normalized)
+                return (src.getInt() * 2 + 1) / (2e32f - 1);
+            else
+                return src.getInt() / (2e16f);
+        else
+            assert false;
+        return 0;
+    }
+
+    static int typeSize(final GLEnum type) {
+        switch (type) {
+            case GL_FLOAT:
+            case GL_UNSIGNED_INT:
+            case GL_INT:
+            case GL_FIXED:
+                return 4;
+            case GL_UNSIGNED_SHORT:
+            case GL_SHORT:
+                return 2;
+            case GL_UNSIGNED_BYTE:
+            case GL_BYTE:
+                return 1;
+            default:
+                assert false;
+                return 0;
+        }
+    }
+
+    void fetch(final int maxAttrib, final int index, final int dstIdx, final ByteBuffer nonVBO,
+            final float[][] fetchedAttribs) {
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer attrib = attribPointers[i];
+            int size = 0;
+            if (attrib.enabled) {
+                size = attrib.size;
+                if (null != attrib.buffer) {
+                    final ByteBuffer src = attrib.buffer.data;
+                    src.position(attrib.ptr + index * attrib.elemStride);
+                    for (int j = 0; j < size; j++)
+                        fetchedAttribs[i][dstIdx * 4 + j] = fetchConvert(src, attrib.type,
+                                attrib.normalized);
+                } else
+                    for (int j = 0; j < size; j++)
+                        fetchedAttribs[i][dstIdx * 4 + j] = fetchConvert(nonVBO, attrib.type,
+                                attrib.normalized);
+            }
+            if (size < 1)
+                fetchedAttribs[i][dstIdx * 4 + 0] = defaultAttribs[i][0];
+            if (size < 2)
+                fetchedAttribs[i][dstIdx * 4 + 1] = defaultAttribs[i][1];
+            if (size < 3)
+                fetchedAttribs[i][dstIdx * 4 + 2] = defaultAttribs[i][2];
+            if (size < 4)
+                fetchedAttribs[i][dstIdx * 4 + 3] = defaultAttribs[i][3];
+        }
+    }
+
+    /**
+     * fetches and converts vertex data from buffers, defaults and user pointers
+     * into MessageData; mainly for display use
+     */
+    public void glDrawArrays(MessageData msgData) {
+        final Message msg = msgData.msg;
+        if (!msg.hasArg7())
+            return;
+        final int maxAttrib = msg.getArg7();
+        final int first = msg.getArg1(), count = msg.getArg2();
+        msgData.attribs = new float[maxAttrib][count * 4];
+        ByteBuffer arrays = null;
+        if (msg.hasData()) // server sends user pointer attribs
+        {
+            arrays = msg.getData().asReadOnlyByteBuffer();
+            arrays.order(SampleView.targetByteOrder);
+        }
+        for (int i = 0; i < count; i++)
+            fetch(maxAttrib, first + i, i, arrays, msgData.attribs);
+        assert null == arrays || arrays.remaining() == 0;
+    }
+
+    // void glDrawElements(GLenum mode, GLsizei count, GLenum type, const
+    // GLvoid* indices)
+    /**
+     * fetches and converts vertex data from buffers, defaults and user pointers
+     * and indices from buffer/pointer into MessageData; mainly for display use
+     */
+    public void glDrawElements(MessageData msgData) {
+        final Message msg = msgData.msg;
+        if (!msg.hasArg7())
+            return;
+        final int maxAttrib = msg.getArg7();
+        final int count = msg.getArg1();
+        final GLEnum type = GLEnum.valueOf(msg.getArg2());
+        msgData.attribs = new float[maxAttrib][count * 4];
+        msgData.indices = new short[count];
+        ByteBuffer arrays = null, index = null;
+        if (msg.hasData()) // server sends user pointer attribs
+        {
+            arrays = msg.getData().asReadOnlyByteBuffer();
+            arrays.order(SampleView.targetByteOrder);
+        }
+        if (null == indexBuffer)
+            index = arrays; // server also interleaves user pointer indices
+        else {
+            index = indexBuffer.data;
+            index.position(msg.getArg3());
+        }
+        if (GLEnum.GL_UNSIGNED_SHORT == type) {
+            for (int i = 0; i < count; i++) {
+                msgData.indices[i] = index.getShort();
+                fetch(maxAttrib, msgData.indices[i] & 0xffff, i, arrays, msgData.attribs);
+            }
+        } else if (GLEnum.GL_UNSIGNED_BYTE == type) {
+            for (int i = 0; i < count; i++) {
+                msgData.indices[i] = (short) (index.get() & 0xff);
+                fetch(maxAttrib, msgData.indices[i], i, arrays, msgData.attribs);
+            }
+        } else
+            assert false;
+        assert null == arrays || arrays.remaining() == 0;
+    }
+
+    // void glEnableVertexAttribArray(GLuint index)
+    public void glEnableVertexAttribArray(Message msg) {
+        if (msg.getArg0() >= 0 && msg.getArg0() < attribPointers.length)
+            attribPointers[msg.getArg0()].enabled = true;
+    }
+
+    // void API_ENTRY(glGenBuffers)(GLsizei n, GLuint:n:out buffers)
+    public void glGenBuffers(Message msg) {
+        final int n = msg.getArg0();
+        final ByteBuffer buffer = msg.getData().asReadOnlyByteBuffer();
+        buffer.order(SampleView.targetByteOrder);
+        for (int i = 0; i < n; i++) {
+            final int name = buffer.getInt();
+            final int index = buffers.indexOfKey(name);
+            if (index < 0)
+                buffers.append(name, new GLBuffer(name));
+        }
+    }
+
+    // void glVertexAttribPointer(GLuint index, GLint size, GLenum type,
+    // GLboolean normalized, GLsizei stride, const GLvoid* ptr)
+    public void glVertexAttribPointer(Message msg) {
+        GLAttribPointer attrib = attribPointers[msg.getArg0()];
+        attrib.size = msg.getArg1();
+        attrib.type = GLEnum.valueOf(msg.getArg2());
+        attrib.normalized = msg.getArg3() != 0;
+        attrib.stride = msg.getArg4();
+        attrib.elemSize = attrib.size * typeSize(attrib.type);
+        if (attrib.stride == 0)
+            attrib.elemStride = attrib.elemSize;
+        else
+            attrib.elemStride = attrib.stride;
+        attrib.ptr = msg.getArg5();
+        attrib.buffer = attribBuffer;
+    }
+
+    // void glVertexAttrib1f(GLuint indx, GLfloat x)
+    public void glVertexAttrib1f(Message msg) {
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
+                0, 0, 1);
+    }
+
+    // void glVertexAttrib1fv(GLuint indx, const GLfloat* values)
+    public void glVertexAttrib1fv(Message msg) {
+        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        values.order(SampleView.targetByteOrder);
+        glVertexAttrib4f(msg.getArg0(),
+                Float.intBitsToFloat(values.getInt()),
+                0, 0, 1);
+    }
+
+    // void glVertexAttrib2f(GLuint indx, GLfloat x, GLfloat y)
+    public void glVertexAttrib2f(Message msg) {
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
+                Float.intBitsToFloat(msg.getArg2()), 0, 1);
+    }
+
+    // void glVertexAttrib2fv(GLuint indx, const GLfloat* values)
+    public void glVertexAttrib2fv(Message msg) {
+        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        values.order(SampleView.targetByteOrder);
+        glVertexAttrib4f(msg.getArg0(),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()), 0, 1);
+    }
+
+    // void glVertexAttrib3f(GLuint indx, GLfloat x, GLfloat y, GLfloat z)
+    public void glVertexAttrib3f(Message msg) {
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
+                Float.intBitsToFloat(msg.getArg2()),
+                Float.intBitsToFloat(msg.getArg3()), 1);
+    }
+
+    // void glVertexAttrib3fv(GLuint indx, const GLfloat* values)
+    public void glVertexAttrib3fv(Message msg) {
+        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        values.order(SampleView.targetByteOrder);
+        glVertexAttrib4f(msg.getArg0(),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()), 1);
+    }
+
+    public void glVertexAttrib4f(Message msg) {
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
+                Float.intBitsToFloat(msg.getArg2()),
+                Float.intBitsToFloat(msg.getArg3()),
+                Float.intBitsToFloat(msg.getArg4()));
+    }
+
+    void glVertexAttrib4f(int indx, float x, float y, float z, float w) {
+        if (indx < 0 || indx >= defaultAttribs.length)
+            return;
+        defaultAttribs[indx][0] = x;
+        defaultAttribs[indx][1] = y;
+        defaultAttribs[indx][2] = z;
+        defaultAttribs[indx][3] = w;
+    }
+
+    // void glVertexAttrib4fv(GLuint indx, const GLfloat* values)
+    public void glVertexAttrib4fv(Message msg) {
+        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        values.order(SampleView.targetByteOrder);
+        glVertexAttrib4f(msg.getArg0(),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()));
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
new file mode 100644
index 0000000..321c538
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
@@ -0,0 +1,128 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.widgets.Display;
+
+public class MessageData {
+    public final Message msg;
+    private Image image = null; // texture
+    public String shader = null; // shader source
+    public String text;
+    public String[] columns = new String[3];
+
+    float[][] attribs = null;
+    short[] indices;
+
+    public MessageData(final Device device, final Message msg, final Context context) {
+        this.msg = msg;
+        StringBuilder builder = new StringBuilder();
+        final Function function = msg.getFunction();
+        if (function != Message.Function.ACK && msg.getType() != Type.BeforeCall)
+            assert msg.hasTime();
+        builder.append(columns[0] = function.name());
+        while (builder.length() < 30)
+            builder.append(' ');
+        columns[1] = String.format("%.3f", msg.getTime());
+        if (msg.hasClock())
+            columns[1] += String.format(":%.3f", msg.getClock());
+        builder.append(columns[1]);
+
+        builder.append("  ");
+        builder.append(String.format("0x%08X", msg.getContextId()));
+        builder.append("  ");
+        columns[2] = "";
+        if (msg.getType() == Type.BeforeCall) // incomplete call, client SKIPPED
+            columns[2] = "[BeforeCall(AfterCall missing)] ";
+        else if (msg.getType() == Type.AfterGeneratedCall)
+            columns[2] = "[AfterGeneratedCall] ";
+        else
+            assert msg.getType() == Type.CompleteCall;
+        columns[2] += MessageFormatter.format(msg, false);
+        builder.append(columns[2]);
+        switch (function) {
+            case glDrawArrays:
+                if (!msg.hasArg7())
+                    break;
+                context.serverVertex.glDrawArrays(this);
+                break;
+            case glDrawElements:
+                if (!msg.hasArg7())
+                    break;
+                context.serverVertex.glDrawElements(this);
+                break;
+            case glShaderSource:
+                shader = msg.getData().toStringUtf8();
+                break;
+
+        }
+        text = builder.toString();
+    }
+
+    public Image getImage() {
+        if (image != null)
+            return image;
+        ImageData imageData = null;
+        switch (msg.getFunction()) {
+            case glTexImage2D:
+                if (!msg.hasData())
+                    return null;
+                imageData = MessageProcessor.receiveImage(msg.getArg3(), msg
+                        .getArg4(), msg.getArg6(), msg.getArg7(), msg.getData());
+                return image = new Image(Display.getCurrent(), imageData);
+            case glTexSubImage2D:
+                assert msg.hasData();
+                imageData = MessageProcessor.receiveImage(msg.getArg4(), msg
+                        .getArg5(), msg.getArg6(), msg.getArg7(), msg.getData());
+                return image = new Image(Display.getCurrent(), imageData);
+            case glCopyTexImage2D:
+                imageData = MessageProcessor.receiveImage(msg.getArg5(), msg.getArg6(),
+                        msg.getPixelFormat(), msg.getPixelType(), msg.getData());
+                imageData = imageData.scaledTo(imageData.width, -imageData.height);
+                return image = new Image(Display.getCurrent(), imageData);
+            case glCopyTexSubImage2D:
+                imageData = MessageProcessor.receiveImage(msg.getArg6(), msg.getArg7(),
+                        msg.getPixelFormat(), msg.getPixelType(), msg.getData());
+                imageData = imageData.scaledTo(imageData.width, -imageData.height);
+                return image = new Image(Display.getCurrent(), imageData);
+            case glReadPixels:
+                if (!msg.hasData())
+                    return null;
+                imageData = MessageProcessor.receiveImage(msg.getArg2(), msg.getArg3(),
+                        msg.getArg4(), msg.getArg5(), msg.getData());
+                imageData = imageData.scaledTo(imageData.width, -imageData.height);
+                return image = new Image(Display.getCurrent(), imageData);
+            case eglSwapBuffers:
+                if (!msg.hasData())
+                    return null;
+                imageData = MessageProcessor.receiveImage(msg.getImageWidth(),
+                        msg.getImageHeight(), msg.getPixelFormat(), msg.getPixelType(),
+                        msg.getData());
+                imageData = imageData.scaledTo(imageData.width, -imageData.height);
+                return image = new Image(Display.getCurrent(), imageData);
+            default:
+                return null;
+        }
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageFormatter.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageFormatter.java
new file mode 100644
index 0000000..b9fa681
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageFormatter.java
@@ -0,0 +1,1488 @@
+/*
+ ** Copyright 2011, 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.
+ */
+
+// auto generated by generate_MessageFormatter_java.py"
+
+package com.android.glesv2debugger;
+
+import java.nio.ByteBuffer;
+
+public class MessageFormatter {
+
+    static String formatFloats(int count, final ByteBuffer data) {
+        if (data.remaining() == 0)
+            return "{}";
+        data.order(SampleView.targetByteOrder);
+        String ret = "{";
+        for (int i = 0; i < count; i++) {
+            ret += Float.intBitsToFloat(data.getInt());
+            if (i < count - 1)
+                ret += ", ";
+        }
+        return ret + "}";
+    }
+
+    static String formatInts(int count, final ByteBuffer data) {
+        if (data.remaining() == 0)
+            return "{}";
+        data.order(SampleView.targetByteOrder);
+        String ret = "{";
+        for (int i = 0; i < count; i++) {
+            ret += data.getInt();
+            if (i < count - 1)
+                ret += ", ";
+        }
+        return ret + "}";
+    }
+
+    static String formatUInts(int count, final ByteBuffer data) {
+        if (data.remaining() == 0)
+            return "{}";
+        data.order(SampleView.targetByteOrder);
+        String ret = "{";
+        for (int i = 0; i < count; i++) {
+            long bits = data.getInt() & 0xffffffff;
+            ret += bits;
+            if (i < count - 1)
+                ret += ", ";
+        }
+        return ret + "}";
+    }
+
+    static String formatMatrix(int columns, int count, final ByteBuffer data) {
+        if (data.remaining() == 0)
+            return "{}";
+        data.order(SampleView.targetByteOrder);
+        String ret = "{";
+        for (int i = 0; i < count; i++) {
+            ret += Float.intBitsToFloat(data.getInt());
+            if (i < count - 1)
+                ret += ", ";
+            if (i % columns == columns - 1)
+                ret += "\n                                             ";
+        }
+        return ret + "}";
+    }
+
+    public static String format(final DebuggerMessage.Message msg,
+                                final boolean code) {
+        String str;
+        switch (msg.getFunction()) {
+            case glActiveTexture:
+                str = String.format("%s(%s%s)",
+                    (code ? "glActiveTexture" : "void")
+                    , (code ? "/*texture*/ " : "texture=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glAttachShader:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glAttachShader" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg1());
+                break;
+            case glBindAttribLocation:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glBindAttribLocation" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*index*/ " : "index=")
+                    , msg.getArg1()
+                    , (code ? "/*name*/ " : "name=")
+                    , (code ? "\"" : "") + msg.getData().toStringUtf8() + (code ? "\"" : ""));
+                break;
+            case glBindBuffer:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glBindBuffer" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*buffer*/ " : "buffer=")
+                    , (code ? "buffer_" : "") + msg.getArg1());
+                break;
+            case glBindFramebuffer:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glBindFramebuffer" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*framebuffer*/ " : "framebuffer=")
+                    , (code ? "framebuffer_" : "") + msg.getArg1());
+                break;
+            case glBindRenderbuffer:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glBindRenderbuffer" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*renderbuffer*/ " : "renderbuffer=")
+                    , (code ? "renderbuffer_" : "") + msg.getArg1());
+                break;
+            case glBindTexture:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glBindTexture" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*texture*/ " : "texture=")
+                    , (code ? "texture_" : "") + msg.getArg1());
+                break;
+            case glBlendColor:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glBlendColor" : "void")
+                    , (code ? "/*red*/ " : "red=")
+                    , Float.intBitsToFloat(msg.getArg0())
+                    , (code ? "/*green*/ " : "green=")
+                    , Float.intBitsToFloat(msg.getArg1())
+                    , (code ? "/*blue*/ " : "blue=")
+                    , Float.intBitsToFloat(msg.getArg2())
+                    , (code ? "/*alpha*/ " : "alpha=")
+                    , Float.intBitsToFloat(msg.getArg3()));
+                break;
+            case glBlendEquation:
+                str = String.format("%s(%s%s)",
+                    (code ? "glBlendEquation" : "void")
+                    , (code ? "/*mode*/ " : "mode=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glBlendEquationSeparate:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glBlendEquationSeparate" : "void")
+                    , (code ? "/*modeRGB*/ " : "modeRGB=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*modeAlpha*/ " : "modeAlpha=")
+                    , GLEnum.valueOf(msg.getArg1()));
+                break;
+            case glBlendFunc:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glBlendFunc" : "void")
+                    , (code ? "/*sfactor*/ " : "sfactor=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*dfactor*/ " : "dfactor=")
+                    , GLEnum.valueOf(msg.getArg1()));
+                break;
+            case glBlendFuncSeparate:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glBlendFuncSeparate" : "void")
+                    , (code ? "/*srcRGB*/ " : "srcRGB=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*dstRGB*/ " : "dstRGB=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*srcAlpha*/ " : "srcAlpha=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*dstAlpha*/ " : "dstAlpha=")
+                    , GLEnum.valueOf(msg.getArg3()));
+                break;
+            case glBufferData:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glBufferData" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*size*/ " : "size=")
+                    , msg.getArg1()
+                    , (code ? "/*data*/ " : "data=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2()))
+                    , (code ? "/*usage*/ " : "usage=")
+                    , GLEnum.valueOf(msg.getArg3()));
+                break;
+            case glBufferSubData:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glBufferSubData" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*offset*/ " : "offset=")
+                    , msg.getArg1()
+                    , (code ? "/*size*/ " : "size=")
+                    , msg.getArg2()
+                    , (code ? "/*data*/ " : "data=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3())));
+                break;
+            case glCheckFramebufferStatus:
+                str = String.format("%s(%s%s)",
+                    (code ? "glCheckFramebufferStatus" : GLEnum.valueOf(msg.getRet()))
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glClear:
+                str = String.format("%s(%s%s)",
+                    (code ? "glClear" : "void")
+                    , (code ? "/*mask*/ " : "mask=")
+                    , msg.getArg0());
+                break;
+            case glClearColor:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glClearColor" : "void")
+                    , (code ? "/*red*/ " : "red=")
+                    , Float.intBitsToFloat(msg.getArg0())
+                    , (code ? "/*green*/ " : "green=")
+                    , Float.intBitsToFloat(msg.getArg1())
+                    , (code ? "/*blue*/ " : "blue=")
+                    , Float.intBitsToFloat(msg.getArg2())
+                    , (code ? "/*alpha*/ " : "alpha=")
+                    , Float.intBitsToFloat(msg.getArg3()));
+                break;
+            case glClearDepthf:
+                str = String.format("%s(%s%s)",
+                    (code ? "glClearDepthf" : "void")
+                    , (code ? "/*depth*/ " : "depth=")
+                    , Float.intBitsToFloat(msg.getArg0()));
+                break;
+            case glClearStencil:
+                str = String.format("%s(%s%s)",
+                    (code ? "glClearStencil" : "void")
+                    , (code ? "/*s*/ " : "s=")
+                    , msg.getArg0());
+                break;
+            case glColorMask:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glColorMask" : "void")
+                    , (code ? "/*red*/ " : "red=")
+                    , msg.getArg0()
+                    , (code ? "/*green*/ " : "green=")
+                    , msg.getArg1()
+                    , (code ? "/*blue*/ " : "blue=")
+                    , msg.getArg2()
+                    , (code ? "/*alpha*/ " : "alpha=")
+                    , msg.getArg3());
+                break;
+            case glCompileShader:
+                str = String.format("%s(%s%s)",
+                    (code ? "glCompileShader" : "void")
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg0());
+                break;
+            case glCompressedTexImage2D:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glCompressedTexImage2D" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*level*/ " : "level=")
+                    , msg.getArg1()
+                    , (code ? "/*internalformat*/ " : "internalformat=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg3()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg4()
+                    , (code ? "/*border*/ " : "border=")
+                    , msg.getArg5()
+                    , (code ? "/*imageSize*/ " : "imageSize=")
+                    , msg.getArg6()
+                    , (code ? "/*data*/ " : "data=")
+                    , (code ? "arg7" : "0x" + Integer.toHexString(msg.getArg7())));
+                break;
+            case glCompressedTexSubImage2D:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glCompressedTexSubImage2D" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*level*/ " : "level=")
+                    , msg.getArg1()
+                    , (code ? "/*xoffset*/ " : "xoffset=")
+                    , msg.getArg2()
+                    , (code ? "/*yoffset*/ " : "yoffset=")
+                    , msg.getArg3()
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg4()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg5()
+                    , (code ? "/*format*/ " : "format=")
+                    , GLEnum.valueOf(msg.getArg6())
+                    , (code ? "/*imageSize*/ " : "imageSize=")
+                    , msg.getArg7()
+                    , (code ? "/*data*/ " : "data=")
+                    , (code ? "arg8" : "0x" + Integer.toHexString(msg.getArg8())));
+                break;
+            case glCopyTexImage2D:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glCopyTexImage2D" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*level*/ " : "level=")
+                    , msg.getArg1()
+                    , (code ? "/*internalformat*/ " : "internalformat=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg3()
+                    , (code ? "/*y*/ " : "y=")
+                    , msg.getArg4()
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg5()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg6()
+                    , (code ? "/*border*/ " : "border=")
+                    , msg.getArg7());
+                break;
+            case glCopyTexSubImage2D:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glCopyTexSubImage2D" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*level*/ " : "level=")
+                    , msg.getArg1()
+                    , (code ? "/*xoffset*/ " : "xoffset=")
+                    , msg.getArg2()
+                    , (code ? "/*yoffset*/ " : "yoffset=")
+                    , msg.getArg3()
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg4()
+                    , (code ? "/*y*/ " : "y=")
+                    , msg.getArg5()
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg6()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg7());
+                break;
+            case glCreateProgram:
+                str = String.format("%s()",
+                    (code ? "glCreateProgram" : msg.getRet())
+);
+                break;
+            case glCreateShader:
+                str = String.format("%s(%s%s)",
+                    (code ? "glCreateShader" : msg.getRet())
+                    , (code ? "/*type*/ " : "type=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glCullFace:
+                str = String.format("%s(%s%s)",
+                    (code ? "glCullFace" : "void")
+                    , (code ? "/*mode*/ " : "mode=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glDeleteBuffers:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glDeleteBuffers" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*buffers*/ " : "buffers=")
+                    , (code ? "(GLuint [])" : "") +  formatUInts(1 * msg.getArg0(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glDeleteFramebuffers:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glDeleteFramebuffers" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*framebuffers*/ " : "framebuffers=")
+                    , (code ? "(GLuint [])" : "") +  formatUInts(1 * msg.getArg0(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glDeleteProgram:
+                str = String.format("%s(%s%s)",
+                    (code ? "glDeleteProgram" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0());
+                break;
+            case glDeleteRenderbuffers:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glDeleteRenderbuffers" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*renderbuffers*/ " : "renderbuffers=")
+                    , (code ? "(GLuint [])" : "") +  formatUInts(1 * msg.getArg0(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glDeleteShader:
+                str = String.format("%s(%s%s)",
+                    (code ? "glDeleteShader" : "void")
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg0());
+                break;
+            case glDeleteTextures:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glDeleteTextures" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*textures*/ " : "textures=")
+                    , (code ? "(GLuint [])" : "") +  formatUInts(1 * msg.getArg0(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glDepthFunc:
+                str = String.format("%s(%s%s)",
+                    (code ? "glDepthFunc" : "void")
+                    , (code ? "/*func*/ " : "func=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glDepthMask:
+                str = String.format("%s(%s%s)",
+                    (code ? "glDepthMask" : "void")
+                    , (code ? "/*flag*/ " : "flag=")
+                    , msg.getArg0());
+                break;
+            case glDepthRangef:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glDepthRangef" : "void")
+                    , (code ? "/*zNear*/ " : "zNear=")
+                    , Float.intBitsToFloat(msg.getArg0())
+                    , (code ? "/*zFar*/ " : "zFar=")
+                    , Float.intBitsToFloat(msg.getArg1()));
+                break;
+            case glDetachShader:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glDetachShader" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg1());
+                break;
+            case glDisable:
+                str = String.format("%s(%s%s)",
+                    (code ? "glDisable" : "void")
+                    , (code ? "/*cap*/ " : "cap=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glDisableVertexAttribArray:
+                str = String.format("%s(%s%s)",
+                    (code ? "glDisableVertexAttribArray" : "void")
+                    , (code ? "/*index*/ " : "index=")
+                    , msg.getArg0());
+                break;
+            case glDrawArrays:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glDrawArrays" : "void")
+                    , (code ? "/*mode*/ " : "mode=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*first*/ " : "first=")
+                    , msg.getArg1()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg2());
+                break;
+            case glDrawElements:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glDrawElements" : "void")
+                    , (code ? "/*mode*/ " : "mode=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*type*/ " : "type=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*indices*/ " : "indices=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3())));
+                break;
+            case glEnable:
+                str = String.format("%s(%s%s)",
+                    (code ? "glEnable" : "void")
+                    , (code ? "/*cap*/ " : "cap=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glEnableVertexAttribArray:
+                str = String.format("%s(%s%s)",
+                    (code ? "glEnableVertexAttribArray" : "void")
+                    , (code ? "/*index*/ " : "index=")
+                    , msg.getArg0());
+                break;
+            case glFinish:
+                str = String.format("%s()",
+                    (code ? "glFinish" : "void")
+);
+                break;
+            case glFlush:
+                str = String.format("%s()",
+                    (code ? "glFlush" : "void")
+);
+                break;
+            case glFramebufferRenderbuffer:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glFramebufferRenderbuffer" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*attachment*/ " : "attachment=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*renderbuffertarget*/ " : "renderbuffertarget=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*renderbuffer*/ " : "renderbuffer=")
+                    , (code ? "renderbuffer_" : "") + msg.getArg3());
+                break;
+            case glFramebufferTexture2D:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glFramebufferTexture2D" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*attachment*/ " : "attachment=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*textarget*/ " : "textarget=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*texture*/ " : "texture=")
+                    , (code ? "texture_" : "") + msg.getArg3()
+                    , (code ? "/*level*/ " : "level=")
+                    , msg.getArg4());
+                break;
+            case glFrontFace:
+                str = String.format("%s(%s%s)",
+                    (code ? "glFrontFace" : "void")
+                    , (code ? "/*mode*/ " : "mode=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glGenBuffers:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGenBuffers" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*buffers*/ " : "buffers=")
+                    , (code ? "(GLuint [])" : "") +  formatUInts(1 * msg.getArg0(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glGenerateMipmap:
+                str = String.format("%s(%s%s)",
+                    (code ? "glGenerateMipmap" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glGenFramebuffers:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGenFramebuffers" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*framebuffers*/ " : "framebuffers=")
+                    , (code ? "(GLuint [])" : "") +  formatUInts(1 * msg.getArg0(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glGenRenderbuffers:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGenRenderbuffers" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*renderbuffers*/ " : "renderbuffers=")
+                    , (code ? "(GLuint [])" : "") +  formatUInts(1 * msg.getArg0(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glGenTextures:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGenTextures" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*textures*/ " : "textures=")
+                    , (code ? "(GLuint [])" : "") +  formatUInts(1 * msg.getArg0(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glGetActiveAttrib:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glGetActiveAttrib" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*index*/ " : "index=")
+                    , msg.getArg1()
+                    , (code ? "/*bufsize*/ " : "bufsize=")
+                    , msg.getArg2()
+                    , (code ? "/*length*/ " : "length=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3()))
+                    , (code ? "/*size*/ " : "size=")
+                    , (code ? "arg4" : "0x" + Integer.toHexString(msg.getArg4()))
+                    , (code ? "/*type*/ " : "type=")
+                    , (code ? "arg5" : "0x" + Integer.toHexString(msg.getArg5()))
+                    , (code ? "/*name*/ " : "name=")
+                    , (code ? "\"" : "") + msg.getData().toStringUtf8() + (code ? "\"" : ""));
+                break;
+            case glGetActiveUniform:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glGetActiveUniform" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*index*/ " : "index=")
+                    , msg.getArg1()
+                    , (code ? "/*bufsize*/ " : "bufsize=")
+                    , msg.getArg2()
+                    , (code ? "/*length*/ " : "length=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3()))
+                    , (code ? "/*size*/ " : "size=")
+                    , (code ? "arg4" : "0x" + Integer.toHexString(msg.getArg4()))
+                    , (code ? "/*type*/ " : "type=")
+                    , (code ? "arg5" : "0x" + Integer.toHexString(msg.getArg5()))
+                    , (code ? "/*name*/ " : "name=")
+                    , (code ? "\"" : "") + msg.getData().toStringUtf8() + (code ? "\"" : ""));
+                break;
+            case glGetAttachedShaders:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glGetAttachedShaders" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*maxcount*/ " : "maxcount=")
+                    , msg.getArg1()
+                    , (code ? "/*count*/ " : "count=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2()))
+                    , (code ? "/*shaders*/ " : "shaders=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3())));
+                break;
+            case glGetAttribLocation:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGetAttribLocation" : msg.getRet())
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*name*/ " : "name=")
+                    , (code ? "\"" : "") + msg.getData().toStringUtf8() + (code ? "\"" : ""));
+                break;
+            case glGetBooleanv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGetBooleanv" : "void")
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg1" : "0x" + Integer.toHexString(msg.getArg1())));
+                break;
+            case glGetBufferParameteriv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetBufferParameteriv" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glGetError:
+                str = String.format("%s()",
+                    (code ? "glGetError" : GLEnum.valueOf(msg.getRet()))
+);
+                break;
+            case glGetFloatv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGetFloatv" : "void")
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg1" : "0x" + Integer.toHexString(msg.getArg1())));
+                break;
+            case glGetFramebufferAttachmentParameteriv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glGetFramebufferAttachmentParameteriv" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*attachment*/ " : "attachment=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3())));
+                break;
+            case glGetIntegerv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGetIntegerv" : "void")
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg1" : "0x" + Integer.toHexString(msg.getArg1())));
+                break;
+            case glGetProgramiv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetProgramiv" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "(GLint [])" : "") + formatInts(1, msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glGetProgramInfoLog:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glGetProgramInfoLog" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*bufsize*/ " : "bufsize=")
+                    , msg.getArg1()
+                    , (code ? "/*length*/ " : "length=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2()))
+                    , (code ? "/*infolog*/ " : "infolog=")
+                    , (code ? "\"" : "") + msg.getData().toStringUtf8() + (code ? "\"" : ""));
+                break;
+            case glGetRenderbufferParameteriv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetRenderbufferParameteriv" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glGetShaderiv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetShaderiv" : "void")
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg0()
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "(GLint [])" : "") + formatInts(1, msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glGetShaderInfoLog:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glGetShaderInfoLog" : "void")
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg0()
+                    , (code ? "/*bufsize*/ " : "bufsize=")
+                    , msg.getArg1()
+                    , (code ? "/*length*/ " : "length=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2()))
+                    , (code ? "/*infolog*/ " : "infolog=")
+                    , (code ? "\"" : "") + msg.getData().toStringUtf8() + (code ? "\"" : ""));
+                break;
+            case glGetShaderPrecisionFormat:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glGetShaderPrecisionFormat" : "void")
+                    , (code ? "/*shadertype*/ " : "shadertype=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*precisiontype*/ " : "precisiontype=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*range*/ " : "range=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2()))
+                    , (code ? "/*precision*/ " : "precision=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3())));
+                break;
+            case glGetShaderSource:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glGetShaderSource" : "void")
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg0()
+                    , (code ? "/*bufsize*/ " : "bufsize=")
+                    , msg.getArg1()
+                    , (code ? "/*length*/ " : "length=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2()))
+                    , (code ? "/*source*/ " : "source=")
+                    , (code ? "\"" : "") + msg.getData().toStringUtf8() + (code ? "\"" : ""));
+                break;
+            case glGetString:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s)",
+                    (code ? "glGetString" : "0x" + Integer.toHexString(msg.getRet()))
+                    , (code ? "/*name*/ " : "name=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glGetTexParameterfv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetTexParameterfv" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glGetTexParameteriv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetTexParameteriv" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glGetUniformfv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetUniformfv" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg1()
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glGetUniformiv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetUniformiv" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg1()
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glGetUniformLocation:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glGetUniformLocation" : msg.getRet())
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0()
+                    , (code ? "/*name*/ " : "name=")
+                    , (code ? "\"" : "") + msg.getData().toStringUtf8() + (code ? "\"" : ""));
+                break;
+            case glGetVertexAttribfv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetVertexAttribfv" : "void")
+                    , (code ? "/*index*/ " : "index=")
+                    , msg.getArg0()
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glGetVertexAttribiv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetVertexAttribiv" : "void")
+                    , (code ? "/*index*/ " : "index=")
+                    , msg.getArg0()
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glGetVertexAttribPointerv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glGetVertexAttribPointerv" : "void")
+                    , (code ? "/*index*/ " : "index=")
+                    , msg.getArg0()
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*pointer*/ " : "pointer=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glHint:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glHint" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*mode*/ " : "mode=")
+                    , GLEnum.valueOf(msg.getArg1()));
+                break;
+            case glIsBuffer:
+                str = String.format("%s(%s%s)",
+                    (code ? "glIsBuffer" : msg.getRet())
+                    , (code ? "/*buffer*/ " : "buffer=")
+                    , (code ? "buffer_" : "") + msg.getArg0());
+                break;
+            case glIsEnabled:
+                str = String.format("%s(%s%s)",
+                    (code ? "glIsEnabled" : msg.getRet())
+                    , (code ? "/*cap*/ " : "cap=")
+                    , GLEnum.valueOf(msg.getArg0()));
+                break;
+            case glIsFramebuffer:
+                str = String.format("%s(%s%s)",
+                    (code ? "glIsFramebuffer" : msg.getRet())
+                    , (code ? "/*framebuffer*/ " : "framebuffer=")
+                    , (code ? "framebuffer_" : "") + msg.getArg0());
+                break;
+            case glIsProgram:
+                str = String.format("%s(%s%s)",
+                    (code ? "glIsProgram" : msg.getRet())
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0());
+                break;
+            case glIsRenderbuffer:
+                str = String.format("%s(%s%s)",
+                    (code ? "glIsRenderbuffer" : msg.getRet())
+                    , (code ? "/*renderbuffer*/ " : "renderbuffer=")
+                    , (code ? "renderbuffer_" : "") + msg.getArg0());
+                break;
+            case glIsShader:
+                str = String.format("%s(%s%s)",
+                    (code ? "glIsShader" : msg.getRet())
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg0());
+                break;
+            case glIsTexture:
+                str = String.format("%s(%s%s)",
+                    (code ? "glIsTexture" : msg.getRet())
+                    , (code ? "/*texture*/ " : "texture=")
+                    , (code ? "texture_" : "") + msg.getArg0());
+                break;
+            case glLineWidth:
+                str = String.format("%s(%s%s)",
+                    (code ? "glLineWidth" : "void")
+                    , (code ? "/*width*/ " : "width=")
+                    , Float.intBitsToFloat(msg.getArg0()));
+                break;
+            case glLinkProgram:
+                str = String.format("%s(%s%s)",
+                    (code ? "glLinkProgram" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0());
+                break;
+            case glPixelStorei:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glPixelStorei" : "void")
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*param*/ " : "param=")
+                    , msg.getArg1());
+                break;
+            case glPolygonOffset:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glPolygonOffset" : "void")
+                    , (code ? "/*factor*/ " : "factor=")
+                    , Float.intBitsToFloat(msg.getArg0())
+                    , (code ? "/*units*/ " : "units=")
+                    , Float.intBitsToFloat(msg.getArg1()));
+                break;
+            case glReadPixels:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glReadPixels" : "void")
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg0()
+                    , (code ? "/*y*/ " : "y=")
+                    , msg.getArg1()
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg2()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg3()
+                    , (code ? "/*format*/ " : "format=")
+                    , GLEnum.valueOf(msg.getArg4())
+                    , (code ? "/*type*/ " : "type=")
+                    , GLEnum.valueOf(msg.getArg5())
+                    , (code ? "/*pixels*/ " : "pixels=")
+                    , (code ? "arg6" : "0x" + Integer.toHexString(msg.getArg6())));
+                break;
+            case glReleaseShaderCompiler:
+                str = String.format("%s()",
+                    (code ? "glReleaseShaderCompiler" : "void")
+);
+                break;
+            case glRenderbufferStorage:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glRenderbufferStorage" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*internalformat*/ " : "internalformat=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg2()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg3());
+                break;
+            case glSampleCoverage:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glSampleCoverage" : "void")
+                    , (code ? "/*value*/ " : "value=")
+                    , Float.intBitsToFloat(msg.getArg0())
+                    , (code ? "/*invert*/ " : "invert=")
+                    , msg.getArg1());
+                break;
+            case glScissor:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glScissor" : "void")
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg0()
+                    , (code ? "/*y*/ " : "y=")
+                    , msg.getArg1()
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg2()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg3());
+                break;
+            case glShaderBinary:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glShaderBinary" : "void")
+                    , (code ? "/*n*/ " : "n=")
+                    , msg.getArg0()
+                    , (code ? "/*shaders*/ " : "shaders=")
+                    , (code ? "arg1" : "0x" + Integer.toHexString(msg.getArg1()))
+                    , (code ? "/*binaryformat*/ " : "binaryformat=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*binary*/ " : "binary=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3()))
+                    , (code ? "/*length*/ " : "length=")
+                    , msg.getArg4());
+                break;
+            case glShaderSource:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glShaderSource" : "void")
+                    , (code ? "/*shader*/ " : "shader=")
+                    , (code ? "shader_" : "") + msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*string*/ " : "string=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2()))
+                    , (code ? "/*length*/ " : "length=")
+                    , (code ? "arg3" : "0x" + Integer.toHexString(msg.getArg3())));
+                break;
+            case glStencilFunc:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glStencilFunc" : "void")
+                    , (code ? "/*func*/ " : "func=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*ref*/ " : "ref=")
+                    , msg.getArg1()
+                    , (code ? "/*mask*/ " : "mask=")
+                    , msg.getArg2());
+                break;
+            case glStencilFuncSeparate:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glStencilFuncSeparate" : "void")
+                    , (code ? "/*face*/ " : "face=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*func*/ " : "func=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*ref*/ " : "ref=")
+                    , msg.getArg2()
+                    , (code ? "/*mask*/ " : "mask=")
+                    , msg.getArg3());
+                break;
+            case glStencilMask:
+                str = String.format("%s(%s%s)",
+                    (code ? "glStencilMask" : "void")
+                    , (code ? "/*mask*/ " : "mask=")
+                    , msg.getArg0());
+                break;
+            case glStencilMaskSeparate:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glStencilMaskSeparate" : "void")
+                    , (code ? "/*face*/ " : "face=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*mask*/ " : "mask=")
+                    , msg.getArg1());
+                break;
+            case glStencilOp:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glStencilOp" : "void")
+                    , (code ? "/*fail*/ " : "fail=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*zfail*/ " : "zfail=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*zpass*/ " : "zpass=")
+                    , GLEnum.valueOf(msg.getArg2()));
+                break;
+            case glStencilOpSeparate:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glStencilOpSeparate" : "void")
+                    , (code ? "/*face*/ " : "face=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*fail*/ " : "fail=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*zfail*/ " : "zfail=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*zpass*/ " : "zpass=")
+                    , GLEnum.valueOf(msg.getArg3()));
+                break;
+            case glTexImage2D:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glTexImage2D" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*level*/ " : "level=")
+                    , msg.getArg1()
+                    , (code ? "/*internalformat*/ " : "internalformat=")
+                    , msg.getArg2()
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg3()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg4()
+                    , (code ? "/*border*/ " : "border=")
+                    , msg.getArg5()
+                    , (code ? "/*format*/ " : "format=")
+                    , GLEnum.valueOf(msg.getArg6())
+                    , (code ? "/*type*/ " : "type=")
+                    , GLEnum.valueOf(msg.getArg7())
+                    , (code ? "/*pixels*/ " : "pixels=")
+                    , (code ? "arg8" : "0x" + Integer.toHexString(msg.getArg8())));
+                break;
+            case glTexParameterf:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glTexParameterf" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*param*/ " : "param=")
+                    , Float.intBitsToFloat(msg.getArg2()));
+                break;
+            case glTexParameterfv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glTexParameterfv" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glTexParameteri:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glTexParameteri" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*param*/ " : "param=")
+                    , msg.getArg2());
+                break;
+            case glTexParameteriv:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glTexParameteriv" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*pname*/ " : "pname=")
+                    , GLEnum.valueOf(msg.getArg1())
+                    , (code ? "/*params*/ " : "params=")
+                    , (code ? "arg2" : "0x" + Integer.toHexString(msg.getArg2())));
+                break;
+            case glTexSubImage2D:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glTexSubImage2D" : "void")
+                    , (code ? "/*target*/ " : "target=")
+                    , GLEnum.valueOf(msg.getArg0())
+                    , (code ? "/*level*/ " : "level=")
+                    , msg.getArg1()
+                    , (code ? "/*xoffset*/ " : "xoffset=")
+                    , msg.getArg2()
+                    , (code ? "/*yoffset*/ " : "yoffset=")
+                    , msg.getArg3()
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg4()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg5()
+                    , (code ? "/*format*/ " : "format=")
+                    , GLEnum.valueOf(msg.getArg6())
+                    , (code ? "/*type*/ " : "type=")
+                    , GLEnum.valueOf(msg.getArg7())
+                    , (code ? "/*pixels*/ " : "pixels=")
+                    , (code ? "arg8" : "0x" + Integer.toHexString(msg.getArg8())));
+                break;
+            case glUniform1f:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glUniform1f" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , Float.intBitsToFloat(msg.getArg1()));
+                break;
+            case glUniform1fv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform1fv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*v*/ " : "v=")
+                    , (code ? "(GLfloat [])" : "") +  formatFloats(1 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniform1i:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glUniform1i" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg1());
+                break;
+            case glUniform1iv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform1iv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*v*/ " : "v=")
+                    , (code ? "(GLint [])" : "") +  formatInts(1 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniform2f:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform2f" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , Float.intBitsToFloat(msg.getArg1())
+                    , (code ? "/*y*/ " : "y=")
+                    , Float.intBitsToFloat(msg.getArg2()));
+                break;
+            case glUniform2fv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform2fv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*v*/ " : "v=")
+                    , (code ? "(GLfloat [])" : "") +  formatFloats(2 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniform2i:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform2i" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg1()
+                    , (code ? "/*y*/ " : "y=")
+                    , msg.getArg2());
+                break;
+            case glUniform2iv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform2iv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*v*/ " : "v=")
+                    , (code ? "(GLint [])" : "") +  formatInts(2 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniform3f:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glUniform3f" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , Float.intBitsToFloat(msg.getArg1())
+                    , (code ? "/*y*/ " : "y=")
+                    , Float.intBitsToFloat(msg.getArg2())
+                    , (code ? "/*z*/ " : "z=")
+                    , Float.intBitsToFloat(msg.getArg3()));
+                break;
+            case glUniform3fv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform3fv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*v*/ " : "v=")
+                    , (code ? "(GLfloat [])" : "") +  formatFloats(3 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniform3i:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glUniform3i" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg1()
+                    , (code ? "/*y*/ " : "y=")
+                    , msg.getArg2()
+                    , (code ? "/*z*/ " : "z=")
+                    , msg.getArg3());
+                break;
+            case glUniform3iv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform3iv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*v*/ " : "v=")
+                    , (code ? "(GLint [])" : "") +  formatInts(3 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniform4f:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glUniform4f" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , Float.intBitsToFloat(msg.getArg1())
+                    , (code ? "/*y*/ " : "y=")
+                    , Float.intBitsToFloat(msg.getArg2())
+                    , (code ? "/*z*/ " : "z=")
+                    , Float.intBitsToFloat(msg.getArg3())
+                    , (code ? "/*w*/ " : "w=")
+                    , Float.intBitsToFloat(msg.getArg4()));
+                break;
+            case glUniform4fv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform4fv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*v*/ " : "v=")
+                    , (code ? "(GLfloat [])" : "") +  formatFloats(4 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniform4i:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glUniform4i" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg1()
+                    , (code ? "/*y*/ " : "y=")
+                    , msg.getArg2()
+                    , (code ? "/*z*/ " : "z=")
+                    , msg.getArg3()
+                    , (code ? "/*w*/ " : "w=")
+                    , msg.getArg4());
+                break;
+            case glUniform4iv:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glUniform4iv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*v*/ " : "v=")
+                    , (code ? "(GLint [])" : "") +  formatInts(4 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniformMatrix2fv:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glUniformMatrix2fv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*transpose*/ " : "transpose=")
+                    , msg.getArg2()
+                    , (code ? "/*value*/ " : "value=")
+                    , (code ? "(GLfloat [])" : "") + formatMatrix(2, 4 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniformMatrix3fv:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glUniformMatrix3fv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*transpose*/ " : "transpose=")
+                    , msg.getArg2()
+                    , (code ? "/*value*/ " : "value=")
+                    , (code ? "(GLfloat [])" : "") + formatMatrix(3, 9 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUniformMatrix4fv:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glUniformMatrix4fv" : "void")
+                    , (code ? "/*location*/ " : "location=")
+                    , msg.getArg0()
+                    , (code ? "/*count*/ " : "count=")
+                    , msg.getArg1()
+                    , (code ? "/*transpose*/ " : "transpose=")
+                    , msg.getArg2()
+                    , (code ? "/*value*/ " : "value=")
+                    , (code ? "(GLfloat [])" : "") + formatMatrix(4, 16 * msg.getArg1(), msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glUseProgram:
+                str = String.format("%s(%s%s)",
+                    (code ? "glUseProgram" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0());
+                break;
+            case glValidateProgram:
+                str = String.format("%s(%s%s)",
+                    (code ? "glValidateProgram" : "void")
+                    , (code ? "/*program*/ " : "program=")
+                    , (code ? "program_" : "") + msg.getArg0());
+                break;
+            case glVertexAttrib1f:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glVertexAttrib1f" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , Float.intBitsToFloat(msg.getArg1()));
+                break;
+            case glVertexAttrib1fv:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glVertexAttrib1fv" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*values*/ " : "values=")
+                    , (code ? "(GLfloat [])" : "") + formatFloats(1, msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glVertexAttrib2f:
+                str = String.format("%s(%s%s, %s%s, %s%s)",
+                    (code ? "glVertexAttrib2f" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , Float.intBitsToFloat(msg.getArg1())
+                    , (code ? "/*y*/ " : "y=")
+                    , Float.intBitsToFloat(msg.getArg2()));
+                break;
+            case glVertexAttrib2fv:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glVertexAttrib2fv" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*values*/ " : "values=")
+                    , (code ? "(GLfloat [])" : "") + formatFloats(2, msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glVertexAttrib3f:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glVertexAttrib3f" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , Float.intBitsToFloat(msg.getArg1())
+                    , (code ? "/*y*/ " : "y=")
+                    , Float.intBitsToFloat(msg.getArg2())
+                    , (code ? "/*z*/ " : "z=")
+                    , Float.intBitsToFloat(msg.getArg3()));
+                break;
+            case glVertexAttrib3fv:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glVertexAttrib3fv" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*values*/ " : "values=")
+                    , (code ? "(GLfloat [])" : "") + formatFloats(3, msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glVertexAttrib4f:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glVertexAttrib4f" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*x*/ " : "x=")
+                    , Float.intBitsToFloat(msg.getArg1())
+                    , (code ? "/*y*/ " : "y=")
+                    , Float.intBitsToFloat(msg.getArg2())
+                    , (code ? "/*z*/ " : "z=")
+                    , Float.intBitsToFloat(msg.getArg3())
+                    , (code ? "/*w*/ " : "w=")
+                    , Float.intBitsToFloat(msg.getArg4()));
+                break;
+            case glVertexAttrib4fv:
+                str = String.format("%s(%s%s, %s%s)",
+                    (code ? "glVertexAttrib4fv" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*values*/ " : "values=")
+                    , (code ? "(GLfloat [])" : "") + formatFloats(4, msg.getData().asReadOnlyByteBuffer()));
+                break;
+            case glVertexAttribPointer:
+                // FIXME: this function uses pointers, debugger may send data in msg.data
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glVertexAttribPointer" : "void")
+                    , (code ? "/*indx*/ " : "indx=")
+                    , msg.getArg0()
+                    , (code ? "/*size*/ " : "size=")
+                    , msg.getArg1()
+                    , (code ? "/*type*/ " : "type=")
+                    , GLEnum.valueOf(msg.getArg2())
+                    , (code ? "/*normalized*/ " : "normalized=")
+                    , msg.getArg3()
+                    , (code ? "/*stride*/ " : "stride=")
+                    , msg.getArg4()
+                    , (code ? "/*ptr*/ " : "ptr=")
+                    , (code ? "arg5" : "0x" + Integer.toHexString(msg.getArg5())));
+                break;
+            case glViewport:
+                str = String.format("%s(%s%s, %s%s, %s%s, %s%s)",
+                    (code ? "glViewport" : "void")
+                    , (code ? "/*x*/ " : "x=")
+                    , msg.getArg0()
+                    , (code ? "/*y*/ " : "y=")
+                    , msg.getArg1()
+                    , (code ? "/*width*/ " : "width=")
+                    , msg.getArg2()
+                    , (code ? "/*height*/ " : "height=")
+                    , msg.getArg3());
+                break;
+            default:
+                str = msg.toString();
+        }
+        return str;
+    }
+}
\ No newline at end of file
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParser.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParser.java
new file mode 100644
index 0000000..8536728
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParser.java
@@ -0,0 +1,747 @@
+/*
+ ** Copyright 2011, 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.
+ */
+
+// auto generated by generate_MessageParser_java.py,
+//  which also prints skeleton code for MessageParserEx.java
+
+package com.android.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.google.protobuf.ByteString;
+
+import java.nio.ByteBuffer;
+
+public abstract class MessageParser {
+
+    String args;
+
+    String[] getList()
+    {
+        String arg = args;
+        args = args.substring(args.lastIndexOf('}') + 1);
+        final int comma = args.indexOf(',');
+        if (comma >= 0)
+            args = args.substring(comma + 1).trim();
+        else
+            args = null;
+
+        final int comment = arg.indexOf('=');
+        if (comment >= 0)
+            arg = arg.substring(comment + 1);
+        arg = arg.trim();
+        assert arg.charAt(0) == '{';
+        arg = arg.substring(1, arg.lastIndexOf('}')).trim();
+        return arg.split("\\s*,\\s*");
+    }
+
+    ByteString parseFloats(int count) {
+        ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
+        String [] arg = getList();
+        for (int i = 0; i < count; i++)
+            buffer.putFloat(Float.parseFloat(arg[i].trim()));
+        buffer.rewind();
+        return ByteString.copyFrom(buffer);
+    }
+
+    ByteString parseInts(int count) {
+        ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
+        String [] arg = getList();
+        for (int i = 0; i < count; i++)
+            buffer.putInt(Integer.parseInt(arg[i].trim()));
+        buffer.rewind();
+        return ByteString.copyFrom(buffer);
+    }
+
+    ByteString parseUInts(int count) {
+        ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
+        String [] arg = getList();
+        for (int i = 0; i < count; i++)
+            buffer.putInt((int)(Long.parseLong(arg[i].trim()) & 0xffffffff));
+        buffer.rewind();
+        return ByteString.copyFrom(buffer);
+    }
+
+    ByteString parseMatrix(int columns, int count) {
+        return parseFloats(columns * columns * count);
+    }
+
+    ByteString parseString() {
+        // TODO: escape sequence and proper string literal
+        String arg = args.substring(args.indexOf('"') + 1, args.lastIndexOf('"'));
+        args = args.substring(args.lastIndexOf('"'));
+        int comma = args.indexOf(',');
+        if (comma >= 0)
+            args = args.substring(comma + 1).trim();
+        else
+            args = null;
+        return ByteString.copyFromUtf8(arg);
+    }
+
+    String getArgument()
+    {
+        final int comma = args.indexOf(',');
+        String arg = null;
+        if (comma >= 0)
+        {
+            arg = args.substring(0, comma);
+            args = args.substring(comma + 1);
+        }
+        else
+        {
+            arg = args;
+            args = null;
+        }
+        final int comment = arg.indexOf('=');
+        if (comment >= 0)
+            arg = arg.substring(comment + 1);
+        return arg.trim();
+    }
+
+    int parseArgument()
+    {
+        String arg = getArgument();
+        if (arg.startsWith("GL_"))
+            return GLEnum.valueOf(arg).value;
+        else if (arg.toLowerCase().startsWith("0x"))
+            return Integer.parseInt(arg.substring(2), 16);
+        else
+            return Integer.parseInt(arg);
+    }
+
+    int parseFloat()
+    {
+        String arg = getArgument();
+        return Float.floatToRawIntBits(Float.parseFloat(arg));
+    }
+
+    public void parse(final Message.Builder builder, String string) {
+        int lparen = string.indexOf("("), rparen = string.lastIndexOf(")");
+        String s = string.substring(0, lparen).trim();
+        args = string.substring(lparen + 1, rparen);
+        String[] t = s.split(" ");
+        Function function = Function.valueOf(t[t.length - 1]);
+        builder.setFunction(function);
+        switch (function) {
+            case glActiveTexture:
+                builder.setArg0(parseArgument()); // GLenum texture
+                break;
+            case glAttachShader:
+                builder.setArg0(parseArgument()); // GLuint program
+                builder.setArg1(parseArgument()); // GLuint shader
+                break;
+            case glBindAttribLocation:
+                builder.setArg0(parseArgument()); // GLuint program
+                builder.setArg1(parseArgument()); // GLuint index
+                builder.setData(parseString()); // GLchar name
+                break;
+            case glBindBuffer:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLuint buffer
+                break;
+            case glBindFramebuffer:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLuint framebuffer
+                break;
+            case glBindRenderbuffer:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLuint renderbuffer
+                break;
+            case glBindTexture:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLuint texture
+                break;
+            case glBlendColor:
+                builder.setArg0(parseFloat()); // GLclampf red
+                builder.setArg1(parseFloat()); // GLclampf green
+                builder.setArg2(parseFloat()); // GLclampf blue
+                builder.setArg3(parseFloat()); // GLclampf alpha
+                break;
+            case glBlendEquation:
+                builder.setArg0(parseArgument()); // GLenum mode
+                break;
+            case glBlendEquationSeparate:
+                builder.setArg0(parseArgument()); // GLenum modeRGB
+                builder.setArg1(parseArgument()); // GLenum modeAlpha
+                break;
+            case glBlendFunc:
+                builder.setArg0(parseArgument()); // GLenum sfactor
+                builder.setArg1(parseArgument()); // GLenum dfactor
+                break;
+            case glBlendFuncSeparate:
+                builder.setArg0(parseArgument()); // GLenum srcRGB
+                builder.setArg1(parseArgument()); // GLenum dstRGB
+                builder.setArg2(parseArgument()); // GLenum srcAlpha
+                builder.setArg3(parseArgument()); // GLenum dstAlpha
+                break;
+            case glBufferData:
+                parse_glBufferData(builder);
+                break;
+            case glBufferSubData:
+                parse_glBufferSubData(builder);
+                break;
+            case glCheckFramebufferStatus:
+                builder.setArg0(parseArgument()); // GLenum target
+                break;
+            case glClear:
+                builder.setArg0(parseArgument()); // GLbitfield mask
+                break;
+            case glClearColor:
+                builder.setArg0(parseFloat()); // GLclampf red
+                builder.setArg1(parseFloat()); // GLclampf green
+                builder.setArg2(parseFloat()); // GLclampf blue
+                builder.setArg3(parseFloat()); // GLclampf alpha
+                break;
+            case glClearDepthf:
+                builder.setArg0(parseFloat()); // GLclampf depth
+                break;
+            case glClearStencil:
+                builder.setArg0(parseArgument()); // GLint s
+                break;
+            case glColorMask:
+                builder.setArg0(parseArgument()); // GLboolean red
+                builder.setArg1(parseArgument()); // GLboolean green
+                builder.setArg2(parseArgument()); // GLboolean blue
+                builder.setArg3(parseArgument()); // GLboolean alpha
+                break;
+            case glCompileShader:
+                builder.setArg0(parseArgument()); // GLuint shader
+                break;
+            case glCompressedTexImage2D:
+                parse_glCompressedTexImage2D(builder);
+                break;
+            case glCompressedTexSubImage2D:
+                parse_glCompressedTexSubImage2D(builder);
+                break;
+            case glCopyTexImage2D:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLint level
+                builder.setArg2(parseArgument()); // GLenum internalformat
+                builder.setArg3(parseArgument()); // GLint x
+                builder.setArg4(parseArgument()); // GLint y
+                builder.setArg5(parseArgument()); // GLsizei width
+                builder.setArg6(parseArgument()); // GLsizei height
+                builder.setArg7(parseArgument()); // GLint border
+                break;
+            case glCopyTexSubImage2D:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLint level
+                builder.setArg2(parseArgument()); // GLint xoffset
+                builder.setArg3(parseArgument()); // GLint yoffset
+                builder.setArg4(parseArgument()); // GLint x
+                builder.setArg5(parseArgument()); // GLint y
+                builder.setArg6(parseArgument()); // GLsizei width
+                builder.setArg7(parseArgument()); // GLsizei height
+                break;
+            case glCreateProgram:
+                break;
+            case glCreateShader:
+                builder.setArg0(parseArgument()); // GLenum type
+                break;
+            case glCullFace:
+                builder.setArg0(parseArgument()); // GLenum mode
+                break;
+            case glDeleteBuffers:
+                builder.setArg0(parseArgument()); // GLsizei n
+                builder.setData(parseUInts(1 * builder.getArg0())); // GLuint buffers
+                break;
+            case glDeleteFramebuffers:
+                builder.setArg0(parseArgument()); // GLsizei n
+                builder.setData(parseUInts(1 * builder.getArg0())); // GLuint framebuffers
+                break;
+            case glDeleteProgram:
+                builder.setArg0(parseArgument()); // GLuint program
+                break;
+            case glDeleteRenderbuffers:
+                builder.setArg0(parseArgument()); // GLsizei n
+                builder.setData(parseUInts(1 * builder.getArg0())); // GLuint renderbuffers
+                break;
+            case glDeleteShader:
+                builder.setArg0(parseArgument()); // GLuint shader
+                break;
+            case glDeleteTextures:
+                builder.setArg0(parseArgument()); // GLsizei n
+                builder.setData(parseUInts(1 * builder.getArg0())); // GLuint textures
+                break;
+            case glDepthFunc:
+                builder.setArg0(parseArgument()); // GLenum func
+                break;
+            case glDepthMask:
+                builder.setArg0(parseArgument()); // GLboolean flag
+                break;
+            case glDepthRangef:
+                builder.setArg0(parseFloat()); // GLclampf zNear
+                builder.setArg1(parseFloat()); // GLclampf zFar
+                break;
+            case glDetachShader:
+                builder.setArg0(parseArgument()); // GLuint program
+                builder.setArg1(parseArgument()); // GLuint shader
+                break;
+            case glDisable:
+                builder.setArg0(parseArgument()); // GLenum cap
+                break;
+            case glDisableVertexAttribArray:
+                builder.setArg0(parseArgument()); // GLuint index
+                break;
+            case glDrawArrays:
+                builder.setArg0(parseArgument()); // GLenum mode
+                builder.setArg1(parseArgument()); // GLint first
+                builder.setArg2(parseArgument()); // GLsizei count
+                break;
+            case glDrawElements:
+                parse_glDrawElements(builder);
+                break;
+            case glEnable:
+                builder.setArg0(parseArgument()); // GLenum cap
+                break;
+            case glEnableVertexAttribArray:
+                builder.setArg0(parseArgument()); // GLuint index
+                break;
+            case glFinish:
+                break;
+            case glFlush:
+                break;
+            case glFramebufferRenderbuffer:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLenum attachment
+                builder.setArg2(parseArgument()); // GLenum renderbuffertarget
+                builder.setArg3(parseArgument()); // GLuint renderbuffer
+                break;
+            case glFramebufferTexture2D:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLenum attachment
+                builder.setArg2(parseArgument()); // GLenum textarget
+                builder.setArg3(parseArgument()); // GLuint texture
+                builder.setArg4(parseArgument()); // GLint level
+                break;
+            case glFrontFace:
+                builder.setArg0(parseArgument()); // GLenum mode
+                break;
+            case glGenBuffers:
+                builder.setArg0(parseArgument()); // GLsizei n
+                builder.setData(parseUInts(1 * builder.getArg0())); // GLuint buffers
+                break;
+            case glGenerateMipmap:
+                builder.setArg0(parseArgument()); // GLenum target
+                break;
+            case glGenFramebuffers:
+                builder.setArg0(parseArgument()); // GLsizei n
+                builder.setData(parseUInts(1 * builder.getArg0())); // GLuint framebuffers
+                break;
+            case glGenRenderbuffers:
+                builder.setArg0(parseArgument()); // GLsizei n
+                builder.setData(parseUInts(1 * builder.getArg0())); // GLuint renderbuffers
+                break;
+            case glGenTextures:
+                builder.setArg0(parseArgument()); // GLsizei n
+                builder.setData(parseUInts(1 * builder.getArg0())); // GLuint textures
+                break;
+            case glGetActiveAttrib:
+                parse_glGetActiveAttrib(builder);
+                break;
+            case glGetActiveUniform:
+                parse_glGetActiveUniform(builder);
+                break;
+            case glGetAttachedShaders:
+                parse_glGetAttachedShaders(builder);
+                break;
+            case glGetAttribLocation:
+                builder.setArg0(parseArgument()); // GLuint program
+                builder.setData(parseString()); // GLchar name
+                break;
+            case glGetBooleanv:
+                parse_glGetBooleanv(builder);
+                break;
+            case glGetBufferParameteriv:
+                parse_glGetBufferParameteriv(builder);
+                break;
+            case glGetError:
+                break;
+            case glGetFloatv:
+                parse_glGetFloatv(builder);
+                break;
+            case glGetFramebufferAttachmentParameteriv:
+                parse_glGetFramebufferAttachmentParameteriv(builder);
+                break;
+            case glGetIntegerv:
+                parse_glGetIntegerv(builder);
+                break;
+            case glGetProgramiv:
+                builder.setArg0(parseArgument()); // GLuint program
+                builder.setArg1(parseArgument()); // GLenum pname
+                builder.setData(parseInts(1)); // GLint params
+                break;
+            case glGetProgramInfoLog:
+                parse_glGetProgramInfoLog(builder);
+                break;
+            case glGetRenderbufferParameteriv:
+                parse_glGetRenderbufferParameteriv(builder);
+                break;
+            case glGetShaderiv:
+                builder.setArg0(parseArgument()); // GLuint shader
+                builder.setArg1(parseArgument()); // GLenum pname
+                builder.setData(parseInts(1)); // GLint params
+                break;
+            case glGetShaderInfoLog:
+                parse_glGetShaderInfoLog(builder);
+                break;
+            case glGetShaderPrecisionFormat:
+                parse_glGetShaderPrecisionFormat(builder);
+                break;
+            case glGetShaderSource:
+                parse_glGetShaderSource(builder);
+                break;
+            case glGetString:
+                builder.setArg0(parseArgument()); // GLenum name
+                break;
+            case glGetTexParameterfv:
+                parse_glGetTexParameterfv(builder);
+                break;
+            case glGetTexParameteriv:
+                parse_glGetTexParameteriv(builder);
+                break;
+            case glGetUniformfv:
+                parse_glGetUniformfv(builder);
+                break;
+            case glGetUniformiv:
+                parse_glGetUniformiv(builder);
+                break;
+            case glGetUniformLocation:
+                builder.setArg0(parseArgument()); // GLuint program
+                builder.setData(parseString()); // GLchar name
+                break;
+            case glGetVertexAttribfv:
+                parse_glGetVertexAttribfv(builder);
+                break;
+            case glGetVertexAttribiv:
+                parse_glGetVertexAttribiv(builder);
+                break;
+            case glGetVertexAttribPointerv:
+                parse_glGetVertexAttribPointerv(builder);
+                break;
+            case glHint:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLenum mode
+                break;
+            case glIsBuffer:
+                builder.setArg0(parseArgument()); // GLuint buffer
+                break;
+            case glIsEnabled:
+                builder.setArg0(parseArgument()); // GLenum cap
+                break;
+            case glIsFramebuffer:
+                builder.setArg0(parseArgument()); // GLuint framebuffer
+                break;
+            case glIsProgram:
+                builder.setArg0(parseArgument()); // GLuint program
+                break;
+            case glIsRenderbuffer:
+                builder.setArg0(parseArgument()); // GLuint renderbuffer
+                break;
+            case glIsShader:
+                builder.setArg0(parseArgument()); // GLuint shader
+                break;
+            case glIsTexture:
+                builder.setArg0(parseArgument()); // GLuint texture
+                break;
+            case glLineWidth:
+                builder.setArg0(parseFloat()); // GLfloat width
+                break;
+            case glLinkProgram:
+                builder.setArg0(parseArgument()); // GLuint program
+                break;
+            case glPixelStorei:
+                builder.setArg0(parseArgument()); // GLenum pname
+                builder.setArg1(parseArgument()); // GLint param
+                break;
+            case glPolygonOffset:
+                builder.setArg0(parseFloat()); // GLfloat factor
+                builder.setArg1(parseFloat()); // GLfloat units
+                break;
+            case glReadPixels:
+                parse_glReadPixels(builder);
+                break;
+            case glReleaseShaderCompiler:
+                break;
+            case glRenderbufferStorage:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLenum internalformat
+                builder.setArg2(parseArgument()); // GLsizei width
+                builder.setArg3(parseArgument()); // GLsizei height
+                break;
+            case glSampleCoverage:
+                builder.setArg0(parseFloat()); // GLclampf value
+                builder.setArg1(parseArgument()); // GLboolean invert
+                break;
+            case glScissor:
+                builder.setArg0(parseArgument()); // GLint x
+                builder.setArg1(parseArgument()); // GLint y
+                builder.setArg2(parseArgument()); // GLsizei width
+                builder.setArg3(parseArgument()); // GLsizei height
+                break;
+            case glShaderBinary:
+                parse_glShaderBinary(builder);
+                break;
+            case glShaderSource:
+                parse_glShaderSource(builder);
+                break;
+            case glStencilFunc:
+                builder.setArg0(parseArgument()); // GLenum func
+                builder.setArg1(parseArgument()); // GLint ref
+                builder.setArg2(parseArgument()); // GLuint mask
+                break;
+            case glStencilFuncSeparate:
+                builder.setArg0(parseArgument()); // GLenum face
+                builder.setArg1(parseArgument()); // GLenum func
+                builder.setArg2(parseArgument()); // GLint ref
+                builder.setArg3(parseArgument()); // GLuint mask
+                break;
+            case glStencilMask:
+                builder.setArg0(parseArgument()); // GLuint mask
+                break;
+            case glStencilMaskSeparate:
+                builder.setArg0(parseArgument()); // GLenum face
+                builder.setArg1(parseArgument()); // GLuint mask
+                break;
+            case glStencilOp:
+                builder.setArg0(parseArgument()); // GLenum fail
+                builder.setArg1(parseArgument()); // GLenum zfail
+                builder.setArg2(parseArgument()); // GLenum zpass
+                break;
+            case glStencilOpSeparate:
+                builder.setArg0(parseArgument()); // GLenum face
+                builder.setArg1(parseArgument()); // GLenum fail
+                builder.setArg2(parseArgument()); // GLenum zfail
+                builder.setArg3(parseArgument()); // GLenum zpass
+                break;
+            case glTexImage2D:
+                parse_glTexImage2D(builder);
+                break;
+            case glTexParameterf:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLenum pname
+                builder.setArg2(parseFloat()); // GLfloat param
+                break;
+            case glTexParameterfv:
+                parse_glTexParameterfv(builder);
+                break;
+            case glTexParameteri:
+                builder.setArg0(parseArgument()); // GLenum target
+                builder.setArg1(parseArgument()); // GLenum pname
+                builder.setArg2(parseArgument()); // GLint param
+                break;
+            case glTexParameteriv:
+                parse_glTexParameteriv(builder);
+                break;
+            case glTexSubImage2D:
+                parse_glTexSubImage2D(builder);
+                break;
+            case glUniform1f:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseFloat()); // GLfloat x
+                break;
+            case glUniform1fv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setData(parseFloats(1 * builder.getArg1())); // GLfloat v
+                break;
+            case glUniform1i:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLint x
+                break;
+            case glUniform1iv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setData(parseInts(1 * builder.getArg1())); // GLint v
+                break;
+            case glUniform2f:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseFloat()); // GLfloat x
+                builder.setArg2(parseFloat()); // GLfloat y
+                break;
+            case glUniform2fv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setData(parseFloats(2 * builder.getArg1())); // GLfloat v
+                break;
+            case glUniform2i:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLint x
+                builder.setArg2(parseArgument()); // GLint y
+                break;
+            case glUniform2iv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setData(parseInts(2 * builder.getArg1())); // GLint v
+                break;
+            case glUniform3f:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseFloat()); // GLfloat x
+                builder.setArg2(parseFloat()); // GLfloat y
+                builder.setArg3(parseFloat()); // GLfloat z
+                break;
+            case glUniform3fv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setData(parseFloats(3 * builder.getArg1())); // GLfloat v
+                break;
+            case glUniform3i:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLint x
+                builder.setArg2(parseArgument()); // GLint y
+                builder.setArg3(parseArgument()); // GLint z
+                break;
+            case glUniform3iv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setData(parseInts(3 * builder.getArg1())); // GLint v
+                break;
+            case glUniform4f:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseFloat()); // GLfloat x
+                builder.setArg2(parseFloat()); // GLfloat y
+                builder.setArg3(parseFloat()); // GLfloat z
+                builder.setArg4(parseFloat()); // GLfloat w
+                break;
+            case glUniform4fv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setData(parseFloats(4 * builder.getArg1())); // GLfloat v
+                break;
+            case glUniform4i:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLint x
+                builder.setArg2(parseArgument()); // GLint y
+                builder.setArg3(parseArgument()); // GLint z
+                builder.setArg4(parseArgument()); // GLint w
+                break;
+            case glUniform4iv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setData(parseInts(4 * builder.getArg1())); // GLint v
+                break;
+            case glUniformMatrix2fv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setArg2(parseArgument()); // GLboolean transpose
+                builder.setData(parseMatrix(2, builder.getArg1())); // GLfloat value
+                break;
+            case glUniformMatrix3fv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setArg2(parseArgument()); // GLboolean transpose
+                builder.setData(parseMatrix(3, builder.getArg1())); // GLfloat value
+                break;
+            case glUniformMatrix4fv:
+                builder.setArg0(parseArgument()); // GLint location
+                builder.setArg1(parseArgument()); // GLsizei count
+                builder.setArg2(parseArgument()); // GLboolean transpose
+                builder.setData(parseMatrix(4, builder.getArg1())); // GLfloat value
+                break;
+            case glUseProgram:
+                builder.setArg0(parseArgument()); // GLuint program
+                break;
+            case glValidateProgram:
+                builder.setArg0(parseArgument()); // GLuint program
+                break;
+            case glVertexAttrib1f:
+                builder.setArg0(parseArgument()); // GLuint indx
+                builder.setArg1(parseFloat()); // GLfloat x
+                break;
+            case glVertexAttrib1fv:
+                builder.setArg0(parseArgument()); // GLuint indx
+                builder.setData(parseFloats(1)); // GLfloat values
+                break;
+            case glVertexAttrib2f:
+                builder.setArg0(parseArgument()); // GLuint indx
+                builder.setArg1(parseFloat()); // GLfloat x
+                builder.setArg2(parseFloat()); // GLfloat y
+                break;
+            case glVertexAttrib2fv:
+                builder.setArg0(parseArgument()); // GLuint indx
+                builder.setData(parseFloats(2)); // GLfloat values
+                break;
+            case glVertexAttrib3f:
+                builder.setArg0(parseArgument()); // GLuint indx
+                builder.setArg1(parseFloat()); // GLfloat x
+                builder.setArg2(parseFloat()); // GLfloat y
+                builder.setArg3(parseFloat()); // GLfloat z
+                break;
+            case glVertexAttrib3fv:
+                builder.setArg0(parseArgument()); // GLuint indx
+                builder.setData(parseFloats(3)); // GLfloat values
+                break;
+            case glVertexAttrib4f:
+                builder.setArg0(parseArgument()); // GLuint indx
+                builder.setArg1(parseFloat()); // GLfloat x
+                builder.setArg2(parseFloat()); // GLfloat y
+                builder.setArg3(parseFloat()); // GLfloat z
+                builder.setArg4(parseFloat()); // GLfloat w
+                break;
+            case glVertexAttrib4fv:
+                builder.setArg0(parseArgument()); // GLuint indx
+                builder.setData(parseFloats(4)); // GLfloat values
+                break;
+            case glVertexAttribPointer:
+                parse_glVertexAttribPointer(builder);
+                break;
+            case glViewport:
+                builder.setArg0(parseArgument()); // GLint x
+                builder.setArg1(parseArgument()); // GLint y
+                builder.setArg2(parseArgument()); // GLsizei width
+                builder.setArg3(parseArgument()); // GLsizei height
+                break;
+            default:
+                assert false;
+        }
+    }
+    abstract void parse_glBufferData(Message.Builder builder);
+    abstract void parse_glBufferSubData(Message.Builder builder);
+    abstract void parse_glCompressedTexImage2D(Message.Builder builder);
+    abstract void parse_glCompressedTexSubImage2D(Message.Builder builder);
+    abstract void parse_glDrawElements(Message.Builder builder);
+    abstract void parse_glGetActiveAttrib(Message.Builder builder);
+    abstract void parse_glGetActiveUniform(Message.Builder builder);
+    abstract void parse_glGetAttachedShaders(Message.Builder builder);
+    abstract void parse_glGetBooleanv(Message.Builder builder);
+    abstract void parse_glGetBufferParameteriv(Message.Builder builder);
+    abstract void parse_glGetFloatv(Message.Builder builder);
+    abstract void parse_glGetFramebufferAttachmentParameteriv(Message.Builder builder);
+    abstract void parse_glGetIntegerv(Message.Builder builder);
+    abstract void parse_glGetProgramInfoLog(Message.Builder builder);
+    abstract void parse_glGetRenderbufferParameteriv(Message.Builder builder);
+    abstract void parse_glGetShaderInfoLog(Message.Builder builder);
+    abstract void parse_glGetShaderPrecisionFormat(Message.Builder builder);
+    abstract void parse_glGetShaderSource(Message.Builder builder);
+    abstract void parse_glGetTexParameterfv(Message.Builder builder);
+    abstract void parse_glGetTexParameteriv(Message.Builder builder);
+    abstract void parse_glGetUniformfv(Message.Builder builder);
+    abstract void parse_glGetUniformiv(Message.Builder builder);
+    abstract void parse_glGetVertexAttribfv(Message.Builder builder);
+    abstract void parse_glGetVertexAttribiv(Message.Builder builder);
+    abstract void parse_glGetVertexAttribPointerv(Message.Builder builder);
+    abstract void parse_glReadPixels(Message.Builder builder);
+    abstract void parse_glShaderBinary(Message.Builder builder);
+    abstract void parse_glShaderSource(Message.Builder builder);
+    abstract void parse_glTexImage2D(Message.Builder builder);
+    abstract void parse_glTexParameterfv(Message.Builder builder);
+    abstract void parse_glTexParameteriv(Message.Builder builder);
+    abstract void parse_glTexSubImage2D(Message.Builder builder);
+    abstract void parse_glVertexAttribPointer(Message.Builder builder);
+}
\ No newline at end of file
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParserEx.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParserEx.java
new file mode 100644
index 0000000..5099146
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParserEx.java
@@ -0,0 +1,306 @@
+/*
+ ** Copyright 2011, 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.
+ */
+
+// skeleton from stdout of generate_MessageParser_java.py
+
+package com.android.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+
+public class MessageParserEx extends MessageParser {
+
+    @Override
+    void parse_glBufferData(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLsizeiptr size
+        // TODO // GLvoid data
+        builder.setArg3(parseArgument()); // GLenum usage
+    }
+
+    @Override
+    void parse_glBufferSubData(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLintptr offset
+        builder.setArg2(parseArgument()); // GLsizeiptr size
+        // TODO // GLvoid data
+    }
+
+    @Override
+    void parse_glCompressedTexImage2D(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLint level
+        builder.setArg2(parseArgument()); // GLenum internalformat
+        builder.setArg3(parseArgument()); // GLsizei width
+        builder.setArg4(parseArgument()); // GLsizei height
+        builder.setArg5(parseArgument()); // GLint border
+        builder.setArg6(parseArgument()); // GLsizei imageSize
+        // TODO: GLvoid* data
+    }
+
+    @Override
+    void parse_glCompressedTexSubImage2D(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLint level
+        builder.setArg2(parseArgument()); // GLint xoffset
+        builder.setArg3(parseArgument()); // GLint yoffset
+        builder.setArg4(parseArgument()); // GLsizei width
+        builder.setArg5(parseArgument()); // GLsizei height
+        builder.setArg6(parseArgument()); // GLenum format
+        builder.setArg7(parseArgument()); // GLsizei imageSize
+        // TODO: GLvoid* data
+    }
+
+    @Override
+    void parse_glDrawElements(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum mode
+        builder.setArg1(parseArgument()); // GLsizei count
+        builder.setArg2(parseArgument()); // GLenum type
+        // TODO: GLvoid* indices
+    }
+
+    @Override
+    void parse_glGetActiveAttrib(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint program
+        builder.setArg1(parseArgument()); // GLuint index
+        builder.setArg2(parseArgument()); // GLsizei bufsize
+        // TODO: GLsizei* length
+        // TODO: GLint* size
+        // TODO: GLenum* type
+        builder.setData(parseString()); // GLchar name
+    }
+
+    @Override
+    void parse_glGetActiveUniform(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint program
+        builder.setArg1(parseArgument()); // GLuint index
+        builder.setArg2(parseArgument()); // GLsizei bufsize
+        // TODO: GLsizei* length
+        // TODO: GLint* size
+        // TODO: GLenum* type
+        builder.setData(parseString()); // GLchar name
+    }
+
+    @Override
+    void parse_glGetAttachedShaders(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint program
+        builder.setArg1(parseArgument()); // GLsizei maxcount
+        // TODO: GLsizei* count
+        // TODO: GLuint* shaders
+    }
+
+    @Override
+    void parse_glGetBooleanv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum pname
+        // TODO: GLboolean* params
+    }
+
+    @Override
+    void parse_glGetBufferParameteriv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLint* params
+    }
+
+    @Override
+    void parse_glGetFloatv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum pname
+        // TODO: GLfloat* params
+    }
+
+    @Override
+    void parse_glGetFramebufferAttachmentParameteriv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLenum attachment
+        builder.setArg2(parseArgument()); // GLenum pname
+        // TODO: GLint* params
+    }
+
+    @Override
+    void parse_glGetIntegerv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum pname
+        // TODO: GLint* params
+    }
+
+    @Override
+    void parse_glGetProgramInfoLog(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint program
+        builder.setArg1(parseArgument()); // GLsizei bufsize
+        // TODO: GLsizei* length
+        builder.setData(parseString()); // GLchar infolog
+    }
+
+    @Override
+    void parse_glGetRenderbufferParameteriv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLint* params
+    }
+
+    @Override
+    void parse_glGetShaderInfoLog(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint shader
+        builder.setArg1(parseArgument()); // GLsizei bufsize
+        // TODO: GLsizei* length
+        builder.setData(parseString()); // GLchar infolog
+    }
+
+    @Override
+    void parse_glGetShaderPrecisionFormat(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum shadertype
+        builder.setArg1(parseArgument()); // GLenum precisiontype
+        // TODO: GLint* range
+        // TODO: GLint* precision
+    }
+
+    @Override
+    void parse_glGetShaderSource(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint shader
+        builder.setArg1(parseArgument()); // GLsizei bufsize
+        // TODO: GLsizei* length
+        builder.setData(parseString()); // GLchar source
+    }
+
+    @Override
+    void parse_glGetTexParameterfv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLfloat* params
+    }
+
+    @Override
+    void parse_glGetTexParameteriv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLint* params
+    }
+
+    @Override
+    void parse_glGetUniformfv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint program
+        builder.setArg1(parseArgument()); // GLint location
+        // TODO: GLfloat* params
+    }
+
+    @Override
+    void parse_glGetUniformiv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint program
+        builder.setArg1(parseArgument()); // GLint location
+        // TODO: GLint* params
+    }
+
+    @Override
+    void parse_glGetVertexAttribfv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint index
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLfloat* params
+    }
+
+    @Override
+    void parse_glGetVertexAttribiv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint index
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLint* params
+    }
+
+    @Override
+    void parse_glGetVertexAttribPointerv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint index
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLvoid** pointer
+    }
+
+    @Override
+    void parse_glReadPixels(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLint x
+        builder.setArg1(parseArgument()); // GLint y
+        builder.setArg2(parseArgument()); // GLsizei width
+        builder.setArg3(parseArgument()); // GLsizei height
+        builder.setArg4(parseArgument()); // GLenum format
+        builder.setArg5(parseArgument()); // GLenum type
+        // TODO: GLvoid* pixels
+    }
+
+    @Override
+    void parse_glShaderBinary(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLsizei n
+        // TODO: GLuint* shaders
+        builder.setArg2(parseArgument()); // GLenum binaryformat
+        // TODO: GLvoid* binary
+        builder.setArg4(parseArgument()); // GLsizei length
+    }
+
+    @Override
+    void parse_glShaderSource(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint shader
+        builder.setArg1(parseArgument()); // GLsizei count
+        assert 1 == builder.getArg1();
+        builder.setData(parseString()); // GLchar** string
+        builder.setArg3(parseArgument());// not used, always 1 null terminated
+                                         // string; GLint* length
+    }
+
+    @Override
+    void parse_glTexImage2D(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLint level
+        builder.setArg2(parseArgument()); // GLint internalformat
+        builder.setArg3(parseArgument()); // GLsizei width
+        builder.setArg4(parseArgument()); // GLsizei height
+        builder.setArg5(parseArgument()); // GLint border
+        builder.setArg6(parseArgument()); // GLenum format
+        builder.setArg7(parseArgument()); // GLenum type
+        // TODO: GLvoid* pixels
+    }
+
+    @Override
+    void parse_glTexParameterfv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLfloat* params
+    }
+
+    @Override
+    void parse_glTexParameteriv(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLenum pname
+        // TODO: GLint* params
+    }
+
+    @Override
+    void parse_glTexSubImage2D(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLenum target
+        builder.setArg1(parseArgument()); // GLint level
+        builder.setArg2(parseArgument()); // GLint xoffset
+        builder.setArg3(parseArgument()); // GLint yoffset
+        builder.setArg4(parseArgument()); // GLsizei width
+        builder.setArg5(parseArgument()); // GLsizei height
+        builder.setArg6(parseArgument()); // GLenum format
+        builder.setArg7(parseArgument()); // GLenum type
+        // TODO: GLvoid* pixels
+    }
+
+    @Override
+    void parse_glVertexAttribPointer(Message.Builder builder) {
+        builder.setArg0(parseArgument()); // GLuint indx
+        builder.setArg1(parseArgument()); // GLint size
+        builder.setArg2(parseArgument()); // GLenum type
+        builder.setArg3(parseArgument()); // GLboolean normalized
+        builder.setArg4(parseArgument()); // GLsizei stride
+        // TODO: GLvoid* ptr
+    }
+
+    public final static MessageParserEx instance = new MessageParserEx();
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java
new file mode 100644
index 0000000..bdd53d1
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java
@@ -0,0 +1,173 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.google.protobuf.ByteString;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.PaletteData;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class MessageProcessor {
+    static void showError(final String message) {
+        // need to call SWT from UI thread
+        MessageDialog.openError(null, "MessageProcessor", message);
+    }
+
+    /**
+     * data layout: uint32 total decompressed length, (chunks: uint32 chunk
+     * decompressed size, uint32 chunk compressed size, chunk data)+. 0 chunk
+     * compressed size means chunk is not compressed
+     */
+    public static byte[] lzfDecompressChunks(final ByteString data) {
+        ByteBuffer in = data.asReadOnlyByteBuffer();
+        in.order(SampleView.targetByteOrder);
+        ByteBuffer out = ByteBuffer.allocate(in.getInt());
+        byte[] inChunk = new byte[0];
+        byte[] outChunk = new byte[0];
+        while (in.remaining() > 0) {
+            int decompressed = in.getInt();
+            int compressed = in.getInt();
+            if (decompressed > outChunk.length)
+                outChunk = new byte[decompressed];
+            if (compressed == 0) {
+                in.get(outChunk, 0, decompressed);
+                out.put(outChunk, 0, decompressed);
+            } else {
+                if (compressed > inChunk.length)
+                    inChunk = new byte[compressed];
+                in.get(inChunk, 0, compressed);
+                int size = org.liblzf.CLZF
+                        .lzf_decompress(inChunk, compressed, outChunk, outChunk.length);
+                assert size == decompressed;
+                out.put(outChunk, 0, size);
+            }
+        }
+        assert !out.hasRemaining();
+        return out.array();
+    }
+
+    /** same data layout as LZFDecompressChunks */
+    public static byte[] lzfCompressChunks(final byte[] in, final int inSize) {
+        byte[] chunk = new byte[256 * 1024]; // chunk size is arbitrary
+        final ByteBuffer out = ByteBuffer.allocate(4 + (inSize + chunk.length - 1)
+                / chunk.length * (chunk.length + 4 * 2));
+        out.order(SampleView.targetByteOrder);
+        out.putInt(inSize);
+        for (int i = 0; i < inSize; i += chunk.length) {
+            int chunkIn = chunk.length;
+            if (i + chunkIn > inSize)
+                chunkIn = inSize - i;
+            final byte[] inChunk = java.util.Arrays.copyOfRange(in, i, i + chunkIn);
+            final int chunkOut = org.liblzf.CLZF
+                    .lzf_compress(inChunk, chunkIn, chunk, chunk.length);
+            out.putInt(chunkIn);
+            out.putInt(chunkOut);
+            if (chunkOut == 0) // compressed bigger than chunk (uncompressed)
+                out.put(inChunk);
+            else
+                out.put(chunk, 0, chunkOut);
+        }
+        return Arrays.copyOf(out.array(), out.position());
+    }
+
+    /**
+     * returns new ref, which is also the decoded image; ref could be bigger
+     * than pixels, in which case the first pixels.length bytes form the image
+     */
+    public static byte[] decodeReferencedImage(byte[] ref, byte[] pixels) {
+        if (ref.length < pixels.length)
+            ref = new byte[pixels.length];
+        for (int i = 0; i < pixels.length; i++)
+            ref[i] ^= pixels[i];
+        for (int i = pixels.length; i < ref.length; i++)
+            ref[i] = 0; // clear unused ref to maintain consistency
+        return ref;
+    }
+
+    public static ImageData receiveImage(int width, int height, int format,
+            int type, final ByteString data) {
+        assert width > 0 && height > 0;
+        int bpp = 0;
+        int redMask = 0, blueMask = 0, greenMask = 0;
+        switch (GLEnum.valueOf(type)) {
+            case GL_UNSIGNED_SHORT_5_6_5:
+            case GL_UNSIGNED_SHORT_4_4_4_4:
+            case GL_UNSIGNED_SHORT_5_5_5_1:
+                format = type;
+                break;
+            case GL_UNSIGNED_BYTE:
+                break;
+            default:
+                showError("unsupported texture type " + type);
+                return null;
+        }
+
+        switch (GLEnum.valueOf(format)) {
+            case GL_ALPHA:
+            case GL_LUMINANCE:
+                redMask = blueMask = greenMask = 0xff;
+                bpp = 8;
+                break;
+            case GL_LUMINANCE_ALPHA:
+                blueMask = 0xff;
+                redMask = 0xff00;
+                bpp = 16;
+                break;
+            case GL_RGB:
+                blueMask = 0xff;
+                greenMask = 0xff00;
+                redMask = 0xff0000;
+                bpp = 24;
+                break;
+            case GL_RGBA:
+                blueMask = 0xff00;
+                greenMask = 0xff0000;
+                redMask = 0xff000000;
+                bpp = 32;
+                break;
+            case GL_UNSIGNED_SHORT_5_6_5:
+                blueMask = ((1 << 5) - 1) << 0;
+                greenMask = ((1 << 6) - 1) << 5;
+                redMask = ((1 << 5) - 1) << 11;
+                bpp = 16;
+                break;
+            case GL_UNSIGNED_SHORT_4_4_4_4:
+                blueMask = ((1 << 4) - 1) << 4;
+                greenMask = ((1 << 4) - 1) << 8;
+                redMask = ((1 << 4) - 1) << 12;
+                bpp = 16;
+                break;
+            case GL_UNSIGNED_SHORT_5_5_5_1:
+                blueMask = ((1 << 5) - 1) << 1;
+                greenMask = ((1 << 5) - 1) << 6;
+                redMask = ((1 << 5) - 1) << 11;
+                bpp = 16;
+                break;
+            default:
+                showError("unsupported texture format: " + format);
+                return null;
+        }
+        byte[] pixels = lzfDecompressChunks(data);
+        assert pixels.length == width * height * (bpp / 8);
+        PaletteData palette = new PaletteData(redMask, greenMask, blueMask);
+        return new ImageData(width, height, bpp, palette, 1, pixels);
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java
new file mode 100644
index 0000000..c633d06
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java
@@ -0,0 +1,322 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+import com.android.sdklib.util.SparseArray;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+abstract interface ProcessMessage {
+    abstract boolean processMessage(final MessageQueue queue, final Message msg)
+            throws IOException;
+}
+
+public class MessageQueue implements Runnable {
+
+    private boolean running = false;
+    private ByteOrder byteOrder;
+    private FileInputStream file; // if null, create and use socket
+    Thread thread = null;
+    private final ProcessMessage[] processes;
+    private ArrayList<Message> complete = new ArrayList<Message>(); // synchronized
+    private ArrayList<Message> commands = new ArrayList<Message>(); // synchronized
+    private SampleView sampleView;
+
+    public MessageQueue(SampleView sampleView, final ProcessMessage[] processes) {
+        this.sampleView = sampleView;
+        this.processes = processes;
+    }
+
+    public void start(final ByteOrder byteOrder, final FileInputStream file) {
+        if (running)
+            return;
+        running = true;
+        this.byteOrder = byteOrder;
+        this.file = file;
+        thread = new Thread(this);
+        thread.start();
+    }
+
+    public void stop() {
+        if (!running)
+            return;
+        running = false;
+    }
+
+    public boolean isRunning() {
+        return running;
+    }
+
+    private void sendCommands(final int contextId) throws IOException {
+        synchronized (commands) {
+            for (int i = 0; i < commands.size(); i++) {
+                Message command = commands.get(i);
+                if (command.getContextId() == contextId || command.getContextId() == 0) {
+                    sendMessage(commands.remove(i));
+                    i--;
+                }
+            }
+        }
+    }
+
+    public void addCommand(Message command) {
+        synchronized (commands) {
+            commands.add(command);
+        }
+    }
+
+    // these should only be accessed from the network thread;
+    // access call chain starts with run()
+    private DataInputStream dis = null;
+    private DataOutputStream dos = null;
+    private SparseArray<ArrayList<Message>> incoming = new SparseArray<ArrayList<Message>>();
+
+    @Override
+    public void run() {
+        Socket socket = null;
+        if (file == null)
+            try {
+                socket = new Socket();
+                socket.connect(new java.net.InetSocketAddress("127.0.0.1", Integer
+                        .parseInt(sampleView.actionPort.getText())));
+                dis = new DataInputStream(socket.getInputStream());
+                dos = new DataOutputStream(socket.getOutputStream());
+            } catch (Exception e) {
+                running = false;
+                Error(e);
+            }
+        else
+            dis = new DataInputStream(file);
+
+        while (running) {
+            try {
+                if (file != null && file.available() == 0) {
+                    running = false;
+                    break;
+                }
+            } catch (IOException e1) {
+                e1.printStackTrace();
+                assert false;
+            }
+
+            Message msg = null;
+            if (incoming.size() > 0) { // find queued incoming
+                for (int i = 0; i < incoming.size(); i++) {
+                    final ArrayList<Message> messages = incoming.valueAt(i);
+                    if (messages.size() > 0) {
+                        msg = messages.remove(0);
+                        break;
+                    }
+                }
+            }
+            try {
+                if (null == msg) // get incoming from network
+                    msg = receiveMessage(dis);
+                processMessage(dos, msg);
+            } catch (IOException e) {
+                Error(e);
+                running = false;
+                break;
+            }
+        }
+
+        try {
+            if (socket != null)
+                socket.close();
+            else
+                file.close();
+        } catch (IOException e) {
+            Error(e);
+            running = false;
+        }
+
+    }
+
+    private void putMessage(final Message msg) {
+        ArrayList<Message> existing = incoming.get(msg.getContextId());
+        if (existing == null)
+            incoming.put(msg.getContextId(), existing = new ArrayList<Message>());
+        existing.add(msg);
+    }
+
+    Message receiveMessage(final int contextId) throws IOException {
+        Message msg = receiveMessage(dis);
+        while (msg.getContextId() != contextId) {
+            putMessage(msg);
+            msg = receiveMessage(dis);
+        }
+        return msg;
+    }
+
+    void sendMessage(final Message msg) throws IOException {
+        sendMessage(dos, msg);
+    }
+
+    // should only be used by DefaultProcessMessage
+    private SparseArray<Message> partials = new SparseArray<Message>();
+
+    Message getPartialMessage(final int contextId) {
+        return partials.get(contextId);
+    }
+
+    // used to add BeforeCall to complete if it was skipped
+    void completePartialMessage(final int contextId) {
+        final Message msg = partials.get(contextId);
+        partials.remove(contextId);
+        assert msg != null;
+        assert msg.getType() == Type.BeforeCall;
+        if (msg != null)
+            synchronized (complete) {
+                complete.add(msg);
+            }
+    }
+
+    // can be used by other message processor as default processor
+    void defaultProcessMessage(final Message msg, boolean expectResponse,
+            boolean sendResponse) throws IOException {
+        final int contextId = msg.getContextId();
+        if (msg.getType() == Type.BeforeCall) {
+            if (sendResponse) {
+                final Message.Builder builder = Message.newBuilder();
+                builder.setContextId(contextId);
+                builder.setType(Type.Response);
+                builder.setExpectResponse(expectResponse);
+                builder.setFunction(Function.CONTINUE);
+                sendMessage(dos, builder.build());
+            }
+            assert partials.indexOfKey(contextId) < 0;
+            partials.put(contextId, msg);
+        } else if (msg.getType() == Type.AfterCall) {
+            if (sendResponse) {
+                final Message.Builder builder = Message.newBuilder();
+                builder.setContextId(contextId);
+                builder.setType(Type.Response);
+                builder.setExpectResponse(expectResponse);
+                builder.setFunction(Function.SKIP);
+                sendMessage(dos, builder.build());
+            }
+            assert partials.indexOfKey(contextId) >= 0;
+            final Message before = partials.get(contextId);
+            partials.remove(contextId);
+            assert before.getFunction() == msg.getFunction();
+            final Message completed = before.toBuilder().mergeFrom(msg)
+                    .setType(Type.CompleteCall).build();
+            synchronized (complete) {
+                complete.add(completed);
+            }
+        } else if (msg.getType() == Type.CompleteCall) {
+            // this type should only be encountered on client after processing
+            assert file != null;
+            assert !msg.getExpectResponse();
+            assert !sendResponse;
+            assert partials.indexOfKey(contextId) < 0;
+            synchronized (complete) {
+                complete.add(msg);
+            }
+        } else if (msg.getType() == Type.Response && msg.getFunction() == Function.SETPROP) {
+            synchronized (complete) {
+                complete.add(msg);
+            }
+        } else
+            assert false;
+    }
+
+    public Message removeCompleteMessage(int contextId) {
+        synchronized (complete) {
+            if (complete.size() == 0)
+                return null;
+            if (0 == contextId) // get a message for any context
+                return complete.remove(0);
+            for (int i = 0; i < complete.size(); i++) {
+                Message msg = complete.get(i);
+                if (msg.getContextId() == contextId) {
+                    complete.remove(i);
+                    return msg;
+                }
+            }
+        }
+        return null;
+    }
+
+    private Message receiveMessage(final DataInputStream dis)
+            throws IOException {
+        int len = 0;
+        try {
+            len = dis.readInt();
+            if (byteOrder == ByteOrder.LITTLE_ENDIAN)
+                len = Integer.reverseBytes(len); // readInt reads BIT_ENDIAN
+        } catch (EOFException e) {
+            Error(new Exception("EOF"));
+        }
+        byte[] buffer = new byte[len];
+        int readLen = 0;
+        while (readLen < len) {
+            int read = -1;
+            try {
+                read = dis.read(buffer, readLen, len - readLen);
+            } catch (EOFException e) {
+                Error(new Exception("EOF"));
+            }
+            if (read < 0) {
+                Error(new Exception("read length = " + read));
+                return null;
+            } else
+                readLen += read;
+        }
+        Message msg = Message.parseFrom(buffer);
+        sendCommands(msg.getContextId());
+        return msg;
+    }
+
+    private void sendMessage(final DataOutputStream dos, final Message message)
+            throws IOException {
+        if (dos == null)
+            return;
+        assert message.getFunction() != Function.NEG;
+        final byte[] data = message.toByteArray();
+        if (byteOrder == ByteOrder.BIG_ENDIAN)
+            dos.writeInt(data.length);
+        else
+            dos.writeInt(Integer.reverseBytes(data.length));
+        dos.write(data);
+    }
+
+    private void processMessage(final DataOutputStream dos, final Message msg) throws IOException {
+        if (msg.getExpectResponse()) {
+            assert dos != null; // readonly source cannot expectResponse
+            for (ProcessMessage process : processes)
+                if (process.processMessage(this, msg))
+                    return;
+            defaultProcessMessage(msg, msg.getExpectResponse(), msg.getExpectResponse());
+        } else
+            defaultProcessMessage(msg, msg.getExpectResponse(), msg.getExpectResponse());
+    }
+
+    void Error(Exception e) {
+        sampleView.showError(e);
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
new file mode 100644
index 0000000..4a8cdc9
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
@@ -0,0 +1,835 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.glesv2debugger.DebuggerMessage.Message.Prop;
+import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+import com.android.sdklib.util.SparseArray;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.ListViewer;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.jface.viewers.ViewerSorter;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Slider;
+import org.eclipse.swt.widgets.TabFolder;
+import org.eclipse.swt.widgets.TabItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IWorkbenchActionConstants;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.part.ViewPart;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.nio.ByteOrder;
+
+/**
+ * This sample class demonstrates how to plug-in a new workbench view. The view
+ * shows data obtained from the model. The sample creates a dummy model on the
+ * fly, but a real implementation would connect to the model available either in
+ * this or another plug-in (e.g. the workspace). The view is connected to the
+ * model using a content provider.
+ * <p>
+ * The view uses a label provider to define how model objects should be
+ * presented in the view. Each view can present the same model objects using
+ * different labels and icons, if needed. Alternatively, a single label provider
+ * can be shared between views in order to ensure that objects of the same type
+ * are presented in the same way everywhere.
+ * <p>
+ */
+
+public class SampleView extends ViewPart implements Runnable, SelectionListener {
+    public static final ByteOrder targetByteOrder = ByteOrder.LITTLE_ENDIAN;
+
+    boolean running = false;
+    Thread thread;
+    MessageQueue messageQueue;
+    SparseArray<DebugContext> debugContexts = new SparseArray<DebugContext>();
+
+    /** The ID of the view as specified by the extension. */
+    public static final String ID = "glesv2debuggerclient.views.SampleView";
+
+    TabFolder tabFolder;
+    TabItem tabItemText, tabItemImage, tabItemBreakpointOption;
+    TabItem tabItemShaderEditor, tabContextViewer;
+    ListViewer viewer; // ListViewer / TableViewer
+    Slider frameNum; // scale max cannot overlap min, so max is array size
+    TreeViewer contextViewer;
+    BreakpointOption breakpointOption;
+    ShaderEditor shaderEditor;
+    Canvas canvas;
+    Text text;
+    Action actionConnect; // connect / disconnect
+
+    Action actionAutoScroll;
+    Action actionFilter;
+    Action actionPort;
+
+    Action actContext; // for toggling contexts
+    DebugContext current = null;
+
+    Point origin = new Point(0, 0); // for smooth scrolling canvas
+    String[] filters = null;
+
+    class ViewContentProvider extends LabelProvider implements IStructuredContentProvider,
+            ITableLabelProvider {
+        Frame frame = null;
+
+        @Override
+        public void inputChanged(Viewer v, Object oldInput, Object newInput) {
+            frame = (Frame) newInput;
+        }
+
+        @Override
+        public void dispose() {
+        }
+
+        @Override
+        public Object[] getElements(Object parent) {
+            return frame.get().toArray();
+        }
+
+        @Override
+        public String getText(Object obj) {
+            MessageData msgData = (MessageData) obj;
+            return msgData.text;
+        }
+
+        @Override
+        public Image getImage(Object obj) {
+            MessageData msgData = (MessageData) obj;
+            return msgData.getImage();
+        }
+
+        @Override
+        public String getColumnText(Object obj, int index) {
+            MessageData msgData = (MessageData) obj;
+            if (index >= msgData.columns.length)
+                return null;
+            return msgData.columns[index];
+        }
+
+        @Override
+        public Image getColumnImage(Object obj, int index) {
+            if (index > -1)
+                return null;
+            MessageData msgData = (MessageData) obj;
+            return msgData.getImage();
+        }
+    }
+
+    class NameSorter extends ViewerSorter {
+        @Override
+        public int compare(Viewer viewer, Object e1, Object e2) {
+            MessageData m1 = (MessageData) e1;
+            MessageData m2 = (MessageData) e2;
+            return (int) ((m1.msg.getTime() - m2.msg.getTime()) * 100);
+        }
+    }
+
+    class Filter extends ViewerFilter {
+        @Override
+        public boolean select(Viewer viewer, Object parentElement,
+                Object element) {
+            MessageData msgData = (MessageData) element;
+            if (null == filters)
+                return true;
+            for (int i = 0; i < filters.length; i++)
+                if (msgData.text.contains(filters[i]))
+                    return true;
+            return false;
+        }
+    }
+
+    public SampleView() {
+
+    }
+
+    public void createLeftPane(Composite parent) {
+        Composite composite = new Composite(parent, 0);
+
+        GridLayout gridLayout = new GridLayout();
+        gridLayout.numColumns = 1;
+        composite.setLayout(gridLayout);
+
+        frameNum = new Slider(composite, SWT.BORDER | SWT.HORIZONTAL);
+        frameNum.setMinimum(0);
+        frameNum.setMaximum(1);
+        frameNum.setSelection(0);
+        frameNum.addSelectionListener(this);
+
+        GridData gridData = new GridData();
+        gridData.horizontalAlignment = SWT.FILL;
+        gridData.grabExcessHorizontalSpace = true;
+        gridData.verticalAlignment = SWT.FILL;
+        frameNum.setLayoutData(gridData);
+
+        // Table table = new Table(composite, SWT.H_SCROLL | SWT.V_SCROLL |
+        // SWT.MULTI
+        // | SWT.FULL_SELECTION);
+        // TableLayout layout = new TableLayout();
+        // table.setLayout(layout);
+        // table.setLinesVisible(true);
+        // table.setHeaderVisible(true);
+        // String[] headings = {
+        // "Name", "Elapsed (ms)", "Detail"
+        // };
+        // int[] weights = {
+        // 50, 16, 60
+        // };
+        // int[] widths = {
+        // 180, 90, 200
+        // };
+        // for (int i = 0; i < headings.length; i++) {
+        // layout.addColumnData(new ColumnWeightData(weights[i], widths[i],
+        // true));
+        // TableColumn nameCol = new TableColumn(table, SWT.NONE, i);
+        // nameCol.setText(headings[i]);
+        // }
+
+        // viewer = new TableViewer(table);
+        viewer = new ListViewer(composite, SWT.DEFAULT);
+        viewer.getList().setFont(new Font(viewer.getList().getDisplay(),
+                "Courier", 10, SWT.BOLD));
+        ViewContentProvider contentProvider = new ViewContentProvider();
+        viewer.setContentProvider(contentProvider);
+        viewer.setLabelProvider(contentProvider);
+        // viewer.setSorter(new NameSorter());
+        viewer.setFilters(new ViewerFilter[] {
+                new Filter()
+        });
+
+        gridData = new GridData();
+        gridData.horizontalAlignment = SWT.FILL;
+        gridData.grabExcessHorizontalSpace = true;
+        gridData.verticalAlignment = SWT.FILL;
+        gridData.grabExcessVerticalSpace = true;
+        viewer.getControl().setLayoutData(gridData);
+    }
+
+    /**
+     * This is a callback that will allow us to create the viewer and initialize
+     * it.
+     */
+    @Override
+    public void createPartControl(Composite parent) {
+        createLeftPane(parent);
+
+        // Create the help context id for the viewer's control
+        PlatformUI.getWorkbench().getHelpSystem()
+                .setHelp(viewer.getControl(), "GLESv2DebuggerClient.viewer");
+
+        tabFolder = new TabFolder(parent, SWT.BORDER);
+
+        text = new Text(tabFolder, SWT.NO_BACKGROUND | SWT.READ_ONLY
+                | SWT.V_SCROLL | SWT.H_SCROLL);
+
+        tabItemText = new TabItem(tabFolder, SWT.NONE);
+        tabItemText.setText("Text");
+        tabItemText.setControl(text);
+
+        canvas = new Canvas(tabFolder, SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE
+                | SWT.V_SCROLL | SWT.H_SCROLL);
+        tabItemImage = new TabItem(tabFolder, SWT.NONE);
+        tabItemImage.setText("Image");
+        tabItemImage.setControl(canvas);
+
+        breakpointOption = new BreakpointOption(this, tabFolder);
+        tabItemBreakpointOption = new TabItem(tabFolder, SWT.NONE);
+        tabItemBreakpointOption.setText("Breakpoint Option");
+        tabItemBreakpointOption.setControl(breakpointOption);
+
+        shaderEditor = new ShaderEditor(this, tabFolder);
+        tabItemShaderEditor = new TabItem(tabFolder, SWT.NONE);
+        tabItemShaderEditor.setText("Shader Editor");
+        tabItemShaderEditor.setControl(shaderEditor);
+
+        contextViewer = new TreeViewer(tabFolder);
+        ContextViewProvider contextViewProvider = new ContextViewProvider(this);
+        contextViewer.addSelectionChangedListener(contextViewProvider);
+        contextViewer.setContentProvider(contextViewProvider);
+        contextViewer.setLabelProvider(contextViewProvider);
+        tabContextViewer = new TabItem(tabFolder, SWT.NONE);
+        tabContextViewer.setText("Context Viewer");
+        tabContextViewer.setControl(contextViewer.getTree());
+
+        final ScrollBar hBar = canvas.getHorizontalBar();
+        hBar.addListener(SWT.Selection, new Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                if (null == canvas.getBackgroundImage())
+                    return;
+                Image image = canvas.getBackgroundImage();
+                int hSelection = hBar.getSelection();
+                int destX = -hSelection - origin.x;
+                Rectangle rect = image.getBounds();
+                canvas.scroll(destX, 0, 0, 0, rect.width, rect.height, false);
+                origin.x = -hSelection;
+            }
+        });
+        final ScrollBar vBar = canvas.getVerticalBar();
+        vBar.addListener(SWT.Selection, new Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                if (null == canvas.getBackgroundImage())
+                    return;
+                Image image = canvas.getBackgroundImage();
+                int vSelection = vBar.getSelection();
+                int destY = -vSelection - origin.y;
+                Rectangle rect = image.getBounds();
+                canvas.scroll(0, destY, 0, 0, rect.width, rect.height, false);
+                origin.y = -vSelection;
+            }
+        });
+        canvas.addListener(SWT.Resize, new Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                if (null == canvas.getBackgroundImage())
+                    return;
+                Image image = canvas.getBackgroundImage();
+                Rectangle rect = image.getBounds();
+                Rectangle client = canvas.getClientArea();
+                hBar.setMaximum(rect.width);
+                vBar.setMaximum(rect.height);
+                hBar.setThumb(Math.min(rect.width, client.width));
+                vBar.setThumb(Math.min(rect.height, client.height));
+                int hPage = rect.width - client.width;
+                int vPage = rect.height - client.height;
+                int hSelection = hBar.getSelection();
+                int vSelection = vBar.getSelection();
+                if (hSelection >= hPage) {
+                    if (hPage <= 0)
+                        hSelection = 0;
+                    origin.x = -hSelection;
+                }
+                if (vSelection >= vPage) {
+                    if (vPage <= 0)
+                        vSelection = 0;
+                    origin.y = -vSelection;
+                }
+                canvas.redraw();
+            }
+        });
+        canvas.addListener(SWT.Paint, new Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                if (null == canvas.getBackgroundImage())
+                    return;
+                Image image = canvas.getBackgroundImage();
+                GC gc = e.gc;
+                gc.drawImage(image, origin.x, origin.y);
+                Rectangle rect = image.getBounds();
+                Rectangle client = canvas.getClientArea();
+                int marginWidth = client.width - rect.width;
+                if (marginWidth > 0) {
+                    gc.fillRectangle(rect.width, 0, marginWidth, client.height);
+                }
+                int marginHeight = client.height - rect.height;
+                if (marginHeight > 0) {
+                    gc.fillRectangle(0, rect.height, client.width, marginHeight);
+                }
+            }
+        });
+
+        hookContextMenu();
+        hookSelectionChanged();
+        contributeToActionBars();
+
+        messageQueue = new MessageQueue(this, new ProcessMessage[] {
+                breakpointOption, shaderEditor
+        });
+    }
+
+    private void hookContextMenu() {
+        MenuManager menuMgr = new MenuManager("#PopupMenu");
+        menuMgr.setRemoveAllWhenShown(true);
+        menuMgr.addMenuListener(new IMenuListener() {
+            @Override
+            public void menuAboutToShow(IMenuManager manager) {
+                SampleView.this.fillContextMenu(manager);
+            }
+        });
+        Menu menu = menuMgr.createContextMenu(viewer.getControl());
+        viewer.getControl().setMenu(menu);
+        getSite().registerContextMenu(menuMgr, viewer);
+    }
+
+    private void contributeToActionBars() {
+        IActionBars bars = getViewSite().getActionBars();
+        fillLocalPullDown(bars.getMenuManager());
+        fillLocalToolBar(bars.getToolBarManager());
+    }
+
+    private void fillLocalPullDown(IMenuManager manager) {
+        // manager.add(actionConnect);
+        // manager.add(new Separator());
+        // manager.add(actionDisconnect);
+    }
+
+    private void fillContextMenu(IMenuManager manager) {
+        // manager.add(actionConnect);
+        // manager.add(actionDisconnect);
+        // Other plug-ins can contribute there actions here
+        manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
+    }
+
+    private void fillLocalToolBar(final IToolBarManager manager) {
+        actionConnect = new Action("Connect", Action.AS_PUSH_BUTTON) {
+            @Override
+            public void run() {
+                if (!running)
+                    changeContext(null); // viewer will switch to newest context
+                connectDisconnect();
+            }
+        };
+        manager.add(actionConnect);
+
+        manager.add(new Action("Open File", Action.AS_PUSH_BUTTON)
+        {
+            @Override
+            public void run()
+            {
+                if (!running)
+                {
+                    changeContext(null); // viewer will switch to newest context
+                    openFile();
+                }
+            }
+        });
+
+        final Shell shell = this.getViewSite().getShell();
+        actionAutoScroll = new Action("Auto Scroll", Action.AS_CHECK_BOX) {
+            @Override
+            public void run() {
+            }
+        };
+        actionAutoScroll.setChecked(true);
+        manager.add(actionAutoScroll);
+
+        actionFilter = new Action("*", Action.AS_DROP_DOWN_MENU) {
+            @Override
+            public void run() {
+                org.eclipse.jface.dialogs.InputDialog dialog = new org.eclipse.jface.dialogs.InputDialog(
+                        shell, "Contains Filter",
+                        "case sensitive substring or *",
+                        actionFilter.getText(), null);
+                if (Window.OK == dialog.open()) {
+                    actionFilter.setText(dialog.getValue());
+                    manager.update(true);
+                    filters = dialog.getValue().split("\\|");
+                    if (filters.length == 1 && filters[0].equals("*"))
+                        filters = null;
+                    viewer.refresh();
+                }
+
+            }
+        };
+        manager.add(actionFilter);
+
+        manager.add(new Action("CaptureDraw", Action.AS_DROP_DOWN_MENU)
+        {
+            @Override
+            public void run()
+            {
+                int contextId = 0;
+                if (current != null)
+                    contextId = current.contextId;
+                InputDialog inputDialog = new InputDialog(shell,
+                        "Capture glDrawArrays/Elements",
+                        "Enter number of glDrawArrays/Elements to glReadPixels for "
+                                + "context 0x" + Integer.toHexString(contextId) +
+                                "\n(0x0 is any context)", "9001", null);
+                if (inputDialog.open() != Window.OK)
+                    return;
+                Message.Builder builder = Message.newBuilder();
+                builder.setContextId(contextId);
+                builder.setType(Type.Response);
+                builder.setExpectResponse(false);
+                builder.setFunction(Function.SETPROP);
+                builder.setProp(Prop.CaptureDraw);
+                builder.setArg0(Integer.parseInt(inputDialog.getValue()));
+                messageQueue.addCommand(builder.build());
+            }
+        });
+
+        manager.add(new Action("CaptureSwap", Action.AS_DROP_DOWN_MENU)
+        {
+            @Override
+            public void run()
+            {
+                int contextId = 0;
+                if (current != null)
+                    contextId = current.contextId;
+                InputDialog inputDialog = new InputDialog(shell,
+                        "Capture eglSwapBuffers",
+                        "Enter number of eglSwapBuffers to glReadPixels for "
+                                + "context 0x" + Integer.toHexString(contextId) +
+                                "\n(0x0 is any context)", "9001", null);
+                if (inputDialog.open() != Window.OK)
+                    return;
+                Message.Builder builder = Message.newBuilder();
+                builder.setContextId(contextId);
+                builder.setType(Type.Response);
+                builder.setExpectResponse(false);
+                builder.setFunction(Function.SETPROP);
+                builder.setProp(Prop.CaptureSwap);
+                builder.setArg0(Integer.parseInt(inputDialog.getValue()));
+                messageQueue.addCommand(builder.build());
+            }
+        });
+
+        manager.add(new Action("SYSTEM_TIME_THREAD", Action.AS_DROP_DOWN_MENU)
+        {
+            @Override
+            public void run()
+            {
+                final String[] timeModes = {
+                        "SYSTEM_TIME_REALTIME", "SYSTEM_TIME_MONOTONIC", "SYSTEM_TIME_PROCESS",
+                        "SYSTEM_TIME_THREAD"
+                };
+                int i = java.util.Arrays.asList(timeModes).indexOf(this.getText());
+                i = (i + 1) % timeModes.length;
+                Message.Builder builder = Message.newBuilder();
+                builder.setContextId(0); // FIXME: proper context id
+                builder.setType(Type.Response);
+                builder.setExpectResponse(false);
+                builder.setFunction(Message.Function.SETPROP);
+                builder.setProp(Prop.TimeMode);
+                builder.setArg0(i);
+                messageQueue.addCommand(builder.build());
+                this.setText(timeModes[i]);
+                manager.update(true);
+            }
+        });
+
+        actContext = new Action("Context: 0x", Action.AS_DROP_DOWN_MENU) {
+            @Override
+            public void run() {
+                if (debugContexts.size() < 2)
+                    return;
+                final String idStr = this.getText().substring(
+                                          "Context: 0x".length());
+                if (idStr.length() == 0)
+                    return;
+                final int contextId = Integer.parseInt(idStr, 16);
+                int index = debugContexts.indexOfKey(contextId);
+                index = (index + 1) % debugContexts.size();
+                changeContext(debugContexts.valueAt(index));
+            }
+        };
+        manager.add(actContext);
+
+        actionPort = new Action("5039", Action.AS_DROP_DOWN_MENU)
+        {
+            @Override
+            public void run() {
+                InputDialog dialog = new InputDialog(shell, "Port", "Debugger port",
+                        actionPort.getText(), null);
+                if (Window.OK == dialog.open()) {
+                    actionPort.setText(dialog.getValue());
+                    manager.update(true);
+                }
+            }
+        };
+        manager.add(actionPort);
+
+        manager.add(new Action("CodeGen Frame", Action.AS_PUSH_BUTTON)
+        {
+            @Override
+            public void run()
+            {
+                if (current != null)
+                {
+                    new CodeGen().codeGenFrame((Frame) viewer.getInput());
+                    // need to reload current frame
+                    viewer.setInput(current.getFrame(frameNum.getSelection()));
+                }
+            }
+        });
+
+        manager.add(new Action("CodeGen Frames", Action.AS_PUSH_BUTTON)
+        {
+            @Override
+            public void run()
+            {
+                if (current != null)
+                {
+                    new CodeGen().codeGenFrames(current, frameNum.getSelection() + 1,
+                            getSite().getShell());
+                    // need to reload current frame
+                    viewer.setInput(current.getFrame(frameNum.getSelection()));
+                }
+            }
+        });
+    }
+
+    private void openFile() {
+        FileDialog dialog = new FileDialog(getSite().getShell(), SWT.OPEN);
+        dialog.setText("Open");
+        dialog.setFilterExtensions(new String[] {
+                "*.gles2dbg"
+        });
+        String filePath = dialog.open();
+        if (filePath == null)
+            return;
+        FileInputStream file = null;
+        try {
+            file = new FileInputStream(filePath);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return;
+        }
+        running = true;
+        messageQueue.start(targetByteOrder, file);
+        thread = new Thread(this);
+        thread.start();
+        actionConnect.setText("Disconnect");
+        getViewSite().getActionBars().getToolBarManager().update(true);
+    }
+
+    private void connectDisconnect() {
+        if (!running) {
+            running = true;
+            messageQueue.start(targetByteOrder, null);
+            thread = new Thread(this);
+            thread.start();
+            actionConnect.setText("Disconnect");
+        } else {
+            running = false;
+            messageQueue.stop();
+            actionConnect.setText("Connect");
+        }
+        this.getSite().getShell().getDisplay().syncExec(new Runnable() {
+            @Override
+            public void run() {
+                getViewSite().getActionBars().getToolBarManager().update(true);
+            }
+        });
+    }
+
+    void messageDataSelected(final MessageData msgData) {
+        if (null == msgData)
+            return;
+        if (frameNum.getSelection() == frameNum.getMaximum())
+            return; // scale max cannot overlap min, so max is array size
+        final Frame frame = current.getFrame(frameNum.getSelection());
+        final Context context = frame.computeContext(msgData);
+        contextViewer.setInput(context);
+        if (msgData.getImage() != null) {
+            canvas.setBackgroundImage(msgData.getImage());
+            tabFolder.setSelection(tabItemImage);
+            canvas.redraw();
+        } else if (null != msgData.shader) {
+            text.setText(msgData.shader);
+            tabFolder.setSelection(tabItemText);
+        } else if (null != msgData.attribs) {
+            StringBuilder builder = new StringBuilder();
+            final int maxAttrib = msgData.msg.getArg7();
+            for (int i = 0; i < msgData.attribs[0].length / 4; i++) {
+                if (msgData.indices != null) {
+                    builder.append(msgData.indices[i] & 0xffff);
+                    builder.append(": ");
+                }
+                for (int j = 0; j < maxAttrib; j++) {
+                    for (int k = 0; k < 4; k++)
+                        builder.append(String.format("%.3g ", msgData.attribs[j][i * 4 + k]));
+                    if (j < maxAttrib - 1)
+                        builder.append("|| ");
+                }
+                builder.append('\n');
+            }
+            text.setText(builder.toString());
+            tabFolder.setSelection(tabItemText);
+        }
+    }
+
+    private void hookSelectionChanged() {
+        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+            @Override
+            public void selectionChanged(SelectionChangedEvent event) {
+                StructuredSelection selection = (StructuredSelection) event
+                        .getSelection();
+                if (null == selection)
+                    return;
+                MessageData msgData = (MessageData) selection.getFirstElement();
+                messageDataSelected(msgData);
+            }
+        });
+    }
+
+    public void showError(final Exception e) {
+        viewer.getControl().getDisplay().syncExec(new Runnable() {
+            @Override
+            public void run() {
+                MessageDialog.openError(viewer.getControl().getShell(),
+                        "GL ES 2.0 Debugger Client", e.getMessage());
+            }
+        });
+    }
+
+    /**
+     * Passing the focus request to the viewer's control.
+     */
+    @Override
+    public void setFocus() {
+        viewer.getControl().setFocus();
+    }
+
+    @Override
+    public void run() {
+        int newMessages = 0;
+
+        boolean shaderEditorUpdate = false;
+        while (running) {
+            final Message oriMsg = messageQueue.removeCompleteMessage(0);
+            if (oriMsg == null && !messageQueue.isRunning())
+                break;
+            if (newMessages > 60 || (newMessages > 0 && null == oriMsg)) {
+                newMessages = 0;
+                if (current != null && current.uiUpdate)
+                    getSite().getShell().getDisplay().syncExec(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (frameNum.getSelection() == current.frameCount() - 1 ||
+                                    frameNum.getSelection() == current.frameCount() - 2)
+                            {
+                                viewer.refresh(false);
+                                if (actionAutoScroll.isChecked())
+                                    viewer.getList().setSelection(
+                                            viewer.getList().getItemCount() - 1);
+                            }
+                            frameNum.setMaximum(current.frameCount());
+                        }
+                    });
+                current.uiUpdate = false;
+
+                if (shaderEditorUpdate)
+                    this.getSite().getShell().getDisplay().syncExec(new Runnable() {
+                        @Override
+                        public void run() {
+                            shaderEditor.updateUI();
+                        }
+                    });
+                shaderEditorUpdate = false;
+            }
+            if (null == oriMsg) {
+                try {
+                    Thread.sleep(1);
+                    continue;
+                } catch (InterruptedException e) {
+                    showError(e);
+                }
+            }
+            DebugContext debugContext = debugContexts.get(oriMsg.getContextId());
+            if (debugContext == null) {
+                debugContext = new DebugContext(oriMsg.getContextId());
+                debugContexts.put(oriMsg.getContextId(), debugContext);
+            }
+            debugContext.processMessage(oriMsg);
+            shaderEditorUpdate |= debugContext.currentContext.serverShader.uiUpdate;
+            debugContext.currentContext.serverShader.uiUpdate = false;
+            if (current == null && debugContext.frameCount() > 0)
+                changeContext(debugContext);
+            newMessages++;
+        }
+        if (running)
+            connectDisconnect(); // error occurred, disconnect
+    }
+
+    /** can be called from non-UI thread */
+    void changeContext(final DebugContext newContext) {
+        getSite().getShell().getDisplay().syncExec(new Runnable() {
+            @Override
+            public void run() {
+                current = newContext;
+                if (current != null)
+                {
+                    frameNum.setMaximum(current.frameCount());
+                    if (frameNum.getSelection() >= current.frameCount())
+                        if (current.frameCount() > 0)
+                            frameNum.setSelection(current.frameCount() - 1);
+                        else
+                            frameNum.setSelection(0);
+                    viewer.setInput(current.getFrame(frameNum.getSelection()));
+                    actContext.setText("Context: 0x" + Integer.toHexString(current.contextId));
+                }
+                else
+                {
+                    frameNum.setMaximum(1); // cannot overlap min
+                    frameNum.setSelection(0);
+                    viewer.setInput(null);
+                    actContext.setText("Context: 0x");
+                }
+                shaderEditor.updateUI();
+                getViewSite().getActionBars().getToolBarManager().update(true);
+            }
+        });
+    }
+
+    @Override
+    public void widgetSelected(SelectionEvent e) {
+        if (e.widget != frameNum)
+            assert false;
+        if (current == null)
+            return;
+        if (frameNum.getSelection() == current.frameCount())
+            return; // scale maximum cannot overlap minimum
+        Frame frame = current.getFrame(frameNum.getSelection());
+        viewer.setInput(frame);
+    }
+
+    @Override
+    public void widgetDefaultSelected(SelectionEvent e) {
+        widgetSelected(e);
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java b/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java
new file mode 100644
index 0000000..c125143
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java
@@ -0,0 +1,407 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ExtendedModifyEvent;
+import org.eclipse.swt.custom.ExtendedModifyListener;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.List;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+
+public class ShaderEditor extends Composite implements SelectionListener, ExtendedModifyListener,
+        ProcessMessage {
+    SampleView sampleView;
+
+    ToolBar toolbar;
+    ToolItem uploadShader, restoreShader, currentPrograms;
+    List list;
+    StyledText styledText;
+
+    GLShader current;
+
+    ArrayList<GLShader> shadersToUpload = new ArrayList<GLShader>();
+
+    ShaderEditor(SampleView sampleView, Composite parent) {
+        super(parent, 0);
+        this.sampleView = sampleView;
+
+        GridLayout gridLayout = new GridLayout();
+        gridLayout.numColumns = 1;
+        this.setLayout(gridLayout);
+
+        toolbar = new ToolBar(this, SWT.BORDER);
+
+        uploadShader = new ToolItem(toolbar, SWT.PUSH);
+        uploadShader.setText("Upload Shader");
+        uploadShader.addSelectionListener(this);
+
+        restoreShader = new ToolItem(toolbar, SWT.PUSH);
+        restoreShader.setText("Original Shader");
+        restoreShader.addSelectionListener(this);
+
+        currentPrograms = new ToolItem(toolbar, SWT.PUSH);
+        currentPrograms.setText("Current Programs: ");
+
+        list = new List(this, SWT.V_SCROLL);
+        list.setFont(new Font(parent.getDisplay(), "Courier", 10, 0));
+        list.addSelectionListener(this);
+        GridData gridData = new GridData();
+        gridData.horizontalAlignment = SWT.FILL;
+        gridData.grabExcessHorizontalSpace = true;
+        gridData.verticalAlignment = SWT.FILL;
+        gridData.grabExcessVerticalSpace = true;
+        list.setLayoutData(gridData);
+
+        styledText = new StyledText(this, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI);
+        gridData = new GridData();
+        gridData.horizontalAlignment = SWT.FILL;
+        gridData.grabExcessHorizontalSpace = true;
+        gridData.verticalAlignment = SWT.FILL;
+        gridData.grabExcessVerticalSpace = true;
+        gridData.verticalSpan = 2;
+        styledText.setLayoutData(gridData);
+        styledText.addExtendedModifyListener(this);
+    }
+
+    public void updateUI() {
+        list.removeAll();
+        String progs = "Current Programs: ";
+        for (int j = 0; j < sampleView.debugContexts.size(); j++) {
+            final Context context = sampleView.debugContexts.valueAt(j).currentContext;
+
+            if (context.serverShader.current != null) {
+                progs += context.serverShader.current.name + "(0x";
+                progs += Integer.toHexString(context.contextId) + ") ";
+            }
+            for (int i = 0; i < context.serverShader.shaders.size(); i++) {
+                GLShader shader = context.serverShader.shaders.valueAt(i);
+                StringBuilder builder = new StringBuilder();
+                builder.append(String.format("%08X", context.contextId));
+                builder.append(' ');
+                builder.append(shader.type);
+                while (builder.length() < 30)
+                    builder.append(" ");
+                builder.append(shader.name);
+                while (builder.length() < 40)
+                    builder.append(" ");
+                builder.append(" : ");
+                for (Context ctx : context.shares) {
+                    builder.append(String.format("%08X", ctx.contextId));
+                    builder.append(' ');
+                }
+                builder.append(": ");
+                for (int program : shader.programs) {
+                    builder.append(program);
+                    builder.append(" ");
+                }
+                list.add(builder.toString());
+            }
+
+        }
+
+        currentPrograms.setText(progs);
+        toolbar.redraw();
+        toolbar.pack(true);
+        toolbar.update();
+    }
+
+    void uploadShader() {
+        current.source = styledText.getText();
+
+        // optional syntax check by glsl_compiler, built from external/mesa3d
+        if (new File("./glsl_compiler").exists())
+            try {
+                File file = File.createTempFile("shader",
+                        current.type == GLEnum.GL_VERTEX_SHADER ? ".vert" : ".frag");
+                FileWriter fileWriter = new FileWriter(file, false);
+                fileWriter.write(current.source);
+                fileWriter.close();
+
+                ProcessBuilder processBuilder = new ProcessBuilder(
+                        "./glsl_compiler", "--glsl-es", file.getAbsolutePath());
+                final Process process = processBuilder.start();
+                InputStream is = process.getInputStream();
+                InputStreamReader isr = new InputStreamReader(is);
+                BufferedReader br = new BufferedReader(isr);
+                String line;
+                String infolog = "";
+
+                styledText.setLineBackground(0, styledText.getLineCount(), null);
+
+                while ((line = br.readLine()) != null) {
+                    infolog += line;
+                    if (!line.startsWith("0:"))
+                        continue;
+                    String[] details = line.split(":|\\(|\\)");
+                    final int ln = Integer.parseInt(details[1]);
+                    if (ln > 0) // usually line 0 means errors other than syntax
+                        styledText.setLineBackground(ln - 1, 1,
+                                new Color(Display.getCurrent(), 255, 230, 230));
+                }
+                file.delete();
+                if (infolog.length() > 0) {
+                    if (!MessageDialog.openConfirm(getShell(),
+                            "Shader Syntax Error, Continue?", infolog))
+                        return;
+                }
+            } catch (IOException e) {
+                sampleView.showError(e);
+            }
+
+        // add the initial command, which when read by server will set
+        // expectResponse for the message loop and go into message exchange
+        synchronized (shadersToUpload) {
+            for (GLShader shader : shadersToUpload) {
+                if (shader.context.context.contextId != current.context.context.contextId)
+                    continue;
+                MessageDialog.openWarning(this.getShell(), "Context 0x" +
+                        Integer.toHexString(current.context.context.contextId),
+                        "Previous shader upload not complete, try again");
+                return;
+            }
+            shadersToUpload.add(current);
+            final int contextId = current.context.context.contextId;
+            Message.Builder builder = getBuilder(contextId);
+            MessageParserEx.instance.parse(builder,
+                    String.format("glShaderSource(%d,1,\"%s\",0)", current.name, current.source));
+            sampleView.messageQueue.addCommand(builder.build());
+        }
+    }
+
+    Message.Builder getBuilder(int contextId) {
+        Message.Builder builder = Message.newBuilder();
+        builder.setContextId(contextId);
+        builder.setType(Type.Response);
+        builder.setExpectResponse(true);
+        return builder;
+    }
+
+    Message exchangeMessage(final int contextId, final MessageQueue queue,
+            String format, Object... args) throws IOException {
+        Message.Builder builder = getBuilder(contextId);
+        MessageParserEx.instance.parse(builder, String.format(format, args));
+        final Function function = builder.getFunction();
+        queue.sendMessage(builder.build());
+        final Message msg = queue.receiveMessage(contextId);
+        assert msg.getContextId() == contextId;
+        assert msg.getType() == Type.AfterGeneratedCall;
+        assert msg.getFunction() == function;
+        return msg;
+    }
+
+    // this is called from network thread
+    public boolean processMessage(final MessageQueue queue, final Message msg)
+            throws IOException {
+        GLShader shader = null;
+        final int contextId = msg.getContextId();
+        synchronized (shadersToUpload) {
+            if (shadersToUpload.size() == 0)
+                return false;
+            boolean matchingContext = false;
+            for (int i = 0; i < shadersToUpload.size(); i++) {
+                shader = shadersToUpload.get(i);
+                for (Context ctx : shader.context.context.shares)
+                    if (ctx.contextId == contextId) {
+                        matchingContext = true;
+                        break;
+                    }
+                if (matchingContext) {
+                    shadersToUpload.remove(i);
+                    break;
+                }
+            }
+            if (!matchingContext)
+                return false;
+        }
+
+        // glShaderSource was already sent to trigger set expectResponse
+        assert msg.getType() == Type.AfterGeneratedCall;
+        assert msg.getFunction() == Function.glShaderSource;
+
+        exchangeMessage(contextId, queue, "glCompileShader(%d)", shader.name);
+
+        // the 0, "" and {0} are dummies for the parser
+        Message rcv = exchangeMessage(contextId, queue,
+                "glGetShaderiv(%d, GL_COMPILE_STATUS, {0})", shader.name);
+        assert rcv.hasData();
+        if (rcv.getData().asReadOnlyByteBuffer().getInt() == 0) {
+            // compile failed
+            rcv = exchangeMessage(contextId, queue,
+                    "glGetShaderInfoLog(%d, 0, 0, \"\")", shader.name);
+            final String title = String.format("Shader %d in 0x%s failed to compile",
+                    shader.name, Integer.toHexString(shader.context.context.contextId));
+            final String message = rcv.getData().toStringUtf8();
+            sampleView.getSite().getShell().getDisplay().syncExec(new Runnable() {
+                @Override
+                public void run()
+                {
+                    MessageDialog.openWarning(getShell(), title, message);
+                }
+            });
+        } else
+            for (int programName : shader.programs) {
+                GLProgram program = shader.context.getProgram(programName);
+                exchangeMessage(contextId, queue, "glLinkProgram(%d)", program.name);
+                rcv = exchangeMessage(contextId, queue,
+                        "glGetProgramiv(%d, GL_LINK_STATUS, {0})", program.name);
+                assert rcv.hasData();
+                if (rcv.getData().asReadOnlyByteBuffer().getInt() != 0)
+                    continue;
+                // link failed
+                rcv = exchangeMessage(contextId, queue,
+                            "glGetProgramInfoLog(%d, 0, 0, \"\")", program.name);
+                final String title = String.format("Program %d in 0x%s failed to link",
+                        program.name, Integer.toHexString(program.context.context.contextId));
+                final String message = rcv.getData().toStringUtf8();
+                sampleView.getSite().getShell().getDisplay().syncExec(new Runnable() {
+                    @Override
+                    public void run()
+                    {
+                        MessageDialog.openWarning(getShell(), title, message);
+                    }
+                });
+                // break;
+            }
+
+        // TODO: add to upload results if failed
+
+        Message.Builder builder = getBuilder(contextId);
+        builder.setExpectResponse(false);
+        if (queue.getPartialMessage(contextId) != null)
+            // the glShaderSource interrupted a BeforeCall, so continue
+            builder.setFunction(Function.CONTINUE);
+        else
+            builder.setFunction(Function.SKIP);
+        queue.sendMessage(builder.build());
+
+        return true;
+    }
+
+    @Override
+    public void widgetSelected(SelectionEvent e) {
+        if (e.getSource() == uploadShader && null != current) {
+            uploadShader();
+            return;
+        } else if (e.getSource() == restoreShader && null != current) {
+            current.source = styledText.getText();
+            styledText.setText(current.originalSource);
+            return;
+        }
+
+        if (list.getSelectionCount() < 1)
+            return;
+        if (null != current && !current.source.equals(styledText.getText())) {
+            String[] btns = {
+                    "&Upload", "&Save", "&Discard"
+            };
+            MessageDialog dialog = new MessageDialog(this.getShell(), "Shader Edited",
+                    null, "Shader source has been edited", MessageDialog.QUESTION, btns, 0);
+            int rc = dialog.open();
+            if (rc == SWT.DEFAULT || rc == 0)
+                uploadShader();
+            else if (rc == 1)
+                current.source = styledText.getText();
+            // else if (rc == 2) do nothing; selection is changing
+        }
+        String[] details = list.getSelection()[0].split("\\s+");
+        final int contextId = Integer.parseInt(details[0], 16);
+        int name = Integer.parseInt(details[2]);
+        current = sampleView.debugContexts.get(contextId).currentContext.serverShader.shaders
+                .get(name);
+        styledText.setText(current.source);
+    }
+
+    @Override
+    public void widgetDefaultSelected(SelectionEvent e) {
+        widgetSelected(e);
+    }
+
+    @Override
+    public void modifyText(ExtendedModifyEvent event) {
+        final String[] keywords = {
+                "gl_Position", "gl_FragColor"
+        };
+        // FIXME: proper scanner for syntax highlighting
+        String text = styledText.getText();
+        int start = event.start;
+        int end = event.start + event.length;
+        start -= 20; // deleting chars from keyword causes rescan
+        end += 20;
+        if (start < 0)
+            start = 0;
+        if (end > text.length())
+            end = text.length();
+        if (null != styledText.getStyleRangeAtOffset(event.start)) {
+            StyleRange clearStyleRange = new StyleRange();
+            clearStyleRange.start = start;
+            clearStyleRange.length = end - start;
+            clearStyleRange.foreground = event.display.getSystemColor(SWT.COLOR_BLACK);
+            styledText.setStyleRange(clearStyleRange);
+        }
+
+        while (start < end) {
+            for (final String keyword : keywords) {
+                if (!text.substring(start).startsWith(keyword))
+                    continue;
+                if (start > 0) {
+                    final char before = text.charAt(start - 1);
+                    if (Character.isLetterOrDigit(before))
+                        continue;
+                    else if (before == '_')
+                        continue;
+                }
+                if (start + keyword.length() < text.length()) {
+                    final char after = text.charAt(start + keyword.length());
+                    if (Character.isLetterOrDigit(after))
+                        continue;
+                    else if (after == '_')
+                        continue;
+                }
+                StyleRange style1 = new StyleRange();
+                style1.start = start;
+                style1.length = keyword.length();
+                style1.foreground = event.display.getSystemColor(SWT.COLOR_BLUE);
+                styledText.setStyleRange(style1);
+            }
+            start++;
+        }
+    }
+}
diff --git a/tools/glesv2debugger/test/com/android/glesv2debugger/MessageParserExTest.java b/tools/glesv2debugger/test/com/android/glesv2debugger/MessageParserExTest.java
new file mode 100644
index 0000000..d2a9a7e
--- /dev/null
+++ b/tools/glesv2debugger/test/com/android/glesv2debugger/MessageParserExTest.java
@@ -0,0 +1,115 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public class MessageParserExTest {
+    /**
+     * @throws java.lang.Exception
+     */
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @Test
+    public void testParseFloats() {
+        final MessageParserEx parser = new MessageParserEx();
+        final String args = "{0, 1    ,2,3  }";
+        parser.args = args;
+        final ByteBuffer data = parser.parseFloats(4).asReadOnlyByteBuffer();
+        data.order(SampleView.targetByteOrder);
+        for (int i = 0; i < 4; i++)
+            assertEquals(i, data.getFloat(), 0);
+    }
+
+    @Test
+    public void testParseArgument() {
+        final MessageParserEx parser = new MessageParserEx();
+        final String args = "sdfa   =  GL_VERTEX_SHADER , -5421 ,0x443=0x54f";
+        parser.args = args;
+        assertEquals(GLEnum.GL_VERTEX_SHADER.value, parser.parseArgument());
+        assertEquals(-5421, parser.parseArgument());
+        assertEquals(0x54f, parser.parseArgument());
+    }
+
+    /**
+     * Test method for
+     * {@link com.android.glesv2debugger.MessageParserEx#parse_glShaderSource(com.android.glesv2debugger.DebuggerMessage.Message.Builder)}
+     * .
+     */
+    @Test
+    public void testParse_glShaderSource() {
+        final Message.Builder builder = Message.newBuilder();
+        final MessageParserEx messageParserEx = new MessageParserEx();
+        final String source = "dks \n jafhskjaho { urehg ; } hskjg";
+        messageParserEx.parse(builder, "void glShaderSource ( shader=4, count= 1, "
+                                + "string =\"" + source + "\"  , 0x0)");
+        assertEquals(Function.glShaderSource, builder.getFunction());
+        assertEquals(4, builder.getArg0());
+        assertEquals(1, builder.getArg1());
+        assertEquals(source, builder.getData().toStringUtf8());
+        assertEquals(0, builder.getArg3());
+    }
+
+    @Test
+    public void testParse_glBlendEquation() {
+        assertNotNull(MessageParserEx.instance);
+        final Message.Builder builder = Message.newBuilder();
+        MessageParserEx.instance.parse(builder, "void glBlendEquation ( mode= GL_ADD ) ; ");
+        assertEquals(Function.glBlendEquation, builder.getFunction());
+        assertEquals(GLEnum.GL_ADD.value, builder.getArg0());
+    }
+
+    /** loopback testing of typical generated MessageFormatter and MessageParser */
+    @Test
+    public void testParseFormatterMessage() {
+        final ByteBuffer srcData = ByteBuffer.allocate(4 * 2 * 4);
+        srcData.order(SampleView.targetByteOrder);
+        for (int i = 0; i < 4 * 2; i++)
+            srcData.putFloat(i);
+        srcData.rewind();
+        Message.Builder builder = Message.newBuilder();
+        builder.setContextId(3752).setExpectResponse(false).setType(Type.CompleteCall);
+        builder.setFunction(Function.glUniformMatrix2fv);
+        builder.setArg0(54).setArg1(2).setArg2(0).setData(ByteString.copyFrom(srcData));
+        Message msg = builder.build();
+        builder = msg.toBuilder();
+        String formatted = MessageFormatter.format(msg, false);
+        formatted = formatted.substring(0, formatted.indexOf('(')) + ' ' + builder.getFunction() +
+                formatted.substring(formatted.indexOf('('));
+        Message.Builder parsed = Message.newBuilder();
+        MessageParserEx.instance.parse(parsed, formatted);
+        assertEquals(builder.getFunction(), parsed.getFunction());
+        assertEquals(builder.getArg0(), parsed.getArg0());
+        assertEquals(builder.getArg1(), parsed.getArg1());
+        assertEquals(builder.getArg2(), parsed.getArg2());
+        assertEquals(builder.getData().toStringUtf8(), parsed.getData().toStringUtf8());
+    }
+
+}
diff --git a/tools/glesv2debugger/test/com/android/glesv2debugger/MessageQueueTest.java b/tools/glesv2debugger/test/com/android/glesv2debugger/MessageQueueTest.java
new file mode 100644
index 0000000..5f8e93d
--- /dev/null
+++ b/tools/glesv2debugger/test/com/android/glesv2debugger/MessageQueueTest.java
@@ -0,0 +1,183 @@
+/*
+ ** Copyright 2011, 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.glesv2debugger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.Function;
+import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteOrder;
+
+public class MessageQueueTest {
+    private MessageQueue queue;
+
+    /**
+     * @throws java.lang.Exception
+     */
+    @Before
+    public void setUp() throws Exception {
+        queue = new MessageQueue(null, new ProcessMessage[0]);
+    }
+
+    /**
+     * Test method for
+     * {@link com.android.glesv2debugger.MessageQueue#defaultProcessMessage(com.android.glesv2debugger.DebuggerMessage.Message, boolean, boolean)}
+     * .
+     * 
+     * @throws IOException
+     */
+    @Test
+    public void testDefaultProcessMessage() throws IOException {
+        final int contextId = 8784;
+        assertNull(queue.getPartialMessage(contextId));
+        Message.Builder builder = Message.newBuilder();
+        builder.setContextId(contextId);
+        builder.setExpectResponse(false);
+        builder.setFunction(Function.glFinish);
+        builder.setType(Type.BeforeCall);
+        Message msg = builder.build();
+        queue.defaultProcessMessage(msg, false, false);
+        assertNotNull(queue.getPartialMessage(contextId));
+
+        builder = msg.toBuilder();
+        builder.setType(Type.AfterCall);
+        builder.setTime(5);
+        msg = builder.build();
+        queue.defaultProcessMessage(msg, false, false);
+        assertNull(queue.getPartialMessage(contextId));
+        Message complete = queue.removeCompleteMessage(contextId);
+        assertNotNull(complete);
+        assertEquals(contextId, complete.getContextId());
+        assertEquals(msg.getFunction(), complete.getFunction());
+        assertEquals(msg.getTime(), complete.getTime(), 0);
+        assertEquals(Type.CompleteCall, complete.getType());
+
+        // an already complete message should just be added to complete queue
+        queue.defaultProcessMessage(complete, false, false);
+        assertNull(queue.getPartialMessage(contextId));
+        complete = queue.removeCompleteMessage(contextId);
+        assertNotNull(complete);
+        assertEquals(contextId, complete.getContextId());
+        assertEquals(msg.getFunction(), complete.getFunction());
+        assertEquals(msg.getTime(), complete.getTime(), 0);
+        assertEquals(Type.CompleteCall, complete.getType());
+    }
+
+    @Test
+    public void testCompletePartialMessage() throws IOException {
+        final int contextId = 8784;
+        assertNull(queue.getPartialMessage(contextId));
+        Message.Builder builder = Message.newBuilder();
+        builder.setContextId(contextId);
+        builder.setExpectResponse(false);
+        builder.setFunction(Function.glFinish);
+        builder.setType(Type.BeforeCall);
+        Message msg = builder.build();
+        queue.defaultProcessMessage(msg, false, false);
+        assertNotNull(queue.getPartialMessage(contextId));
+        queue.completePartialMessage(contextId);
+
+        final Message complete = queue.removeCompleteMessage(contextId);
+        assertNotNull(complete);
+        assertEquals(contextId, complete.getContextId());
+        assertEquals(msg.getFunction(), complete.getFunction());
+        assertEquals(msg.getTime(), complete.getTime(), 0);
+        assertEquals(Type.BeforeCall, complete.getType());
+    }
+
+    /** Write two messages from two contexts to file and test handling them */
+    @Test
+    public void testRunWithFile() throws FileNotFoundException, IOException, InterruptedException {
+        final File filePath = File.createTempFile("test", ".gles2dbg");
+        DataOutputStream file = new DataOutputStream(new FileOutputStream(filePath));
+        Message.Builder builder = Message.newBuilder();
+        final int contextId0 = 521643, contextId1 = 87634;
+        assertNull(queue.removeCompleteMessage(contextId0));
+        assertNull(queue.removeCompleteMessage(contextId1));
+
+        builder.setContextId(contextId0).setExpectResponse(false).setType(Type.BeforeCall);
+        builder.setFunction(Function.glClear).setArg0(contextId0);
+        Message msg0 = builder.build();
+        byte[] data = msg0.toByteArray();
+        file.writeInt(data.length);
+        file.write(data);
+
+        builder = Message.newBuilder();
+        builder.setContextId(contextId1).setExpectResponse(false).setType(Type.BeforeCall);
+        builder.setFunction(Function.glDisable).setArg0(contextId1);
+        Message msg1 = builder.build();
+        data = msg1.toByteArray();
+        file.writeInt(data.length);
+        file.write(data);
+
+        builder = Message.newBuilder();
+        msg0 = builder.setContextId(msg0.getContextId()).setExpectResponse(false)
+                .setType(Type.AfterCall).setFunction(msg0.getFunction()).setTime(2).build();
+        data = msg0.toByteArray();
+        file.writeInt(data.length);
+        file.write(data);
+
+        builder = Message.newBuilder();
+        msg1 = builder.setContextId(msg1.getContextId()).setExpectResponse(false)
+                .setType(Type.AfterCall).setFunction(msg1.getFunction()).setTime(465).build();
+        data = msg1.toByteArray();
+        file.writeInt(data.length);
+        file.write(data);
+
+        file.close();
+
+        FileInputStream fis = new FileInputStream(filePath);
+        // Java VM uses big endian, so the file was written in big endian
+        queue.start(ByteOrder.BIG_ENDIAN, fis);
+        queue.thread.join();
+
+        Message complete0 = queue.removeCompleteMessage(msg0.getContextId());
+        assertNotNull(complete0);
+        assertNull(queue.removeCompleteMessage(contextId0));
+        assertEquals(contextId0, complete0.getContextId());
+        assertEquals(false, complete0.getExpectResponse());
+        assertEquals(Type.CompleteCall, complete0.getType());
+        assertEquals(msg0.getFunction(), complete0.getFunction());
+        assertEquals(contextId0, complete0.getArg0());
+        assertEquals(msg0.getTime(), complete0.getTime(), 0);
+
+        Message complete1 = queue.removeCompleteMessage(msg1.getContextId());
+        assertNotNull(complete1);
+        assertNull(queue.removeCompleteMessage(contextId1));
+        assertEquals(contextId1, complete1.getContextId());
+        assertEquals(false, complete1.getExpectResponse());
+        assertEquals(Type.CompleteCall, complete1.getType());
+        assertEquals(msg1.getFunction(), complete1.getFunction());
+        assertEquals(contextId1, complete1.getArg0());
+        assertEquals(msg1.getTime(), complete1.getTime(), 0);
+
+        filePath.delete();
+    }
+}
diff --git a/tools/jdwpspy/Android.mk b/tools/jdwpspy/Android.mk
index eca3e22..2201aab 100644
--- a/tools/jdwpspy/Android.mk
+++ b/tools/jdwpspy/Android.mk
@@ -4,9 +4,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
-	Main.c \
-	Net.c \
-	find_JdwpConstants.c
+	Main.cpp \
+	Net.cpp \
+	find_JdwpConstants.cpp
 
 LOCAL_C_INCLUDES += \
 	dalvik/vm
diff --git a/tools/jdwpspy/Common.h b/tools/jdwpspy/Common.h
index c42d183..ddaba9c 100644
--- a/tools/jdwpspy/Common.h
+++ b/tools/jdwpspy/Common.h
@@ -14,11 +14,6 @@
 typedef unsigned int u4;
 typedef unsigned long long u8;
 
-#ifndef __bool_true_false_are_defined
-typedef enum { false=0, true=!false } bool;
-#define __bool_true_false_are_defined 1
-#endif
-
 #define NELEM(x) (sizeof(x) / sizeof((x)[0]))
 
 #ifndef _JDWP_MISC_INLINE
diff --git a/tools/jdwpspy/Main.c b/tools/jdwpspy/Main.cpp
similarity index 87%
rename from tools/jdwpspy/Main.c
rename to tools/jdwpspy/Main.cpp
index 62a0007..0f68d52 100644
--- a/tools/jdwpspy/Main.c
+++ b/tools/jdwpspy/Main.cpp
@@ -33,7 +33,7 @@
 void printHexDumpEx(FILE* fp, const void* vaddr, size_t length,
     HexDumpMode mode, const char* prefix)
 {
-    const unsigned char* addr = vaddr;
+    const unsigned char* addr = reinterpret_cast<const unsigned char*>(vaddr);
     char out[77];       /* exact fit */
     unsigned int offset;    /* offset to show while printing */
     char* hex;
@@ -53,19 +53,17 @@
     gap = (int) offset & 0x0f;
     while (length) {
         unsigned int lineOffset = offset & ~0x0f;
-        int i, count;
-        
-        hex = out;
-        asc = out + 59;
+        char* hex = out;
+        char* asc = out + 59;
 
-        for (i = 0; i < 8; i++) {
+        for (int i = 0; i < 8; i++) {
             *hex++ = gHexDigit[lineOffset >> 28];
             lineOffset <<= 4;
         }
         hex++;
         hex++;
 
-        count = ((int)length > 16-gap) ? 16-gap : (int) length; /* cap length */
+        int count = ((int)length > 16-gap) ? 16-gap : (int) length; /* cap length */
         assert(count != 0);
         assert(count+gap <= 16);
 
@@ -75,6 +73,7 @@
             asc += gap;
         }
 
+        int i;
         for (i = gap ; i < count+gap; i++) {
             *hex++ = gHexDigit[*addr >> 4];
             *hex++ = gHexDigit[*addr & 0x0f];
@@ -118,9 +117,6 @@
  */
 int main(int argc, char* argv[])
 {
-    int connectPort, listenPort;
-    int cc;
-
     if (argc < 2 || argc > 3) {
         usage("jdwpspy");
         return 2;
@@ -129,15 +125,15 @@
     setvbuf(stdout, NULL, _IONBF, 0);
 
     /* may want this to be host:port */
-    connectPort = atoi(argv[1]);
+    int connectPort = atoi(argv[1]);
 
+    int listenPort;
     if (argc > 2)
         listenPort = atoi(argv[2]);
     else
         listenPort = connectPort + 1;
 
-    cc = run("localhost", connectPort, listenPort);
+    int cc = run("localhost", connectPort, listenPort);
 
     return (cc != 0);
 }
-
diff --git a/tools/jdwpspy/Net.c b/tools/jdwpspy/Net.cpp
similarity index 97%
rename from tools/jdwpspy/Net.c
rename to tools/jdwpspy/Net.cpp
index 555fe49..b923006 100644
--- a/tools/jdwpspy/Net.c
+++ b/tools/jdwpspy/Net.cpp
@@ -7,9 +7,9 @@
 #include "jdwp/JdwpConstants.h"
 
 #include <stdlib.h>
-#include <unistd.h>     
+#include <unistd.h>
 #include <stdio.h>
-#include <string.h>     
+#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -205,9 +205,7 @@
  */
 static const char* getCommandName(int cmdSet, int cmd)
 {
-    int i;
-
-    for (i = 0; i < (int) NELEM(gHandlerMap); i++) {
+    for (int i = 0; i < (int) NELEM(gHandlerMap); i++) {
         if (gHandlerMap[i].cmdSet == cmdSet &&
             gHandlerMap[i].cmd == cmd)
         {
@@ -229,10 +227,7 @@
 NetState* jdwpNetStartup(unsigned short listenPort, const char* connectHost,
     unsigned short connectPort)
 {
-    NetState* netState;
-    int one = 1;
-
-    netState = (NetState*) malloc(sizeof(*netState));
+    NetState* netState = (NetState*) malloc(sizeof(*netState));
     memset(netState, 0, sizeof(*netState));
     netState->listenSock = -1;
     netState->dbg.sock = netState->vm.sock = -1;
@@ -251,12 +246,15 @@
     }
 
     /* allow immediate re-use if we die */
-    if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one,
-            sizeof(one)) < 0)
     {
-        fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n",
-            strerror(errno));
-        goto fail;
+        int one = 1;
+        if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one,
+                sizeof(one)) < 0)
+        {
+            fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n",
+                strerror(errno));
+            goto fail;
+        }
     }
 
     struct sockaddr_in addr;
@@ -474,7 +472,7 @@
     char prefix[3];
     u4 length, id;
     u1 flags, cmdSet=0, cmd=0;
-    u2 error=0;
+    JdwpError error = ERR_NONE;
     bool reply;
     int dataLen;
 
@@ -483,7 +481,7 @@
     flags = get1(buf+8);
     if ((flags & kJDWPFlagReply) != 0) {
         reply = true;
-        error = get2BE(buf+9);
+        error = static_cast<JdwpError>(get2BE(buf+9));
     } else {
         reply = false;
         cmdSet = get1(buf+9);
@@ -748,4 +746,3 @@
 
     return 0;
 }
-
diff --git a/tools/jdwpspy/find_JdwpConstants.c b/tools/jdwpspy/find_JdwpConstants.c
deleted file mode 100644
index 8ff8186..0000000
--- a/tools/jdwpspy/find_JdwpConstants.c
+++ /dev/null
@@ -1 +0,0 @@
-#include "jdwp/JdwpConstants.c"
diff --git a/tools/jdwpspy/find_JdwpConstants.cpp b/tools/jdwpspy/find_JdwpConstants.cpp
new file mode 100644
index 0000000..57b7dbb
--- /dev/null
+++ b/tools/jdwpspy/find_JdwpConstants.cpp
@@ -0,0 +1 @@
+#include "jdwp/JdwpConstants.cpp"