Merge "remove support for console in SurfaceFlinger"
diff --git a/api/current.txt b/api/current.txt
index 374c415..975444e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2851,6 +2851,7 @@
     method public void onSaveInstanceState(android.os.Bundle);
     method public void onStart();
     method public void onStop();
+    method public void onViewCreated(android.view.View, android.os.Bundle);
     method public void registerForContextMenu(android.view.View);
     method public void setArguments(android.os.Bundle);
     method public void setHasOptionsMenu(boolean);
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 80ba1e9..9aa70a4 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -48,6 +48,11 @@
         LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
         return -errno;
     }
+    if (chmod(pkgdir, 0751) < 0) {
+        LOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));
+        unlink(pkgdir);
+        return -errno;
+    }
     if (chown(pkgdir, uid, gid) < 0) {
         LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
         unlink(pkgdir);
@@ -58,6 +63,12 @@
         unlink(pkgdir);
         return -errno;
     }
+    if (chmod(libdir, 0755) < 0) {
+        LOGE("cannot chmod dir '%s': %s\n", libdir, strerror(errno));
+        unlink(libdir);
+        unlink(pkgdir);
+        return -errno;
+    }
     if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
         LOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
         unlink(libdir);
@@ -67,15 +78,15 @@
     return 0;
 }
 
-int uninstall(const char *pkgname)
+int uninstall(const char *pkgname, uid_t persona)
 {
     char pkgdir[PKG_PATH_MAX];
 
-    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0))
+    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona))
         return -1;
 
-        /* delete contents AND directory, no exceptions */
-    return delete_dir_contents(pkgdir, 1, 0);
+    /* delete contents AND directory, no exceptions */
+    return delete_dir_contents(pkgdir, 1, NULL);
 }
 
 int renamepkg(const char *oldpkgname, const char *newpkgname)
@@ -95,17 +106,48 @@
     return 0;
 }
 
-int delete_user_data(const char *pkgname)
+int delete_user_data(const char *pkgname, uid_t persona)
 {
     char pkgdir[PKG_PATH_MAX];
 
-    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0))
+    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona))
         return -1;
 
-        /* delete contents, excluding "lib", but not the directory itself */
+    /* delete contents, excluding "lib", but not the directory itself */
     return delete_dir_contents(pkgdir, 0, "lib");
 }
 
+int make_user_data(const char *pkgname, uid_t uid, uid_t persona)
+{
+    char pkgdir[PKG_PATH_MAX];
+    char real_libdir[PKG_PATH_MAX];
+
+    // Create the data dir for the package
+    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) {
+        return -1;
+    }
+    if (mkdir(pkgdir, 0751) < 0) {
+        LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
+        return -errno;
+    }
+    if (chown(pkgdir, uid, uid) < 0) {
+        LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
+        unlink(pkgdir);
+        return -errno;
+    }
+    return 0;
+}
+
+int delete_persona(uid_t persona)
+{
+    char pkgdir[PKG_PATH_MAX];
+
+    if (create_persona_path(pkgdir, persona))
+        return -1;
+
+    return delete_dir_contents(pkgdir, 1, NULL);
+}
+
 int delete_cache(const char *pkgname)
 {
     char cachedir[PKG_PATH_MAX];
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index e0d0f97..c062d36 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -49,7 +49,7 @@
 
 static int do_remove(char **arg, char reply[REPLY_MAX])
 {
-    return uninstall(arg[0]); /* pkgname */
+    return uninstall(arg[0], atoi(arg[1])); /* pkgname, userid */
 }
 
 static int do_rename(char **arg, char reply[REPLY_MAX])
@@ -92,7 +92,17 @@
 
 static int do_rm_user_data(char **arg, char reply[REPLY_MAX])
 {
-    return delete_user_data(arg[0]); /* pkgname */
+    return delete_user_data(arg[0], atoi(arg[1])); /* pkgname, userid */
+}
+
+static int do_mk_user_data(char **arg, char reply[REPLY_MAX])
+{
+    return make_user_data(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, userid */
+}
+
+static int do_rm_user(char **arg, char reply[REPLY_MAX])
+{
+    return delete_persona(atoi(arg[0])); /* userid */
 }
 
 static int do_movefiles(char **arg, char reply[REPLY_MAX])
@@ -122,16 +132,18 @@
     { "dexopt",               3, do_dexopt },
     { "movedex",              2, do_move_dex },
     { "rmdex",                1, do_rm_dex },
-    { "remove",               1, do_remove },
+    { "remove",               2, do_remove },
     { "rename",               2, do_rename },
     { "freecache",            1, do_free_cache },
     { "rmcache",              1, do_rm_cache },
     { "protect",              2, do_protect },
     { "getsize",              3, do_get_size },
-    { "rmuserdata",           1, do_rm_user_data },
+    { "rmuserdata",           2, do_rm_user_data },
     { "movefiles",            0, do_movefiles },
     { "linklib",              2, do_linklib },
     { "unlinklib",            1, do_unlinklib },
+    { "mkuserdata",           3, do_mk_user_data },
+    { "rmuser",               1, do_rm_user },
 };
 
 static int readx(int s, void *_buf, int count)
@@ -286,14 +298,50 @@
         return -1;
     }
 
+    // append "app/" to dirs[0]
+    char *system_app_path = build_string2(android_system_dirs.dirs[0].path, APP_SUBDIR);
+    android_system_dirs.dirs[0].path = system_app_path;
+    android_system_dirs.dirs[0].len = strlen(system_app_path);
+
     // vendor
     // TODO replace this with an environment variable (doesn't exist yet)
-    android_system_dirs.dirs[1].path = "/vendor/";
+    android_system_dirs.dirs[1].path = "/vendor/app/";
     android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path);
 
     return 0;
 }
 
+int initialize_directories() {
+    // /data/user
+    char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX);
+    // /data/data
+    char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX);
+    // /data/user/0
+    char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX,
+            "0");
+    int ret = -1;
+    if (user_data_dir != NULL && primary_data_dir != NULL && legacy_data_dir != NULL) {
+        ret = 0;
+        // Make the /data/user directory if necessary
+        if (access(user_data_dir, R_OK) < 0) {
+            if (mkdir(user_data_dir, 0755) < 0) {
+                return -1;
+            }
+            if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) {
+                return -1;
+            }
+        }
+        // Make the /data/user/0 symlink to /data/data if necessary
+        if (access(primary_data_dir, R_OK) < 0) {
+              ret = symlink(legacy_data_dir, primary_data_dir);
+        }
+        free(user_data_dir);
+        free(legacy_data_dir);
+        free(primary_data_dir);
+    }
+    return ret;
+}
+
 int main(const int argc, const char *argv[]) {
     char buf[BUFFER_MAX];
     struct sockaddr addr;
@@ -305,6 +353,11 @@
         exit(1);
     }
 
+    if (initialize_directories() < 0) {
+        LOGE("Could not create directories; exiting.\n");
+        exit(1);
+    }
+
     lsocket = android_get_control_socket(SOCKET_PATH);
     if (lsocket < 0) {
         LOGE("Failed to get socket from environment: %s\n", strerror(errno));
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index cbca135..e5f6739 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -102,6 +102,9 @@
                     const char *postfix,
                     uid_t persona);
 
+int create_persona_path(char path[PKG_PATH_MAX],
+                    uid_t persona);
+
 int is_valid_package_name(const char* pkgname);
 
 int create_cache_path(char path[PKG_PATH_MAX], const char *src);
@@ -124,12 +127,17 @@
 
 int append_and_increment(char** dst, const char* src, size_t* dst_size);
 
+char *build_string2(char *s1, char *s2);
+char *build_string3(char *s1, char *s2, char *s3);
+
 /* commands.c */
 
 int install(const char *pkgname, uid_t uid, gid_t gid);
-int uninstall(const char *pkgname);
+int uninstall(const char *pkgname, uid_t persona);
 int renamepkg(const char *oldpkgname, const char *newpkgname);
-int delete_user_data(const char *pkgname);
+int delete_user_data(const char *pkgname, uid_t persona);
+int make_user_data(const char *pkgname, uid_t uid, uid_t persona);
+int delete_persona(uid_t persona);
 int delete_cache(const char *pkgname);
 int move_dex(const char *src, const char *dst);
 int rm_dex(const char *path);
diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c
index f37a6fb..3099b83 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -96,6 +96,46 @@
 }
 
 /**
+ * Create the path name for user data for a certain persona.
+ * Returns 0 on success, and -1 on failure.
+ */
+int create_persona_path(char path[PKG_PATH_MAX],
+                    uid_t persona)
+{
+    size_t uid_len;
+    char* persona_prefix;
+    if (persona == 0) {
+        persona_prefix = PRIMARY_USER_PREFIX;
+        uid_len = 0;
+    } else {
+        persona_prefix = SECONDARY_USER_PREFIX;
+        uid_len = snprintf(NULL, 0, "%d", persona);
+    }
+
+    char *dst = path;
+    size_t dst_size = PKG_PATH_MAX;
+
+    if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0
+            || append_and_increment(&dst, persona_prefix, &dst_size) < 0) {
+        LOGE("Error building prefix for user path");
+        return -1;
+    }
+
+    if (persona != 0) {
+        if (dst_size < uid_len + 1) {
+            LOGE("Error building user path");
+            return -1;
+        }
+        int ret = snprintf(dst, dst_size, "%d", persona);
+        if (ret < 0 || (size_t) ret != uid_len) {
+            LOGE("Error appending persona id to path");
+            return -1;
+        }
+    }
+    return 0;
+}
+
+/**
  * Checks whether the package name is valid. Returns -1 on error and
  * 0 on success.
  */
@@ -408,3 +448,35 @@
     *dst_size -= ret;
     return 0;
 }
+
+char *build_string2(char *s1, char *s2) {
+    if (s1 == NULL || s2 == NULL) return NULL;
+
+    int len_s1 = strlen(s1);
+    int len_s2 = strlen(s2);
+    int len = len_s1 + len_s2 + 1;
+    char *result = malloc(len);
+    if (result == NULL) return NULL;
+
+    strcpy(result, s1);
+    strcpy(result + len_s1, s2);
+
+    return result;
+}
+
+char *build_string3(char *s1, char *s2, char *s3) {
+    if (s1 == NULL || s2 == NULL || s3 == NULL) return NULL;
+
+    int len_s1 = strlen(s1);
+    int len_s2 = strlen(s2);
+    int len_s3 = strlen(s3);
+    int len = len_s1 + len_s2 + len_s3 + 1;
+    char *result = malloc(len);
+    if (result == NULL) return NULL;
+
+    strcpy(result, s1);
+    strcpy(result + len_s1, s2);
+    strcpy(result + len_s1 + len_s2, s3);
+
+    return result;
+}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index d058e38..78a450c 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -35,9 +35,9 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.provider.Settings;
 
 import java.io.File;
 import java.lang.reflect.Field;
@@ -60,6 +60,7 @@
 
     private static final String PM_NOT_RUNNING_ERR =
         "Error: Could not access the Package Manager.  Is the system running?";
+    private static final int ROOT_UID = 0;
 
     public static void main(String[] args) {
         new Pm().run(args);
@@ -127,6 +128,16 @@
             return;
         }
 
+        if ("createUser".equals(op)) {
+            runCreateUser();
+            return;
+        }
+
+        if ("removeUser".equals(op)) {
+            runRemoveUser();
+            return;
+        }
+
         try {
             if (args.length == 1) {
                 if (args[0].equalsIgnoreCase("-l")) {
@@ -763,6 +774,63 @@
         }
     }
 
+    public void runCreateUser() {
+        // Need to be run as root
+        if (Process.myUid() != ROOT_UID) {
+            System.err.println("Error: createUser must be run as root");
+            return;
+        }
+        String name;
+        String arg = nextArg();
+        if (arg == null) {
+            System.err.println("Error: no user name specified.");
+            showUsage();
+            return;
+        }
+        name = arg;
+        try {
+            if (mPm.createUser(name, 0) == null) {
+                System.err.println("Error: couldn't create user.");
+                showUsage();
+            }
+        } catch (RemoteException e) {
+            System.err.println(e.toString());
+            System.err.println(PM_NOT_RUNNING_ERR);
+        }
+
+    }
+
+    public void runRemoveUser() {
+        // Need to be run as root
+        if (Process.myUid() != ROOT_UID) {
+            System.err.println("Error: removeUser must be run as root");
+            return;
+        }
+        int userId;
+        String arg = nextArg();
+        if (arg == null) {
+            System.err.println("Error: no user id specified.");
+            showUsage();
+            return;
+        }
+        try {
+            userId = Integer.parseInt(arg);
+        } catch (NumberFormatException e) {
+            System.err.println("Error: user id has to be a number.");
+            showUsage();
+            return;
+        }
+        try {
+            if (!mPm.removeUser(userId)) {
+                System.err.println("Error: couldn't remove user.");
+                showUsage();
+            }
+        } catch (RemoteException e) {
+            System.err.println(e.toString());
+            System.err.println(PM_NOT_RUNNING_ERR);
+        }
+    }
+
     class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
         boolean finished;
         boolean result;
@@ -1006,6 +1074,8 @@
         System.err.println("       pm enable PACKAGE_OR_COMPONENT");
         System.err.println("       pm disable PACKAGE_OR_COMPONENT");
         System.err.println("       pm setInstallLocation [0/auto] [1/internal] [2/external]");
+        System.err.println("       pm createUser USER_NAME");
+        System.err.println("       pm removeUser USER_ID");
         System.err.println("");
         System.err.println("The list packages command prints all packages, optionally only");
         System.err.println("those whose package name contains the text in FILTER.  Options:");
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ef8ba8e..85918cf 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1113,7 +1113,11 @@
      */
     @Override
     public UserInfo createUser(String name, int flags) {
-        // TODO
+        try {
+            return mPM.createUser(name, flags);
+        } catch (RemoteException e) {
+            // Should never happen!
+        }
         return null;
     }
 
@@ -1136,8 +1140,11 @@
      */
     @Override
     public boolean removeUser(int id) {
-        // TODO:
-        return false;
+        try {
+            return mPM.removeUser(id);
+        } catch (RemoteException e) {
+            return false;
+        }
     }
 
     /**
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index a528a1a..dd158f9 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -326,8 +326,9 @@
     static final int INITIALIZING = 0;     // Not yet created.
     static final int CREATED = 1;          // Created.
     static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
-    static final int STARTED = 3;          // Created and started, not resumed.
-    static final int RESUMED = 4;          // Created started and resumed.
+    static final int STOPPED = 3;          // Fully created, not started.
+    static final int STARTED = 4;          // Created and started, not resumed.
+    static final int RESUMED = 5;          // Created started and resumed.
     
     int mState = INITIALIZING;
     
@@ -959,6 +960,19 @@
     }
     
     /**
+     * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}
+     * has returned, but before any saved state has been restored in to the view.
+     * This gives subclasses a chance to initialize themselves once
+     * they know their view hierarchy has been completely created.  The fragment's
+     * view hierarchy is not however attached to its parent at this point.
+     * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+     * @param savedInstanceState If non-null, this fragment is being re-constructed
+     * from a previous saved state as given here.
+     */
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+    }
+    
+    /**
      * Called to have the fragment instantiate its user interface view.
      * This is optional, and non-graphical fragments can return null (which
      * is the default implementation).  This will be called between
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 1eaca3b..0da656f 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -714,13 +714,14 @@
                                 null, f.mSavedFragmentState);
                         if (f.mView != null) {
                             f.mView.setSaveFromParentEnabled(false);
+                            if (f.mHidden) f.mView.setVisibility(View.GONE);
                             f.restoreViewState();
-                            if (f.mHidden) f.mView.setVisibility(View.GONE); 
+                            f.onViewCreated(f.mView, f.mSavedFragmentState);
                         }
                     }
                 case Fragment.CREATED:
                     if (newState > Fragment.CREATED) {
-                        if (DEBUG) Log.v(TAG, "moveto CONTENT: " + f);
+                        if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                         if (!f.mFromLayout) {
                             ViewGroup container = null;
                             if (f.mContainerId != 0) {
@@ -744,9 +745,10 @@
                                         anim.start();
                                     }
                                     container.addView(f.mView);
-                                    f.restoreViewState();
                                 }
-                                if (f.mHidden) f.mView.setVisibility(View.GONE); 
+                                if (f.mHidden) f.mView.setVisibility(View.GONE);
+                                f.restoreViewState();
+                                f.onViewCreated(f.mView, f.mSavedFragmentState);
                             }
                         }
                         
@@ -756,10 +758,13 @@
                             throw new SuperNotCalledException("Fragment " + f
                                     + " did not call through to super.onActivityCreated()");
                         }
+                        if (f.mView != null) {
+                        }
                         f.mSavedFragmentState = null;
                     }
                 case Fragment.ACTIVITY_CREATED:
-                    if (newState > Fragment.ACTIVITY_CREATED) {
+                case Fragment.STOPPED:
+                    if (newState > Fragment.STOPPED) {
                         if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                         f.mCalled = false;
                         f.onStart();
@@ -803,9 +808,10 @@
                                     + " did not call through to super.onStop()");
                         }
                     }
+                case Fragment.STOPPED:
                 case Fragment.ACTIVITY_CREATED:
                     if (newState < Fragment.ACTIVITY_CREATED) {
-                        if (DEBUG) Log.v(TAG, "movefrom CONTENT: " + f);
+                        if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
                         if (f.mView != null) {
                             // Need to save the current view state if not
                             // done already.
@@ -1631,7 +1637,7 @@
     }
     
     public void dispatchStop() {
-        moveToState(Fragment.ACTIVITY_CREATED, false);
+        moveToState(Fragment.STOPPED, false);
     }
     
     public void dispatchDestroy() {
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 6e2f4b6..a5ee26c 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -195,11 +195,11 @@
     }
 
     /**
-     * Attach to list view once Fragment is ready to run.
+     * Attach to list view once the view hierarchy has been created.
      */
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
         ensureList();
     }
 
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index f4693c2..56f236d 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -26,8 +26,8 @@
 import android.server.BluetoothService;
 import android.util.Log;
 
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
 
 import java.util.Set;
 
@@ -57,7 +57,7 @@
  * Todo(): Write tests for this class, when the Android Mock support is completed.
  * @hide
  */
-public final class BluetoothDeviceProfileState extends HierarchicalStateMachine {
+public final class BluetoothDeviceProfileState extends StateMachine {
     private static final String TAG = "BluetoothDeviceProfileState";
     private static final boolean DBG = false;
 
@@ -235,16 +235,16 @@
         }
     }
 
-    private class BondedDevice extends HierarchicalState {
+    private class BondedDevice extends State {
         @Override
-        protected void enter() {
+        public void enter() {
             Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what);
             Message m = new Message();
             m.copyFrom(getCurrentMessage());
             sendMessageAtFrontOfQueue(m);
         }
         @Override
-        protected boolean processMessage(Message message) {
+        public boolean processMessage(Message message) {
             log("ACL Connected State -> Processing Message: " + message.what);
             switch(message.what) {
                 case CONNECT_HFP_OUTGOING:
@@ -353,12 +353,12 @@
         }
     }
 
-    private class OutgoingHandsfree extends HierarchicalState {
+    private class OutgoingHandsfree extends State {
         private boolean mStatus = false;
         private int mCommand;
 
         @Override
-        protected void enter() {
+        public void enter() {
             Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
             mCommand = getCurrentMessage().what;
             if (mCommand != CONNECT_HFP_OUTGOING &&
@@ -374,7 +374,7 @@
         }
 
         @Override
-        protected boolean processMessage(Message message) {
+        public boolean processMessage(Message message) {
             log("OutgoingHandsfree State -> Processing Message: " + message.what);
             Message deferMsg = new Message();
             int command = message.what;
@@ -466,12 +466,12 @@
         }
     }
 
-    private class IncomingHandsfree extends HierarchicalState {
+    private class IncomingHandsfree extends State {
         private boolean mStatus = false;
         private int mCommand;
 
         @Override
-        protected void enter() {
+        public void enter() {
             Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what);
             mCommand = getCurrentMessage().what;
             if (mCommand != CONNECT_HFP_INCOMING &&
@@ -487,7 +487,7 @@
         }
 
         @Override
-        protected boolean processMessage(Message message) {
+        public boolean processMessage(Message message) {
             log("IncomingHandsfree State -> Processing Message: " + message.what);
             switch(message.what) {
                 case CONNECT_HFP_OUTGOING:
@@ -546,12 +546,12 @@
         }
     }
 
-    private class OutgoingA2dp extends HierarchicalState {
+    private class OutgoingA2dp extends State {
         private boolean mStatus = false;
         private int mCommand;
 
         @Override
-        protected void enter() {
+        public void enter() {
             Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what);
             mCommand = getCurrentMessage().what;
             if (mCommand != CONNECT_A2DP_OUTGOING &&
@@ -567,7 +567,7 @@
         }
 
         @Override
-        protected boolean processMessage(Message message) {
+        public boolean processMessage(Message message) {
             log("OutgoingA2dp State->Processing Message: " + message.what);
             Message deferMsg = new Message();
             switch(message.what) {
@@ -656,12 +656,12 @@
         }
     }
 
-    private class IncomingA2dp extends HierarchicalState {
+    private class IncomingA2dp extends State {
         private boolean mStatus = false;
         private int mCommand;
 
         @Override
-        protected void enter() {
+        public void enter() {
             Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what);
             mCommand = getCurrentMessage().what;
             if (mCommand != CONNECT_A2DP_INCOMING &&
@@ -677,7 +677,7 @@
         }
 
         @Override
-        protected boolean processMessage(Message message) {
+        public boolean processMessage(Message message) {
             log("IncomingA2dp State->Processing Message: " + message.what);
             switch(message.what) {
                 case CONNECT_HFP_OUTGOING:
@@ -734,12 +734,12 @@
     }
 
 
-    private class OutgoingHid extends HierarchicalState {
+    private class OutgoingHid extends State {
         private boolean mStatus = false;
         private int mCommand;
 
         @Override
-        protected void enter() {
+        public void enter() {
             log("Entering OutgoingHid state with: " + getCurrentMessage().what);
             mCommand = getCurrentMessage().what;
             if (mCommand != CONNECT_HID_OUTGOING &&
@@ -751,7 +751,7 @@
         }
 
         @Override
-        protected boolean processMessage(Message message) {
+        public boolean processMessage(Message message) {
             log("OutgoingHid State->Processing Message: " + message.what);
             Message deferMsg = new Message();
             switch(message.what) {
@@ -815,12 +815,12 @@
         }
     }
 
-  private class IncomingHid extends HierarchicalState {
+  private class IncomingHid extends State {
       private boolean mStatus = false;
       private int mCommand;
 
       @Override
-      protected void enter() {
+    public void enter() {
           log("Entering IncomingHid state with: " + getCurrentMessage().what);
           mCommand = getCurrentMessage().what;
           if (mCommand != CONNECT_HID_INCOMING &&
@@ -832,7 +832,7 @@
       }
 
       @Override
-      protected boolean processMessage(Message message) {
+    public boolean processMessage(Message message) {
           log("IncomingHid State->Processing Message: " + message.what);
           Message deferMsg = new Message();
           switch(message.what) {
diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java
index 18060a0..98afdb8 100644
--- a/core/java/android/bluetooth/BluetoothProfileState.java
+++ b/core/java/android/bluetooth/BluetoothProfileState.java
@@ -22,8 +22,8 @@
 import android.os.Message;
 import android.util.Log;
 
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
 
 /**
  * This state machine is used to serialize the connections
@@ -39,7 +39,7 @@
  * @hide
  */
 
-public class BluetoothProfileState extends HierarchicalStateMachine {
+public class BluetoothProfileState extends StateMachine {
     private static final boolean DBG = true;
     private static final String TAG = "BluetoothProfileState";
 
@@ -101,15 +101,15 @@
         context.registerReceiver(mBroadcastReceiver, filter);
     }
 
-    private class StableState extends HierarchicalState {
+    private class StableState extends State {
         @Override
-        protected void enter() {
+        public void enter() {
             log("Entering Stable State");
             mPendingDevice = null;
         }
 
         @Override
-        protected boolean processMessage(Message msg) {
+        public boolean processMessage(Message msg) {
             if (msg.what != TRANSITION_TO_STABLE) {
                 transitionTo(mPendingCommandState);
             }
@@ -117,15 +117,15 @@
         }
     }
 
-    private class PendingCommandState extends HierarchicalState {
+    private class PendingCommandState extends State {
         @Override
-        protected void enter() {
+        public void enter() {
             log("Entering PendingCommandState State");
             dispatchMessage(getCurrentMessage());
         }
 
         @Override
-        protected boolean processMessage(Message msg) {
+        public boolean processMessage(Message msg) {
             if (msg.what == TRANSITION_TO_STABLE) {
                 transitionTo(mStableState);
             } else {
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index fbf8f92..11cd446 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -35,6 +35,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.content.IntentSender;
 
@@ -329,4 +330,7 @@
 
     boolean setInstallLocation(int loc);
     int getInstallLocation();
+
+    UserInfo createUser(in String name, int flags);
+    boolean removeUser(int userId);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 99c4c7f..ff817c1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -662,10 +662,15 @@
     public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
 
     /**
-     * Feature for {@link #getSystemAvailableFeatures} and
-     * {@link #hasSystemFeature}: The device's audio pipeline is low-latency,
-     * more suitable for audio applications sensitive to delays or lag in
-     * sound input or output.
+     * Range of IDs allocated for a user.
+     * @hide
+     */
+    public static final int PER_USER_RANGE = 100000;
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's
+     * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or
+     * lag in sound input or output.
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
@@ -2387,4 +2392,37 @@
      * @hide
      */
     public abstract void updateUserFlags(int id, int flags);
+
+    /**
+     * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
+     * user.
+     * @hide
+     */
+    public static boolean isSameUser(int uid1, int uid2) {
+        return getUserId(uid1) == getUserId(uid2);
+    }
+
+    /**
+     * Returns the user id for a given uid.
+     * @hide
+     */
+    public static int getUserId(int uid) {
+        return uid / PER_USER_RANGE;
+    }
+
+    /**
+     * Returns the uid that is composed from the userId and the appId.
+     * @hide
+     */
+    public static int getUid(int userId, int appId) {
+        return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
+    }
+
+    /**
+     * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
+     * @hide
+     */
+    public static int getAppId(int uid) {
+        return uid % PER_USER_RANGE;
+    }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 54a8842..564f4f4 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PatternMatcher;
diff --git a/core/java/android/content/pm/UserInfo.aidl b/core/java/android/content/pm/UserInfo.aidl
new file mode 100644
index 0000000..2e7cb8f
--- /dev/null
+++ b/core/java/android/content/pm/UserInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** 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 android.content.pm;
+
+parcelable UserInfo;
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 3704d3a..ba5331c 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -74,8 +74,7 @@
 
     @Override
     public String toString() {
-        return "UserInfo{"
- + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
+        return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
     }
 
     public int describeContents() {
diff --git a/core/java/android/speech/tts/FileSynthesisRequest.java b/core/java/android/speech/tts/FileSynthesisRequest.java
index 6a9b2dc..7efc264 100644
--- a/core/java/android/speech/tts/FileSynthesisRequest.java
+++ b/core/java/android/speech/tts/FileSynthesisRequest.java
@@ -45,6 +45,7 @@
     private int mChannelCount;
     private RandomAccessFile mFile;
     private boolean mStopped = false;
+    private boolean mDone = false;
 
     FileSynthesisRequest(String text, File fileName) {
         super(text);
@@ -89,6 +90,11 @@
     }
 
     @Override
+    boolean isDone() {
+        return mDone;
+    }
+
+    @Override
     public int start(int sampleRateInHz, int audioFormat, int channelCount) {
         if (DBG) {
             Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat
@@ -164,6 +170,7 @@
                 mFile.write(
                         makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
                 closeFile();
+                mDone = true;
                 return TextToSpeech.SUCCESS;
             } catch (IOException ex) {
                 Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
@@ -174,6 +181,14 @@
     }
 
     @Override
+    public void error() {
+        if (DBG) Log.d(TAG, "FileSynthesisRequest.error()");
+        synchronized (mStateLock) {
+            cleanUp();
+        }
+    }
+
+    @Override
     public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
             byte[] buffer, int offset, int length) {
         synchronized (mStateLock) {
@@ -187,9 +202,11 @@
             out = new FileOutputStream(mFileName);
             out.write(makeWavHeader(sampleRateInHz, audioFormat, channelCount, length));
             out.write(buffer, offset, length);
+            mDone = true;
             return TextToSpeech.SUCCESS;
         } catch (IOException ex) {
             Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+            mFileName.delete();
             return TextToSpeech.ERROR;
         } finally {
             try {
diff --git a/core/java/android/speech/tts/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
index 2267015..dc5ff70 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisRequest.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
@@ -50,6 +50,7 @@
     private final Object mStateLock = new Object();
     private AudioTrack mAudioTrack = null;
     private boolean mStopped = false;
+    private boolean mDone = false;
 
     PlaybackSynthesisRequest(String text, int streamType, float volume, float pan) {
         super(text);
@@ -72,7 +73,6 @@
         if (mAudioTrack != null) {
             mAudioTrack.flush();
             mAudioTrack.stop();
-            // TODO: do we need to wait for playback to finish before releasing?
             mAudioTrack.release();
             mAudioTrack = null;
         }
@@ -85,6 +85,11 @@
         return MIN_AUDIO_BUFFER_SIZE;
     }
 
+    @Override
+    boolean isDone() {
+        return mDone;
+    }
+
     // TODO: add a thread that writes to the AudioTrack?
     @Override
     public int start(int sampleRateInHz, int audioFormat, int channelCount) {
@@ -104,8 +109,7 @@
                 return TextToSpeech.ERROR;
             }
 
-            mAudioTrack = createAudioTrack(sampleRateInHz, audioFormat, channelCount,
-                    AudioTrack.MODE_STREAM);
+            mAudioTrack = createStreamingAudioTrack(sampleRateInHz, audioFormat, channelCount);
             if (mAudioTrack == null) {
                 return TextToSpeech.ERROR;
             }
@@ -183,12 +187,21 @@
                 Log.e(TAG, "done(): Not started");
                 return TextToSpeech.ERROR;
             }
+            mDone = true;
             cleanUp();
         }
         return TextToSpeech.SUCCESS;
     }
 
     @Override
+    public void error() {
+        if (DBG) Log.d(TAG, "error()");
+        synchronized (mStateLock) {
+            cleanUp();
+        }
+    }
+
+    @Override
     public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
             byte[] buffer, int offset, int length) {
         if (DBG) {
@@ -208,15 +221,32 @@
                 return TextToSpeech.ERROR;
             }
 
-            mAudioTrack = createAudioTrack(sampleRateInHz, audioFormat, channelCount,
-                    AudioTrack.MODE_STATIC);
+            int channelConfig = getChannelConfig(channelCount);
+            if (channelConfig < 0) {
+                Log.e(TAG, "Unsupported number of channels :" + channelCount);
+                cleanUp();
+                return TextToSpeech.ERROR;
+            }
+            int bytesPerFrame = getBytesPerFrame(audioFormat);
+            if (bytesPerFrame < 0) {
+                Log.e(TAG, "Unsupported audio format :" + audioFormat);
+                cleanUp();
+                return TextToSpeech.ERROR;
+            }
+
+            mAudioTrack = new AudioTrack(mStreamType, sampleRateInHz, channelConfig,
+                    audioFormat, buffer.length, AudioTrack.MODE_STATIC);
             if (mAudioTrack == null) {
                 return TextToSpeech.ERROR;
             }
 
             try {
                 mAudioTrack.write(buffer, offset, length);
+                setupVolume(mAudioTrack, mVolume, mPan);
                 mAudioTrack.play();
+                blockUntilDone(mAudioTrack, bytesPerFrame, length);
+                mDone = true;
+                if (DBG) Log.d(TAG, "Wrote data to audio track succesfully : " + length);
             } catch (IllegalStateException ex) {
                 Log.e(TAG, "Playback error", ex);
                 return TextToSpeech.ERROR;
@@ -228,15 +258,48 @@
         return TextToSpeech.SUCCESS;
     }
 
-    private AudioTrack createAudioTrack(int sampleRateInHz, int audioFormat, int channelCount,
-            int mode) {
-        int channelConfig;
+    private void blockUntilDone(AudioTrack audioTrack, int bytesPerFrame, int length) {
+        int lengthInFrames = length / bytesPerFrame;
+        int currentPosition = 0;
+        while ((currentPosition = audioTrack.getPlaybackHeadPosition()) < lengthInFrames) {
+            long estimatedTimeMs = ((lengthInFrames - currentPosition) * 1000) /
+                    audioTrack.getSampleRate();
+            if (DBG) Log.d(TAG, "About to sleep for : " + estimatedTimeMs + " ms," +
+                    " Playback position : " + currentPosition);
+            try {
+                Thread.sleep(estimatedTimeMs);
+            } catch (InterruptedException ie) {
+                break;
+            }
+        }
+    }
+
+    private int getBytesPerFrame(int audioFormat) {
+        if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
+            return 1;
+        } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
+            return 2;
+        }
+
+        return -1;
+    }
+
+    private int getChannelConfig(int channelCount) {
         if (channelCount == 1) {
-            channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+            return AudioFormat.CHANNEL_OUT_MONO;
         } else if (channelCount == 2){
-            channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
-        } else {
-            Log.e(TAG, "Unsupported number of channels: " + channelCount);
+            return AudioFormat.CHANNEL_OUT_STEREO;
+        }
+
+        return -1;
+    }
+
+    private AudioTrack createStreamingAudioTrack(int sampleRateInHz, int audioFormat,
+            int channelCount) {
+        int channelConfig = getChannelConfig(channelCount);
+
+        if (channelConfig < 0) {
+            Log.e(TAG, "Unsupported number of channels : " + channelCount);
             return null;
         }
 
@@ -244,10 +307,11 @@
                 = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
         int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes);
         AudioTrack audioTrack = new AudioTrack(mStreamType, sampleRateInHz, channelConfig,
-                audioFormat, bufferSizeInBytes, mode);
+                audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
         if (audioTrack == null) {
             return null;
         }
+
         if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
             audioTrack.release();
             return null;
@@ -255,4 +319,4 @@
         setupVolume(audioTrack, mVolume, mPan);
         return audioTrack;
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java
index f4bb852..515218b 100644
--- a/core/java/android/speech/tts/SynthesisRequest.java
+++ b/core/java/android/speech/tts/SynthesisRequest.java
@@ -114,6 +114,11 @@
     public abstract int getMaxBufferSize();
 
     /**
+     * Checks whether the synthesis request completed successfully.
+     */
+    abstract boolean isDone();
+
+    /**
      * Aborts the speech request.
      *
      * Can be called from multiple threads.
@@ -162,6 +167,14 @@
     public abstract int done();
 
     /**
+     * The service should call this method if the speech synthesis fails.
+     *
+     * This method should only be called on the synthesis thread,
+     * while in {@link TextToSpeechService#onSynthesizeText}.
+     */
+    public abstract void error();
+
+    /**
      * The service can call this method instead of using {@link #start}, {@link #audioAvailable}
      * and {@link #done} if all the audio data is available in a single buffer.
      *
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index a408ea2..da97fb4 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -150,12 +150,10 @@
      *
      * Called on the synthesis thread.
      *
-     * @param request The synthesis request. The method should
-     *         call {@link SynthesisRequest#start}, {@link SynthesisRequest#audioAvailable},
-     *         and {@link SynthesisRequest#done} on this request.
-     * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+     * @param request The synthesis request. The method should use the methods in the request
+     *         object to communicate the results of the synthesis.
      */
-    protected abstract int onSynthesizeText(SynthesisRequest request);
+    protected abstract void onSynthesizeText(SynthesisRequest request);
 
     private boolean areDefaultsEnforced() {
         return getSecureSettingInt(Settings.Secure.TTS_USE_DEFAULTS,
@@ -442,7 +440,8 @@
                 synthesisRequest = mSynthesisRequest;
             }
             setRequestParams(synthesisRequest);
-            return TextToSpeechService.this.onSynthesizeText(synthesisRequest);
+            TextToSpeechService.this.onSynthesizeText(synthesisRequest);
+            return synthesisRequest.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
         }
 
         protected SynthesisRequest createSynthesisRequest() {
diff --git a/core/java/android/view/PointerIcon.aidl b/core/java/android/view/PointerIcon.aidl
new file mode 100644
index 0000000..b09340b
--- /dev/null
+++ b/core/java/android/view/PointerIcon.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+parcelable PointerIcon;
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
new file mode 100644
index 0000000..bb7ed41
--- /dev/null
+++ b/core/java/android/view/PointerIcon.java
@@ -0,0 +1,435 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+import com.android.internal.util.XmlUtils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * <p>
+ * Pointer icons can be provided either by the system using system styles,
+ * or by applications using bitmaps or application resources.
+ * </p>
+ *
+ * @hide
+ */
+public final class PointerIcon implements Parcelable {
+    private static final String TAG = "PointerIcon";
+
+    /** Style constant: Custom icon with a user-supplied bitmap. */
+    public static final int STYLE_CUSTOM = -1;
+
+    /** Style constant: Null icon.  It has no bitmap. */
+    public static final int STYLE_NULL = 0;
+
+    /** Style constant: Arrow icon.  (Default mouse pointer) */
+    public static final int STYLE_ARROW = 1000;
+
+    /** {@hide} Style constant: Spot hover icon for touchpads. */
+    public static final int STYLE_SPOT_HOVER = 2000;
+
+    /** {@hide} Style constant: Spot touch icon for touchpads. */
+    public static final int STYLE_SPOT_TOUCH = 2001;
+
+    /** {@hide} Style constant: Spot anchor icon for touchpads. */
+    public static final int STYLE_SPOT_ANCHOR = 2002;
+
+    // OEM private styles should be defined starting at this range to avoid
+    // conflicts with any system styles that may be defined in the future.
+    private static final int STYLE_OEM_FIRST = 10000;
+
+    // The default pointer icon.
+    private static final int STYLE_DEFAULT = STYLE_ARROW;
+
+    private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL);
+
+    private final int mStyle;
+    private int mSystemIconResourceId;
+    private Bitmap mBitmap;
+    private float mHotSpotX;
+    private float mHotSpotY;
+
+    private PointerIcon(int style) {
+        mStyle = style;
+    }
+
+    /**
+     * Gets a special pointer icon that has no bitmap.
+     *
+     * @return The null pointer icon.
+     *
+     * @see #STYLE_NULL
+     */
+    public static PointerIcon getNullIcon() {
+        return gNullIcon;
+    }
+
+    /**
+     * Gets the default pointer icon.
+     *
+     * @param context The context.
+     * @return The default pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     */
+    public static PointerIcon getDefaultIcon(Context context) {
+        return getSystemIcon(context, STYLE_DEFAULT);
+    }
+
+    /**
+     * Gets a system pointer icon for the given style.
+     * If style is not recognized, returns the default pointer icon.
+     *
+     * @param context The context.
+     * @param style The pointer icon style.
+     * @return The pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     */
+    public static PointerIcon getSystemIcon(Context context, int style) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        if (style == STYLE_NULL) {
+            return gNullIcon;
+        }
+
+        int styleIndex = getSystemIconStyleIndex(style);
+        if (styleIndex == 0) {
+            styleIndex = getSystemIconStyleIndex(STYLE_DEFAULT);
+        }
+
+        TypedArray a = context.obtainStyledAttributes(null,
+                com.android.internal.R.styleable.Pointer,
+                com.android.internal.R.attr.pointerStyle, 0);
+        int resourceId = a.getResourceId(styleIndex, -1);
+        a.recycle();
+
+        if (resourceId == -1) {
+            Log.w(TAG, "Missing theme resources for pointer icon style " + style);
+            return style == STYLE_DEFAULT ? gNullIcon : getSystemIcon(context, STYLE_DEFAULT);
+        }
+
+        PointerIcon icon = new PointerIcon(style);
+        if ((resourceId & 0xff000000) == 0x01000000) {
+            icon.mSystemIconResourceId = resourceId;
+        } else {
+            icon.loadResource(context.getResources(), resourceId);
+        }
+        return icon;
+    }
+
+    /**
+     * Creates a custom pointer from the given bitmap and hotspot information.
+     *
+     * @param bitmap The bitmap for the icon.
+     * @param hotspotX The X offset of the pointer icon hotspot in the bitmap.
+     *        Must be within the [0, bitmap.getWidth()) range.
+     * @param hotspotY The Y offset of the pointer icon hotspot in the bitmap.
+     *        Must be within the [0, bitmap.getHeight()) range.
+     * @return A pointer icon for this bitmap.
+     *
+     * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+     *         parameters are invalid.
+     */
+    public static PointerIcon createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+        if (bitmap == null) {
+            throw new IllegalArgumentException("bitmap must not be null");
+        }
+        validateHotSpot(bitmap, hotSpotX, hotSpotY);
+
+        PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+        icon.mBitmap = bitmap;
+        icon.mHotSpotX = hotSpotX;
+        icon.mHotSpotY = hotSpotY;
+        return icon;
+    }
+
+    /**
+     * Loads a custom pointer icon from an XML resource.
+     * <p>
+     * The XML resource should have the following form:
+     * <code>
+     * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+     * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+     *   android:bitmap="@drawable/my_pointer_bitmap"
+     *   android:hotSpotX="24"
+     *   android:hotSpotY="24" /&gt;
+     * </code>
+     * </p>
+     *
+     * @param resources The resources object.
+     * @param resourceId The resource id.
+     * @return The pointer icon.
+     *
+     * @throws IllegalArgumentException if resources is null.
+     * @throws Resources.NotFoundException if the resource was not found or the drawable
+     * linked in the resource was not found.
+     */
+    public static PointerIcon loadCustomIcon(Resources resources, int resourceId) {
+        if (resources == null) {
+            throw new IllegalArgumentException("resources must not be null");
+        }
+
+        PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+        icon.loadResource(resources, resourceId);
+        return icon;
+    }
+
+    /**
+     * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
+     * Returns a pointer icon (not necessarily the same instance) with the information filled in.
+     *
+     * @param context The context.
+     * @return The loaded pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     * @see #isLoaded()
+     * @hide
+     */
+    public PointerIcon load(Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        if (mSystemIconResourceId == 0 || mBitmap != null) {
+            return this;
+        }
+
+        PointerIcon result = new PointerIcon(mStyle);
+        result.mSystemIconResourceId = mSystemIconResourceId;
+        result.loadResource(context.getResources(), mSystemIconResourceId);
+        return result;
+    }
+
+    /**
+     * Returns true if the pointer icon style is {@link #STYLE_NULL}.
+     *
+     * @return True if the pointer icon style is {@link #STYLE_NULL}.
+     */
+    public boolean isNullIcon() {
+        return mStyle == STYLE_NULL;
+    }
+
+    /**
+     * Returns true if the pointer icon has been loaded and its bitmap and hotspot
+     * information are available.
+     *
+     * @return True if the pointer icon is loaded.
+     * @see #load(Context)
+     */
+    public boolean isLoaded() {
+        return mBitmap != null || mStyle == STYLE_NULL;
+    }
+
+    /**
+     * Gets the style of the pointer icon.
+     *
+     * @return The pointer icon style.
+     */
+    public int getStyle() {
+        return mStyle;
+    }
+
+    /**
+     * Gets the bitmap of the pointer icon.
+     *
+     * @return The pointer icon bitmap, or null if the style is {@link #STYLE_NULL}.
+     *
+     * @throws IllegalStateException if the bitmap is not loaded.
+     * @see #isLoaded()
+     * @see #load(Context)
+     */
+    public Bitmap getBitmap() {
+        throwIfIconIsNotLoaded();
+        return mBitmap;
+    }
+
+    /**
+     * Gets the X offset of the pointer icon hotspot.
+     *
+     * @return The hotspot X offset.
+     *
+     * @throws IllegalStateException if the bitmap is not loaded.
+     * @see #isLoaded()
+     * @see #load(Context)
+     */
+    public float getHotSpotX() {
+        throwIfIconIsNotLoaded();
+        return mHotSpotX;
+    }
+
+    /**
+     * Gets the Y offset of the pointer icon hotspot.
+     *
+     * @return The hotspot Y offset.
+     *
+     * @throws IllegalStateException if the bitmap is not loaded.
+     * @see #isLoaded()
+     * @see #load(Context)
+     */
+    public float getHotSpotY() {
+        throwIfIconIsNotLoaded();
+        return mHotSpotY;
+    }
+
+    private void throwIfIconIsNotLoaded() {
+        if (!isLoaded()) {
+            throw new IllegalStateException("The icon is not loaded.");
+        }
+    }
+
+    public static final Parcelable.Creator<PointerIcon> CREATOR
+            = new Parcelable.Creator<PointerIcon>() {
+        public PointerIcon createFromParcel(Parcel in) {
+            int style = in.readInt();
+            if (style == STYLE_NULL) {
+                return getNullIcon();
+            }
+
+            int systemIconResourceId = in.readInt();
+            if (systemIconResourceId != 0) {
+                PointerIcon icon = new PointerIcon(style);
+                icon.mSystemIconResourceId = systemIconResourceId;
+                return icon;
+            }
+
+            Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
+            float hotSpotX = in.readFloat();
+            float hotSpotY = in.readFloat();
+            return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+        }
+
+        public PointerIcon[] newArray(int size) {
+            return new PointerIcon[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mStyle);
+
+        if (mStyle != STYLE_NULL) {
+            out.writeInt(mSystemIconResourceId);
+            if (mSystemIconResourceId == 0) {
+                mBitmap.writeToParcel(out, flags);
+                out.writeFloat(mHotSpotX);
+                out.writeFloat(mHotSpotY);
+            }
+        }
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null || !(other instanceof PointerIcon)) {
+            return false;
+        }
+
+        PointerIcon otherIcon = (PointerIcon) other;
+        if (mStyle != otherIcon.mStyle
+                || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
+            return false;
+        }
+
+        if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
+                || mHotSpotX != otherIcon.mHotSpotX
+                || mHotSpotY != otherIcon.mHotSpotY)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void loadResource(Resources resources, int resourceId) {
+        XmlResourceParser parser = resources.getXml(resourceId);
+        final int bitmapRes;
+        final float hotSpotX;
+        final float hotSpotY;
+        try {
+            XmlUtils.beginDocument(parser, "pointer-icon");
+
+            TypedArray a = resources.obtainAttributes(
+                    parser, com.android.internal.R.styleable.PointerIcon);
+            bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
+            hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+            hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+            a.recycle();
+        } catch (Exception ex) {
+            throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
+        } finally {
+            parser.close();
+        }
+
+        if (bitmapRes == 0) {
+            throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
+        }
+
+        Drawable drawable = resources.getDrawable(bitmapRes);
+        if (!(drawable instanceof BitmapDrawable)) {
+            throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
+                    + "refer to a bitmap drawable.");
+        }
+
+        // Set the properties now that we have successfully loaded the icon.
+        mBitmap = ((BitmapDrawable)drawable).getBitmap();
+        mHotSpotX = hotSpotX;
+        mHotSpotY = hotSpotY;
+    }
+
+    private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+        if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
+            throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
+        }
+        if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
+            throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
+        }
+    }
+
+    private static int getSystemIconStyleIndex(int style) {
+        switch (style) {
+            case STYLE_ARROW:
+                return com.android.internal.R.styleable.Pointer_pointerIconArrow;
+            case STYLE_SPOT_HOVER:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
+            case STYLE_SPOT_TOUCH:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
+            case STYLE_SPOT_ANCHOR:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
+            default:
+                return 0;
+        }
+    }
+}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index af954c9..9d29a60 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -251,7 +251,7 @@
      */
     public void addHeaderView(View v, Object data, boolean isSelectable) {
 
-        if (mAdapter != null) {
+        if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
             throw new IllegalStateException(
                     "Cannot add header view to list -- setAdapter has already been called.");
         }
@@ -261,6 +261,12 @@
         info.data = data;
         info.isSelectable = isSelectable;
         mHeaderViewInfos.add(info);
+
+        // in the case of re-adding a header view, or adding one later on,
+        // we need to notify the observer
+        if (mDataSetObserver != null) {
+            mDataSetObserver.onChanged();
+        }
     }
 
     /**
@@ -294,7 +300,9 @@
         if (mHeaderViewInfos.size() > 0) {
             boolean result = false;
             if (((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
-                mDataSetObserver.onChanged();
+                if (mDataSetObserver != null) {
+                    mDataSetObserver.onChanged();
+                }
                 result = true;
             }
             removeFixedViewInfo(v, mHeaderViewInfos);
@@ -328,6 +336,12 @@
      * @param isSelectable true if the footer view can be selected
      */
     public void addFooterView(View v, Object data, boolean isSelectable) {
+
+        if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
+            throw new IllegalStateException(
+                    "Cannot add footer view to list -- setAdapter has already been called.");
+        }
+
         FixedViewInfo info = new FixedViewInfo();
         info.view = v;
         info.data = data;
@@ -371,7 +385,9 @@
         if (mFooterViewInfos.size() > 0) {
             boolean result = false;
             if (((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
-                mDataSetObserver.onChanged();
+                if (mDataSetObserver != null) {
+                    mDataSetObserver.onChanged();
+                }
                 result = true;
             }
             removeFixedViewInfo(v, mFooterViewInfos);
diff --git a/core/java/com/android/internal/util/HierarchicalState.java b/core/java/com/android/internal/util/IState.java
similarity index 61%
rename from core/java/com/android/internal/util/HierarchicalState.java
rename to core/java/com/android/internal/util/IState.java
index b37f46c..056f8e9 100644
--- a/core/java/com/android/internal/util/HierarchicalState.java
+++ b/core/java/com/android/internal/util/IState.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -21,21 +21,29 @@
 /**
  * {@hide}
  *
- * The class for implementing states in a HierarchicalStateMachine
+ * The interface for implementing states in a {@link StateMachine}
  */
-public class HierarchicalState {
+public interface IState {
 
     /**
-     * Constructor
+     * Returned by processMessage to indicate the the message was processed.
      */
-    protected HierarchicalState() {
-    }
+    static final boolean HANDLED = true;
+
+    /**
+     * Returned by processMessage to indicate the the message was NOT processed.
+     */
+    static final boolean NOT_HANDLED = false;
 
     /**
      * Called when a state is entered.
      */
-    protected void enter() {
-    }
+    void enter();
+
+    /**
+     * Called when a state is exited.
+     */
+    void exit();
 
     /**
      * Called when a message is to be processed by the
@@ -49,28 +57,15 @@
      * be processed until this routine returns.
      *
      * @param msg to process
-     * @return true if processing has completed and false
-     *         if the parent state's processMessage should
-     *         be invoked.
+     * @return HANDLED if processing has completed and NOT_HANDLED
+     *         if the message wasn't processed.
      */
-    protected boolean processMessage(Message msg) {
-        return false;
-    }
+    boolean processMessage(Message msg);
 
     /**
-     * Called when a state is exited.
+     * Name of State for debugging purposes.
+     *
+     * @return name of state.
      */
-    protected void exit() {
-    }
-
-    /**
-     * @return name of state, but default returns the states
-     * class name. An instance name would be better but requiring
-     * it seems unnecessary.
-     */
-    public String getName() {
-        String name = getClass().getName();
-        int lastDollar = name.lastIndexOf('$');
-        return name.substring(lastDollar + 1);
-    }
+    String getName();
 }
diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java
new file mode 100644
index 0000000..3eadff5
--- /dev/null
+++ b/core/java/com/android/internal/util/State.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.os.Message;
+
+/**
+ * {@hide}
+ *
+ * The class for implementing states in a StateMachine
+ */
+public class State implements IState {
+
+    /**
+     * Constructor
+     */
+    protected State() {
+    }
+
+    /* (non-Javadoc)
+     * @see com.android.internal.util.IState#enter()
+     */
+    @Override
+    public void enter() {
+    }
+
+    /* (non-Javadoc)
+     * @see com.android.internal.util.IState#exit()
+     */
+    @Override
+    public void exit() {
+    }
+
+    /* (non-Javadoc)
+     * @see com.android.internal.util.IState#processMessage(android.os.Message)
+     */
+    @Override
+    public boolean processMessage(Message msg) {
+        return false;
+    }
+
+    /**
+     * Name of State for debugging purposes.
+     *
+     * This default implementation returns the class name, returning
+     * the instance name would better in cases where a State class
+     * is used for multiple states. But normally there is one class per
+     * state and the class name is sufficient and easy to get. You may
+     * want to provide a setName or some other mechanism for setting
+     * another name if the class name is not appropriate.
+     *
+     * @see com.android.internal.util.IState#processMessage(android.os.Message)
+     */
+    @Override
+    public String getName() {
+        String name = getClass().getName();
+        int lastDollar = name.lastIndexOf('$');
+        return name.substring(lastDollar + 1);
+    }
+}
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/StateMachine.java
similarity index 85%
rename from core/java/com/android/internal/util/HierarchicalStateMachine.java
rename to core/java/com/android/internal/util/StateMachine.java
index f6aa184..cbe72dd 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -29,10 +29,10 @@
 /**
  * {@hide}
  *
- * <p>A hierarchical state machine is a state machine which processes messages
+ * <p>The state machine defined here is a hierarchical state machine which processes messages
  * and can have states arranged hierarchically.</p>
  * 
- * <p>A state is a <code>HierarchicalState</code> object and must implement
+ * <p>A state is a <code>State</code> object and must implement
  * <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
  * The enter/exit methods are equivalent to the construction and destruction
  * in Object Oriented programming and are used to perform initialization and
@@ -76,7 +76,7 @@
  * will exit the current state and its parent and then exit from the controlling thread
  * and no further messages will be processed.</p>
  *
- * <p>In addition to <code>processMessage</code> each <code>HierarchicalState</code> has
+ * <p>In addition to <code>processMessage</code> each <code>State</code> has
  * an <code>enter</code> method and <code>exit</exit> method which may be overridden.</p>
  *
  * <p>Since the states are arranged in a hierarchy transitioning to a new state
@@ -122,11 +122,11 @@
  * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
  * when the next message is received mS4.processMessage will be invoked.</p>
  *
- * <p>Now for some concrete examples, here is the canonical HelloWorld as an HSM.
+ * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine.
  * It responds with "Hello World" being printed to the log for every message.</p>
 <code>
-class HelloWorld extends HierarchicalStateMachine {
-    Hsm1(String name) {
+class HelloWorld extends StateMachine {
+    HelloWorld(String name) {
         super(name);
         addState(mState1);
         setInitialState(mState1);
@@ -138,7 +138,7 @@
         return hw;
     }
 
-    class State1 extends HierarchicalState {
+    class State1 extends State {
         &#64;Override public boolean processMessage(Message message) {
             Log.d(TAG, "Hello World");
             return HANDLED;
@@ -221,9 +221,9 @@
      }
 }
 </code>
- * <p>The implementation is below and also in HierarchicalStateMachineTest:</p>
+ * <p>The implementation is below and also in StateMachineTest:</p>
 <code>
-class Hsm1 extends HierarchicalStateMachine {
+class Hsm1 extends StateMachine {
     private static final String TAG = "hsm1";
 
     public static final int CMD_1 = 1;
@@ -255,7 +255,7 @@
         Log.d(TAG, "ctor X");
     }
 
-    class P1 extends HierarchicalState {
+    class P1 extends State {
         &#64;Override public void enter() {
             Log.d(TAG, "mP1.enter");
         }
@@ -282,7 +282,7 @@
         }
     }
 
-    class S1 extends HierarchicalState {
+    class S1 extends State {
         &#64;Override public void enter() {
             Log.d(TAG, "mS1.enter");
         }
@@ -302,7 +302,7 @@
         }
     }
 
-    class S2 extends HierarchicalState {
+    class S2 extends State {
         &#64;Override public void enter() {
             Log.d(TAG, "mS2.enter");
         }
@@ -330,7 +330,7 @@
         }
     }
 
-    class P2 extends HierarchicalState {
+    class P2 extends State {
         &#64;Override public void enter() {
             Log.d(TAG, "mP2.enter");
             sendMessage(obtainMessage(CMD_5));
@@ -409,16 +409,16 @@
 D/hsm1    ( 1999): halting
 </code>
  */
-public class HierarchicalStateMachine {
+public class StateMachine {
 
-    private static final String TAG = "HierarchicalStateMachine";
+    private static final String TAG = "StateMachine";
     private String mName;
 
     /** Message.what value when quitting */
-    public static final int HSM_QUIT_CMD = -1;
+    public static final int SM_QUIT_CMD = -1;
 
     /** Message.what value when initializing */
-    public static final int HSM_INIT_CMD = -1;
+    public static final int SM_INIT_CMD = -1;
 
     /**
      * Convenience constant that maybe returned by processMessage
@@ -441,8 +441,8 @@
      */
     public static class ProcessedMessageInfo {
         private int what;
-        private HierarchicalState state;
-        private HierarchicalState orgState;
+        private State state;
+        private State orgState;
 
         /**
          * Constructor
@@ -451,7 +451,7 @@
          * @param orgState is the first state the received the message but
          * did not processes the message.
          */
-        ProcessedMessageInfo(Message message, HierarchicalState state, HierarchicalState orgState) {
+        ProcessedMessageInfo(Message message, State state, State orgState) {
             update(message, state, orgState);
         }
 
@@ -461,7 +461,7 @@
          * @param orgState is the first state the received the message but
          * did not processes the message.
          */
-        public void update(Message message, HierarchicalState state, HierarchicalState orgState) {
+        public void update(Message message, State state, State orgState) {
             this.what = message.what;
             this.state = state;
             this.orgState = orgState;
@@ -477,14 +477,14 @@
         /**
          * @return the state that handled this message
          */
-        public HierarchicalState getState() {
+        public State getState() {
             return state;
         }
 
         /**
          * @return the original state that received the message.
          */
-        public HierarchicalState getOriginalState() {
+        public State getOriginalState() {
             return orgState;
         }
 
@@ -593,7 +593,7 @@
          * @param orgState is the first state the received the message but
          * did not processes the message.
          */
-        void add(Message message, HierarchicalState state, HierarchicalState orgState) {
+        void add(Message message, State state, State orgState) {
             mCount += 1;
             if (mMessages.size() < mMaxSize) {
                 mMessages.add(new ProcessedMessageInfo(message, state, orgState));
@@ -608,7 +608,7 @@
         }
     }
 
-    private static class HsmHandler extends Handler {
+    private static class SmHandler extends Handler {
 
         /** The debug flag */
         private boolean mDbg = false;
@@ -643,8 +643,8 @@
         /** State used when state machine is quitting */
         private QuittingState mQuittingState = new QuittingState();
 
-        /** Reference to the HierarchicalStateMachine */
-        private HierarchicalStateMachine mHsm;
+        /** Reference to the StateMachine */
+        private StateMachine mSm;
 
         /**
          * Information about a state.
@@ -652,7 +652,7 @@
          */
         private class StateInfo {
             /** The state */
-            HierarchicalState state;
+            State state;
 
             /** The parent of this state, null if there is no parent */
             StateInfo parentStateInfo;
@@ -672,14 +672,14 @@
         }
 
         /** The map of all of the states in the state machine */
-        private HashMap<HierarchicalState, StateInfo> mStateInfo =
-            new HashMap<HierarchicalState, StateInfo>();
+        private HashMap<State, StateInfo> mStateInfo =
+            new HashMap<State, StateInfo>();
 
         /** The initial state that will process the first message */
-        private HierarchicalState mInitialState;
+        private State mInitialState;
 
         /** The destination state when transitionTo has been invoked */
-        private HierarchicalState mDestState;
+        private State mDestState;
 
         /** The list of deferred messages */
         private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
@@ -687,10 +687,10 @@
         /**
          * State entered when transitionToHaltingState is called.
          */
-        private class HaltingState extends HierarchicalState {
+        private class HaltingState extends State {
             @Override
             public boolean processMessage(Message msg) {
-                mHsm.haltedProcessMessage(msg);
+                mSm.haltedProcessMessage(msg);
                 return true;
             }
         }
@@ -698,7 +698,7 @@
         /**
          * State entered when a valid quit message is handled.
          */
-        private class QuittingState extends HierarchicalState {
+        private class QuittingState extends State {
             @Override
             public boolean processMessage(Message msg) {
                 return NOT_HANDLED;
@@ -745,7 +745,7 @@
              * the appropriate states. We loop on this to allow
              * enter and exit methods to use transitionTo.
              */
-            HierarchicalState destState = null;
+            State destState = null;
             while (mDestState != null) {
                 if (mDbg) Log.d(TAG, "handleMessage: new destination call exit");
 
@@ -785,11 +785,11 @@
                     /**
                      * We are quitting so ignore all messages.
                      */
-                    mHsm.quitting();
-                    if (mHsm.mHsmThread != null) {
+                    mSm.quitting();
+                    if (mSm.mSmThread != null) {
                         // If we made the thread then quit looper which stops the thread.
                         getLooper().quit();
-                        mHsm.mHsmThread = null;
+                        mSm.mSmThread = null;
                     }
                 } else if (destState == mHaltingState) {
                     /**
@@ -797,7 +797,7 @@
                      * state. All subsequent messages will be processed in
                      * in the halting state which invokes haltedProcessMessage(msg);
                      */
-                    mHsm.halting();
+                    mSm.halting();
                 }
             }
         }
@@ -833,7 +833,7 @@
              * starting at the first entry.
              */
             mIsConstructionCompleted = true;
-            mMsg = obtainMessage(HSM_INIT_CMD);
+            mMsg = obtainMessage(SM_INIT_CMD);
             invokeEnterMethods(0);
 
             /**
@@ -863,7 +863,7 @@
                     /**
                      * No parents left so it's not handled
                      */
-                    mHsm.unhandledMessage(msg);
+                    mSm.unhandledMessage(msg);
                     if (isQuit(msg)) {
                         transitionTo(mQuittingState);
                     }
@@ -878,7 +878,7 @@
              * Record that we processed the message
              */
             if (curStateInfo != null) {
-                HierarchicalState orgState = mStateStack[mStateStackTopIndex].state;
+                State orgState = mStateStack[mStateStackTopIndex].state;
                 mProcessedMessages.add(msg, curStateInfo.state, orgState);
             } else {
                 mProcessedMessages.add(msg, null, null);
@@ -892,7 +892,7 @@
         private final void invokeExitMethods(StateInfo commonStateInfo) {
             while ((mStateStackTopIndex >= 0) &&
                     (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
-                HierarchicalState curState = mStateStack[mStateStackTopIndex].state;
+                State curState = mStateStack[mStateStackTopIndex].state;
                 if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
                 curState.exit();
                 mStateStack[mStateStackTopIndex].active = false;
@@ -934,7 +934,7 @@
          * reversing the order of the items on the temporary stack as
          * they are moved.
          *
-         * @return index into mStateState where entering needs to start
+         * @return index into mStateStack where entering needs to start
          */
         private final int moveTempStateStackToStateStack() {
             int startingIndex = mStateStackTopIndex + 1;
@@ -967,7 +967,7 @@
          * @return StateInfo of the common ancestor for the destState and
          * current state or null if there is no common parent.
          */
-        private final StateInfo setupTempStateStackWithStatesToEnter(HierarchicalState destState) {
+        private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
             /**
              * Search up the parent list of the destination state for an active
              * state. Use a do while() loop as the destState must always be entered
@@ -1019,7 +1019,7 @@
         /**
          * @return current state
          */
-        private final HierarchicalState getCurrentState() {
+        private final IState getCurrentState() {
             return mStateStack[mStateStackTopIndex].state;
         }
 
@@ -1032,7 +1032,7 @@
          * @param parent the parent of state
          * @return stateInfo for this state
          */
-        private final StateInfo addState(HierarchicalState state, HierarchicalState parent) {
+        private final StateInfo addState(State state, State parent) {
             if (mDbg) {
                 Log.d(TAG, "addStateInternal: E state=" + state.getName()
                         + ",parent=" + ((parent == null) ? "" : parent.getName()));
@@ -1067,29 +1067,29 @@
          * Constructor
          *
          * @param looper for dispatching messages
-         * @param hsm the hierarchical state machine
+         * @param sm the hierarchical state machine
          */
-        private HsmHandler(Looper looper, HierarchicalStateMachine hsm) {
+        private SmHandler(Looper looper, StateMachine sm) {
             super(looper);
-            mHsm = hsm;
+            mSm = sm;
 
             addState(mHaltingState, null);
             addState(mQuittingState, null);
         }
 
-        /** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */
-        private final void setInitialState(HierarchicalState initialState) {
+        /** @see StateMachine#setInitialState(State) */
+        private final void setInitialState(State initialState) {
             if (mDbg) Log.d(TAG, "setInitialState: initialState" + initialState.getName());
             mInitialState = initialState;
         }
 
-        /** @see HierarchicalStateMachine#transitionTo(HierarchicalState) */
-        private final void transitionTo(HierarchicalState destState) {
-            if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + destState.getName());
-            mDestState = destState;
+        /** @see StateMachine#transitionTo(IState) */
+        private final void transitionTo(IState destState) {
+            mDestState = (State) destState;
+            if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + mDestState.getName());
         }
 
-        /** @see HierarchicalStateMachine#deferMessage(Message) */
+        /** @see StateMachine#deferMessage(Message) */
         private final void deferMessage(Message msg) {
             if (mDbg) Log.d(TAG, "deferMessage: msg=" + msg.what);
 
@@ -1100,51 +1100,51 @@
             mDeferredMessages.add(newMsg);
         }
 
-        /** @see HierarchicalStateMachine#deferMessage(Message) */
+        /** @see StateMachine#deferMessage(Message) */
         private final void quit() {
             if (mDbg) Log.d(TAG, "quit:");
-            sendMessage(obtainMessage(HSM_QUIT_CMD, mQuitObj));
+            sendMessage(obtainMessage(SM_QUIT_CMD, mQuitObj));
         }
 
-        /** @see HierarchicalStateMachine#isQuit(Message) */
+        /** @see StateMachine#isQuit(Message) */
         private final boolean isQuit(Message msg) {
-            return (msg.what == HSM_QUIT_CMD) && (msg.obj == mQuitObj);
+            return (msg.what == SM_QUIT_CMD) && (msg.obj == mQuitObj);
         }
 
-        /** @see HierarchicalStateMachine#isDbg() */
+        /** @see StateMachine#isDbg() */
         private final boolean isDbg() {
             return mDbg;
         }
 
-        /** @see HierarchicalStateMachine#setDbg(boolean) */
+        /** @see StateMachine#setDbg(boolean) */
         private final void setDbg(boolean dbg) {
             mDbg = dbg;
         }
 
-        /** @see HierarchicalStateMachine#setProcessedMessagesSize(int) */
+        /** @see StateMachine#setProcessedMessagesSize(int) */
         private final void setProcessedMessagesSize(int maxSize) {
             mProcessedMessages.setSize(maxSize);
         }
 
-        /** @see HierarchicalStateMachine#getProcessedMessagesSize() */
+        /** @see StateMachine#getProcessedMessagesSize() */
         private final int getProcessedMessagesSize() {
             return mProcessedMessages.size();
         }
 
-        /** @see HierarchicalStateMachine#getProcessedMessagesCount() */
+        /** @see StateMachine#getProcessedMessagesCount() */
         private final int getProcessedMessagesCount() {
             return mProcessedMessages.count();
         }
 
-        /** @see HierarchicalStateMachine#getProcessedMessageInfo(int) */
+        /** @see StateMachine#getProcessedMessageInfo(int) */
         private final ProcessedMessageInfo getProcessedMessageInfo(int index) {
             return mProcessedMessages.get(index);
         }
 
     }
 
-    private HsmHandler mHsmHandler;
-    private HandlerThread mHsmThread;
+    private SmHandler mSmHandler;
+    private HandlerThread mSmThread;
 
     /**
      * Initialize.
@@ -1154,28 +1154,28 @@
      */
     private void initStateMachine(String name, Looper looper) {
         mName = name;
-        mHsmHandler = new HsmHandler(looper, this);
+        mSmHandler = new SmHandler(looper, this);
     }
 
     /**
-     * Constructor creates an HSM with its own thread.
+     * Constructor creates a StateMachine with its own thread.
      *
      * @param name of the state machine
      */
-    protected HierarchicalStateMachine(String name) {
-        mHsmThread = new HandlerThread(name);
-        mHsmThread.start();
-        Looper looper = mHsmThread.getLooper();
+    protected StateMachine(String name) {
+        mSmThread = new HandlerThread(name);
+        mSmThread.start();
+        Looper looper = mSmThread.getLooper();
 
         initStateMachine(name, looper);
     }
 
     /**
-     * Constructor creates an HSMStateMachine using the looper.
+     * Constructor creates an StateMachine using the looper.
      *
      * @param name of the state machine
      */
-    protected HierarchicalStateMachine(String name, Looper looper) {
+    protected StateMachine(String name, Looper looper) {
         initStateMachine(name, looper);
     }
 
@@ -1184,30 +1184,30 @@
      * @param state the state to add
      * @param parent the parent of state
      */
-    protected final void addState(HierarchicalState state, HierarchicalState parent) {
-        mHsmHandler.addState(state, parent);
+    protected final void addState(State state, State parent) {
+        mSmHandler.addState(state, parent);
     }
 
     /**
      * @return current message
      */
     protected final Message getCurrentMessage() {
-        return mHsmHandler.getCurrentMessage();
+        return mSmHandler.getCurrentMessage();
     }
 
     /**
      * @return current state
      */
-    protected final HierarchicalState getCurrentState() {
-        return mHsmHandler.getCurrentState();
+    protected final IState getCurrentState() {
+        return mSmHandler.getCurrentState();
     }
 
     /**
      * Add a new state to the state machine, parent will be null
      * @param state to add
      */
-    protected final void addState(HierarchicalState state) {
-        mHsmHandler.addState(state, null);
+    protected final void addState(State state) {
+        mSmHandler.addState(state, null);
     }
 
     /**
@@ -1216,8 +1216,8 @@
      *
      * @param initialState is the state which will receive the first message.
      */
-    protected final void setInitialState(HierarchicalState initialState) {
-        mHsmHandler.setInitialState(initialState);
+    protected final void setInitialState(State initialState) {
+        mSmHandler.setInitialState(initialState);
     }
 
     /**
@@ -1228,8 +1228,8 @@
      *
      * @param destState will be the state that receives the next message.
      */
-    protected final void transitionTo(HierarchicalState destState) {
-        mHsmHandler.transitionTo(destState);
+    protected final void transitionTo(IState destState) {
+        mSmHandler.transitionTo(destState);
     }
 
     /**
@@ -1240,7 +1240,7 @@
      * will be called.
      */
     protected final void transitionToHaltingState() {
-        mHsmHandler.transitionTo(mHsmHandler.mHaltingState);
+        mSmHandler.transitionTo(mSmHandler.mHaltingState);
     }
 
     /**
@@ -1253,7 +1253,7 @@
      * @param msg is deferred until the next transition.
      */
     protected final void deferMessage(Message msg) {
-        mHsmHandler.deferMessage(msg);
+        mSmHandler.deferMessage(msg);
     }
 
 
@@ -1263,7 +1263,7 @@
      * @param msg that couldn't be handled.
      */
     protected void unhandledMessage(Message msg) {
-        if (mHsmHandler.mDbg) Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
+        if (mSmHandler.mDbg) Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
     }
 
     /**
@@ -1276,15 +1276,15 @@
     /**
      * This will be called once after handling a message that called
      * transitionToHalting. All subsequent messages will invoke
-     * {@link HierarchicalStateMachine#haltedProcessMessage(Message)}
+     * {@link StateMachine#haltedProcessMessage(Message)}
      */
     protected void halting() {
     }
 
     /**
      * This will be called once after a quit message that was NOT handled by
-     * the derived HSM. The HSM will stop and any subsequent messages will be
-     * ignored. In addition, if this HSM created the thread, the thread will
+     * the derived StateMachine. The StateMachine will stop and any subsequent messages will be
+     * ignored. In addition, if this StateMachine created the thread, the thread will
      * be stopped after this method returns.
      */
     protected void quitting() {
@@ -1303,35 +1303,35 @@
      * @param maxSize number of messages to maintain at anyone time.
      */
     public final void setProcessedMessagesSize(int maxSize) {
-        mHsmHandler.setProcessedMessagesSize(maxSize);
+        mSmHandler.setProcessedMessagesSize(maxSize);
     }
 
     /**
      * @return number of messages processed
      */
     public final int getProcessedMessagesSize() {
-        return mHsmHandler.getProcessedMessagesSize();
+        return mSmHandler.getProcessedMessagesSize();
     }
 
     /**
      * @return the total number of messages processed
      */
     public final int getProcessedMessagesCount() {
-        return mHsmHandler.getProcessedMessagesCount();
+        return mSmHandler.getProcessedMessagesCount();
     }
 
     /**
      * @return a processed message information
      */
     public final ProcessedMessageInfo getProcessedMessageInfo(int index) {
-        return mHsmHandler.getProcessedMessageInfo(index);
+        return mSmHandler.getProcessedMessageInfo(index);
     }
 
     /**
      * @return Handler
      */
     public final Handler getHandler() {
-        return mHsmHandler;
+        return mSmHandler;
     }
 
     /**
@@ -1341,7 +1341,7 @@
      */
     public final Message obtainMessage()
     {
-        return Message.obtain(mHsmHandler);
+        return Message.obtain(mSmHandler);
     }
 
     /**
@@ -1351,7 +1351,7 @@
      * @return message
      */
     public final Message obtainMessage(int what) {
-        return Message.obtain(mHsmHandler, what);
+        return Message.obtain(mSmHandler, what);
     }
 
     /**
@@ -1364,7 +1364,7 @@
      */
     public final Message obtainMessage(int what, Object obj)
     {
-        return Message.obtain(mHsmHandler, what, obj);
+        return Message.obtain(mSmHandler, what, obj);
     }
 
     /**
@@ -1378,7 +1378,7 @@
      */
     public final Message obtainMessage(int what, int arg1, int arg2)
     {
-        return Message.obtain(mHsmHandler, what, arg1, arg2);
+        return Message.obtain(mSmHandler, what, arg1, arg2);
     }
 
     /**
@@ -1393,107 +1393,107 @@
      */
     public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
     {
-        return Message.obtain(mHsmHandler, what, arg1, arg2, obj);
+        return Message.obtain(mSmHandler, what, arg1, arg2, obj);
     }
 
     /**
      * Enqueue a message to this state machine.
      */
     public final void sendMessage(int what) {
-        mHsmHandler.sendMessage(obtainMessage(what));
+        mSmHandler.sendMessage(obtainMessage(what));
     }
 
     /**
      * Enqueue a message to this state machine.
      */
     public final void sendMessage(int what, Object obj) {
-        mHsmHandler.sendMessage(obtainMessage(what,obj));
+        mSmHandler.sendMessage(obtainMessage(what,obj));
     }
 
     /**
      * Enqueue a message to this state machine.
      */
     public final void sendMessage(Message msg) {
-        mHsmHandler.sendMessage(msg);
+        mSmHandler.sendMessage(msg);
     }
 
     /**
      * Enqueue a message to this state machine after a delay.
      */
     public final void sendMessageDelayed(int what, long delayMillis) {
-        mHsmHandler.sendMessageDelayed(obtainMessage(what), delayMillis);
+        mSmHandler.sendMessageDelayed(obtainMessage(what), delayMillis);
     }
 
     /**
      * Enqueue a message to this state machine after a delay.
      */
     public final void sendMessageDelayed(int what, Object obj, long delayMillis) {
-        mHsmHandler.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+        mSmHandler.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
     }
 
     /**
      * Enqueue a message to this state machine after a delay.
      */
     public final void sendMessageDelayed(Message msg, long delayMillis) {
-        mHsmHandler.sendMessageDelayed(msg, delayMillis);
+        mSmHandler.sendMessageDelayed(msg, delayMillis);
     }
 
     /**
      * Enqueue a message to the front of the queue for this state machine.
-     * Protected, may only be called by instances of HierarchicalStateMachine.
+     * Protected, may only be called by instances of StateMachine.
      */
     protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
-        mHsmHandler.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
+        mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
     }
 
     /**
      * Enqueue a message to the front of the queue for this state machine.
-     * Protected, may only be called by instances of HierarchicalStateMachine.
+     * Protected, may only be called by instances of StateMachine.
      */
     protected final void sendMessageAtFrontOfQueue(int what) {
-        mHsmHandler.sendMessageAtFrontOfQueue(obtainMessage(what));
+        mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what));
     }
 
     /**
      * Enqueue a message to the front of the queue for this state machine.
-     * Protected, may only be called by instances of HierarchicalStateMachine.
+     * Protected, may only be called by instances of StateMachine.
      */
     protected final void sendMessageAtFrontOfQueue(Message msg) {
-        mHsmHandler.sendMessageAtFrontOfQueue(msg);
+        mSmHandler.sendMessageAtFrontOfQueue(msg);
     }
 
     /**
      * Removes a message from the message queue.
-     * Protected, may only be called by instances of HierarchicalStateMachine.
+     * Protected, may only be called by instances of StateMachine.
      */
     protected final void removeMessages(int what) {
-        mHsmHandler.removeMessages(what);
+        mSmHandler.removeMessages(what);
     }
 
     /**
      * Conditionally quit the looper and stop execution.
      *
-     * This sends the HSM_QUIT_MSG to the state machine and
+     * This sends the SM_QUIT_MSG to the state machine and
      * if not handled by any state's processMessage then the
      * state machine will be stopped and no further messages
      * will be processed.
      */
     public final void quit() {
-        mHsmHandler.quit();
+        mSmHandler.quit();
     }
 
     /**
      * @return ture if msg is quit
      */
     protected final boolean isQuit(Message msg) {
-        return mHsmHandler.isQuit(msg);
+        return mSmHandler.isQuit(msg);
     }
 
     /**
      * @return if debugging is enabled
      */
     public boolean isDbg() {
-        return mHsmHandler.isDbg();
+        return mSmHandler.isDbg();
     }
 
     /**
@@ -1502,7 +1502,7 @@
      * @param dbg is true to enable debugging.
      */
     public void setDbg(boolean dbg) {
-        mHsmHandler.setDbg(dbg);
+        mSmHandler.setDbg(dbg);
     }
 
     /**
@@ -1510,6 +1510,6 @@
      */
     public void start() {
         /** Send the complete construction message */
-        mHsmHandler.completeConstruction();
+        mSmHandler.completeConstruction();
     }
 }
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index f8f8761..290f528 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -54,6 +54,7 @@
 	android_view_KeyCharacterMap.cpp \
 	android_view_GLES20Canvas.cpp \
 	android_view_MotionEvent.cpp \
+	android_view_PointerIcon.cpp \
 	android_view_VelocityTracker.cpp \
 	android_text_AndroidCharacter.cpp \
 	android_text_AndroidBidi.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b628b9dc..a4a229a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -170,6 +170,7 @@
 extern int register_android_view_InputQueue(JNIEnv* env);
 extern int register_android_view_KeyEvent(JNIEnv* env);
 extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_PointerIcon(JNIEnv* env);
 extern int register_android_view_VelocityTracker(JNIEnv* env);
 extern int register_android_content_res_ObbScanner(JNIEnv* env);
 extern int register_android_content_res_Configuration(JNIEnv* env);
@@ -1212,6 +1213,7 @@
     REG_JNI(register_android_view_InputQueue),
     REG_JNI(register_android_view_KeyEvent),
     REG_JNI(register_android_view_MotionEvent),
+    REG_JNI(register_android_view_PointerIcon),
     REG_JNI(register_android_view_VelocityTracker),
 
     REG_JNI(register_android_content_res_ObbScanner),
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 207c72f..310f02f 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -743,7 +743,11 @@
                                       jcharArray text, int index, int count,
                                       jfloat x, jfloat y, int flags, SkPaint* paint) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
+#if RTL_USE_HARFBUZZ
+        drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, flags, paint);
+#else
         TextLayout::drawText(paint, textArray + index, count, flags, x, y, canvas);
+#endif
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
     }
 
@@ -752,7 +756,11 @@
                                           int start, int end,
                                           jfloat x, jfloat y, int flags, SkPaint* paint) {
         const jchar* textArray = env->GetStringChars(text, NULL);
+#if RTL_USE_HARFBUZZ
+        drawTextWithGlyphs(canvas, textArray, start, end, x, y, flags, paint);
+#else
         TextLayout::drawText(paint, textArray + start, end - start, flags, x, y, canvas);
+#endif
         env->ReleaseStringChars(text, textArray);
     }
 
@@ -781,6 +789,23 @@
                 x, y, flags, paint);
     }
 
+    static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
+            int start, int count, int contextCount,
+            jfloat x, jfloat y, int flags, SkPaint* paint) {
+
+        sp<TextLayoutCacheValue> value = gTextLayoutCache.getValue(
+                paint, textArray, start, count, contextCount, flags);
+        if (value == NULL) {
+            LOGE("drawTextWithGlyphs -- cannot get Cache value");
+            return ;
+        }
+#if DEBUG_GLYPHS
+        logGlyphs(value);
+#endif
+        doDrawGlyphs(canvas, value->getGlyphs(), 0, value->getGlyphsCount(),
+                x, y, flags, paint);
+    }
+
     static void drawTextWithGlyphs___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
                                       jcharArray text, int index, int count,
                                       jfloat x, jfloat y, int flags, SkPaint* paint) {
@@ -831,8 +856,13 @@
         jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
 
         jchar* chars = env->GetCharArrayElements(text, NULL);
+#if RTL_USE_HARFBUZZ
+        drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex,
+                count, contextCount, x, y, dirFlags, paint);
+#else
         TextLayout::drawTextRun(paint, chars + contextIndex, index - contextIndex,
-                                count, contextCount, dirFlags, x, y, canvas);
+                count, contextCount, dirFlags, x, y, canvas);
+#endif
         env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
     }
 
@@ -844,8 +874,13 @@
         jint count = end - start;
         jint contextCount = contextEnd - contextStart;
         const jchar* chars = env->GetStringChars(text, NULL);
+#if RTL_USE_HARFBUZZ
+        drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart,
+                count, contextCount, x, y, dirFlags, paint);
+#else
         TextLayout::drawTextRun(paint, chars + contextStart, start - contextStart,
-                                count, contextCount, dirFlags, x, y, canvas);
+                count, contextCount, dirFlags, x, y, canvas);
+#endif
         env->ReleaseStringChars(text, chars);
     }
 
diff --git a/core/jni/android/graphics/RtlProperties.h b/core/jni/android/graphics/RtlProperties.h
index 4fac89a..a41c91b 100644
--- a/core/jni/android/graphics/RtlProperties.h
+++ b/core/jni/android/graphics/RtlProperties.h
@@ -52,7 +52,7 @@
 #define DEBUG_ADVANCES 0
 
 // Define if we want (1) to have Glyphs debug values or not (0)
-#define DEBUG_GLYPHS 1
+#define DEBUG_GLYPHS 0
 
 } // namespace android
 #endif // ANDROID_RTL_PROPERTIES_H
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index 088202e..77a731a 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -420,7 +420,9 @@
             UBiDi* bidi = ubidi_open();
             if (bidi) {
                 UErrorCode status = U_ZERO_ERROR;
+#if DEBUG_GLYPHS
                 LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
+#endif
                 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
                 if (U_SUCCESS(status)) {
                     int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
@@ -430,7 +432,6 @@
 #endif
 
                     if (rc == 1 || !U_SUCCESS(status)) {
-                        LOGD("HERE !!!");
                         computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount,
                                 dirFlags, outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
                         ubidi_close(bidi);
@@ -517,16 +518,25 @@
 #endif
 
     // Get Advances and their total
-    jfloat totalAdvance = 0;
-    for (size_t i = 0; i < count; i++) {
-        totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[i]);
-#if DEBUG_ADVANCES
-        LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i],
-                totalAdvance);
-#endif
+    jfloat totalAdvance = outAdvances[0] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
+    for (size_t i = 1; i < count; i++) {
+        size_t clusterPrevious = shaperItem.log_clusters[i - 1];
+        size_t cluster = shaperItem.log_clusters[i];
+        if (cluster == clusterPrevious) {
+            outAdvances[i] = 0;
+        } else {
+            totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
+        }
     }
     *outTotalAdvance = totalAdvance;
 
+#if DEBUG_ADVANCES
+    for (size_t i = 0; i < count; i++) {
+        LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
+                outAdvances[i], shaperItem.log_clusters[i], totalAdvance);
+    }
+#endif
+
     // Get Glyphs
     if (outGlyphs) {
         *outGlyphsCount = shaperItem.num_glyphs;
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
new file mode 100644
index 0000000..091341a
--- /dev/null
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "PointerIcon-JNI"
+
+#include "JNIHelp.h"
+
+#include "android_view_PointerIcon.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <android/graphics/GraphicsJNI.h>
+
+namespace android {
+
+static struct {
+    jclass clazz;
+    jfieldID mStyle;
+    jfieldID mBitmap;
+    jfieldID mHotSpotX;
+    jfieldID mHotSpotY;
+    jmethodID getSystemIcon;
+    jmethodID load;
+} gPointerIconClassInfo;
+
+
+// --- Global Functions ---
+
+jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env, jobject contextObj, int32_t style) {
+    jobject pointerIconObj = env->CallStaticObjectMethod(gPointerIconClassInfo.clazz,
+            gPointerIconClassInfo.getSystemIcon, contextObj, style);
+    if (env->ExceptionCheck()) {
+        LOGW("An exception occurred while getting a pointer icon with style %d.", style);
+        LOGW_EX(env);
+        env->ExceptionClear();
+        return NULL;
+    }
+    return pointerIconObj;
+}
+
+status_t android_view_PointerIcon_load(JNIEnv* env, jobject pointerIconObj, jobject contextObj,
+        PointerIcon* outPointerIcon) {
+    outPointerIcon->reset();
+
+    if (!pointerIconObj) {
+        return OK;
+    }
+
+    jobject loadedPointerIconObj = env->CallObjectMethod(pointerIconObj,
+            gPointerIconClassInfo.load, contextObj);
+    if (env->ExceptionCheck() || !loadedPointerIconObj) {
+        LOGW("An exception occurred while loading a pointer icon.");
+        LOGW_EX(env);
+        env->ExceptionClear();
+        return UNKNOWN_ERROR;
+    }
+
+    outPointerIcon->style = env->GetIntField(loadedPointerIconObj,
+            gPointerIconClassInfo.mStyle);
+    outPointerIcon->hotSpotX = env->GetFloatField(loadedPointerIconObj,
+            gPointerIconClassInfo.mHotSpotX);
+    outPointerIcon->hotSpotY = env->GetFloatField(loadedPointerIconObj,
+            gPointerIconClassInfo.mHotSpotY);
+
+    jobject bitmapObj = env->GetObjectField(loadedPointerIconObj, gPointerIconClassInfo.mBitmap);
+    if (bitmapObj) {
+        SkBitmap* bitmap = GraphicsJNI::getNativeBitmap(env, bitmapObj);
+        if (bitmap) {
+            outPointerIcon->bitmap = *bitmap; // use a shared pixel ref
+        }
+        env->DeleteLocalRef(bitmapObj);
+    }
+
+    env->DeleteLocalRef(loadedPointerIconObj);
+    return OK;
+}
+
+status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env, jobject contextObj,
+        int32_t style, PointerIcon* outPointerIcon) {
+    jobject pointerIconObj = android_view_PointerIcon_getSystemIcon(env, contextObj, style);
+    if (!pointerIconObj) {
+        outPointerIcon->reset();
+        return UNKNOWN_ERROR;
+    }
+
+    status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+            contextObj, outPointerIcon);
+    env->DeleteLocalRef(pointerIconObj);
+    return status;
+}
+
+
+// --- JNI Registration ---
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_view_PointerIcon(JNIEnv* env) {
+    FIND_CLASS(gPointerIconClassInfo.clazz, "android/view/PointerIcon");
+
+    GET_FIELD_ID(gPointerIconClassInfo.mBitmap, gPointerIconClassInfo.clazz,
+            "mBitmap", "Landroid/graphics/Bitmap;");
+
+    GET_FIELD_ID(gPointerIconClassInfo.mStyle, gPointerIconClassInfo.clazz,
+            "mStyle", "I");
+
+    GET_FIELD_ID(gPointerIconClassInfo.mHotSpotX, gPointerIconClassInfo.clazz,
+            "mHotSpotX", "F");
+
+    GET_FIELD_ID(gPointerIconClassInfo.mHotSpotY, gPointerIconClassInfo.clazz,
+            "mHotSpotY", "F");
+
+    GET_STATIC_METHOD_ID(gPointerIconClassInfo.getSystemIcon, gPointerIconClassInfo.clazz,
+            "getSystemIcon", "(Landroid/content/Context;I)Landroid/view/PointerIcon;");
+
+    GET_METHOD_ID(gPointerIconClassInfo.load, gPointerIconClassInfo.clazz,
+            "load", "(Landroid/content/Context;)Landroid/view/PointerIcon;");
+
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
new file mode 100644
index 0000000..3bfd645
--- /dev/null
+++ b/core/jni/android_view_PointerIcon.h
@@ -0,0 +1,80 @@
+/*
+ * 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 _ANDROID_VIEW_POINTER_ICON_H
+#define _ANDROID_VIEW_POINTER_ICON_H
+
+#include "jni.h"
+
+#include <utils/Errors.h>
+#include <SkBitmap.h>
+
+namespace android {
+
+/* Pointer icon styles.
+ * Must match the definition in android.view.PointerIcon.
+ */
+enum {
+    POINTER_ICON_STYLE_CUSTOM = -1,
+    POINTER_ICON_STYLE_NULL = 0,
+    POINTER_ICON_STYLE_ARROW = 1000,
+    POINTER_ICON_STYLE_SPOT_HOVER = 2000,
+    POINTER_ICON_STYLE_SPOT_TOUCH = 2001,
+    POINTER_ICON_STYLE_SPOT_ANCHOR = 2002,
+};
+
+/*
+ * Describes a pointer icon.
+ */
+struct PointerIcon {
+    inline PointerIcon() {
+        reset();
+    }
+
+    int32_t style;
+    SkBitmap bitmap;
+    float hotSpotX;
+    float hotSpotY;
+
+    inline bool isNullIcon() {
+        return style == POINTER_ICON_STYLE_NULL;
+    }
+
+    inline void reset() {
+        style = POINTER_ICON_STYLE_NULL;
+        bitmap.reset();
+        hotSpotX = 0;
+        hotSpotY = 0;
+    }
+};
+
+/* Gets a system pointer icon with the specified style. */
+extern jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env,
+        jobject contextObj, int32_t style);
+
+/* Loads the bitmap associated with a pointer icon.
+ * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
+extern status_t android_view_PointerIcon_load(JNIEnv* env,
+        jobject pointerIconObj, jobject contextObj, PointerIcon* outPointerIcon);
+
+/* Loads the bitmap associated with a pointer icon by style.
+ * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
+extern status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env,
+        jobject contextObj, int32_t style, PointerIcon* outPointerIcon);
+
+} // namespace android
+
+#endif // _ANDROID_OS_POINTER_ICON_H
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor.png b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
new file mode 100644
index 0000000..d7aca36
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml
new file mode 100644
index 0000000..2222b8e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_anchor"
+    android:hotSpotX="33"
+    android:hotSpotY="33" />
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover.png b/core/res/res/drawable-mdpi/pointer_spot_hover.png
new file mode 100644
index 0000000..5041aa3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml
new file mode 100644
index 0000000..dc62a69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_hover"
+    android:hotSpotX="33"
+    android:hotSpotY="33" />
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch.png b/core/res/res/drawable-mdpi/pointer_spot_touch.png
new file mode 100644
index 0000000..64a42a1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml
new file mode 100644
index 0000000..4bffee6
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_touch"
+    android:hotSpotX="24"
+    android:hotSpotY="24" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 819ce58..e8767d8 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -757,6 +757,13 @@
         <!-- Default style for the Switch widget. -->
         <attr name="switchStyle" format="reference" />
 
+        <!-- ============== -->
+        <!-- Pointer styles -->
+        <!-- ============== -->
+        <eat-comment />
+
+        <!-- Reference to the Pointer style -->
+        <attr name="pointerStyle" format="reference" />
     </declare-styleable>
 
     <!-- **************************************************************** -->
@@ -4921,6 +4928,17 @@
         <attr name="switchPadding" format="dimension" />
     </declare-styleable>
 
+    <declare-styleable name="Pointer">
+        <!-- Reference to a pointer icon drawable with STYLE_ARROW -->
+        <attr name="pointerIconArrow" format="reference" />
+        <!-- Reference to a pointer icon drawable with STYLE_SPOT_HOVER -->
+        <attr name="pointerIconSpotHover" format="reference" />
+        <!-- Reference to a pointer icon drawable with STYLE_SPOT_TOUCH -->
+        <attr name="pointerIconSpotTouch" format="reference" />
+        <!-- Reference to a pointer icon drawable with STYLE_SPOT_ANCHOR -->
+        <attr name="pointerIconSpotAnchor" format="reference" />
+    </declare-styleable>
+
     <declare-styleable name="PointerIcon">
         <!-- Drawable to use as the icon bitmap. -->
         <attr name="bitmap" format="reference" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index bf4c6d7..b4042c0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -2210,4 +2210,12 @@
         <item name="android:borderLeft">0dip</item>
         <item name="android:borderRight">0dip</item>
     </style>
+
+    <!-- Pointer styles -->
+    <style name="Pointer">
+        <item name="android:pointerIconArrow">@android:drawable/pointer_arrow_icon</item>
+        <item name="android:pointerIconSpotHover">@android:drawable/pointer_spot_hover_icon</item>
+        <item name="android:pointerIconSpotTouch">@android:drawable/pointer_spot_touch_icon</item>
+        <item name="android:pointerIconSpotAnchor">@android:drawable/pointer_spot_anchor_icon</item>
+    </style>
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index b1e4f0f..0748b10 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -323,6 +323,8 @@
         <item name="fastScrollOverlayPosition">floating</item>
         <item name="fastScrollTextColor">@android:color/primary_text_dark</item>
 
+        <!-- Pointer style -->
+        <item name="pointerStyle">@android:style/Pointer</item>
     </style>
 
     <!-- Variant of the default (dark) theme with no title bar -->
diff --git a/core/tests/coretests/src/com/android/internal/util/HierarchicalStateMachineTest.java b/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
similarity index 87%
rename from core/tests/coretests/src/com/android/internal/util/HierarchicalStateMachineTest.java
rename to core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
index b6f8be5..ab6b2b6 100644
--- a/core/tests/coretests/src/com/android/internal/util/HierarchicalStateMachineTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
@@ -22,9 +22,9 @@
 import android.os.Message;
 import android.os.SystemClock;
 
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
-import com.android.internal.util.HierarchicalStateMachine.ProcessedMessageInfo;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.StateMachine.ProcessedMessageInfo;
 
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -33,9 +33,9 @@
 import junit.framework.TestCase;
 
 /**
- * Test for HierarchicalStateMachine.
+ * Test for StateMachine.
  */
-public class HierarchicalStateMachineTest extends TestCase {
+public class StateMachineTest extends TestCase {
     private static final int TEST_CMD_1 = 1;
     private static final int TEST_CMD_2 = 2;
     private static final int TEST_CMD_3 = 3;
@@ -45,12 +45,12 @@
 
     private static final boolean DBG = true;
     private static final boolean WAIT_FOR_DEBUGGER = false;
-    private static final String TAG = "HierarchicalStateMachineTest";
+    private static final String TAG = "StateMachineTest";
 
     /**
      * Tests that we can quit the state machine.
      */
-    class StateMachineQuitTest extends HierarchicalStateMachine {
+    class StateMachineQuitTest extends StateMachine {
         private int mQuitCount = 0;
 
         StateMachineQuitTest(String name) {
@@ -65,8 +65,9 @@
             setInitialState(mS1);
         }
 
-        class S1 extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class S1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 if (isQuit(message)) {
                     mQuitCount += 1;
                     if (mQuitCount > 2) {
@@ -129,18 +130,18 @@
 
         // The first two message didn't quit and were handled by mS1
         pmi = smQuitTest.getProcessedMessageInfo(6);
-        assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+        assertEquals(StateMachine.SM_QUIT_CMD, pmi.getWhat());
         assertEquals(smQuitTest.mS1, pmi.getState());
         assertEquals(smQuitTest.mS1, pmi.getOriginalState());
 
         pmi = smQuitTest.getProcessedMessageInfo(7);
-        assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+        assertEquals(StateMachine.SM_QUIT_CMD, pmi.getWhat());
         assertEquals(smQuitTest.mS1, pmi.getState());
         assertEquals(smQuitTest.mS1, pmi.getOriginalState());
 
         // The last message was never handled so the states are null
         pmi = smQuitTest.getProcessedMessageInfo(8);
-        assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+        assertEquals(StateMachine.SM_QUIT_CMD, pmi.getWhat());
         assertEquals(null, pmi.getState());
         assertEquals(null, pmi.getOriginalState());
 
@@ -150,7 +151,7 @@
     /**
      * Test enter/exit can use transitionTo
      */
-    class StateMachineEnterExitTransitionToTest extends HierarchicalStateMachine {
+    class StateMachineEnterExitTransitionToTest extends StateMachine {
         StateMachineEnterExitTransitionToTest(String name) {
             super(name);
             mThisSm = this;
@@ -166,34 +167,38 @@
             setInitialState(mS1);
         }
 
-        class S1 extends HierarchicalState {
-            @Override protected void enter() {
+        class S1 extends State {
+            @Override
+            public void enter() {
                 // Test that message is HSM_INIT_CMD
-                assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
+                assertEquals(SM_INIT_CMD, getCurrentMessage().what);
 
                 // Test that a transition in enter and the initial state works
                 mS1EnterCount += 1;
                 transitionTo(mS2);
                 Log.d(TAG, "S1.enter");
             }
-            @Override protected void exit() {
+            @Override
+            public void exit() {
                 // Test that message is HSM_INIT_CMD
-                assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
+                assertEquals(SM_INIT_CMD, getCurrentMessage().what);
 
                 mS1ExitCount += 1;
                 Log.d(TAG, "S1.exit");
             }
         }
 
-        class S2 extends HierarchicalState {
-            @Override protected void enter() {
+        class S2 extends State {
+            @Override
+            public void enter() {
                 // Test that message is HSM_INIT_CMD
-                assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
+                assertEquals(SM_INIT_CMD, getCurrentMessage().what);
 
                 mS2EnterCount += 1;
                 Log.d(TAG, "S2.enter");
             }
-            @Override protected void exit() {
+            @Override
+            public void exit() {
                 // Test that message is TEST_CMD_1
                 assertEquals(TEST_CMD_1, getCurrentMessage().what);
 
@@ -202,7 +207,8 @@
                 transitionTo(mS4);
                 Log.d(TAG, "S2.exit");
             }
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public boolean processMessage(Message message) {
                 // Start a transition to S3 but it will be
                 // changed to a transition to S4 in exit
                 transitionTo(mS3);
@@ -211,28 +217,32 @@
             }
         }
 
-        class S3 extends HierarchicalState {
-            @Override protected void enter() {
+        class S3 extends State {
+            @Override
+            public void enter() {
                 // Test that we can do halting in an enter/exit
                 transitionToHaltingState();
                 mS3EnterCount += 1;
                 Log.d(TAG, "S3.enter");
             }
-            @Override protected void exit() {
+            @Override
+            public void exit() {
                 mS3ExitCount += 1;
                 Log.d(TAG, "S3.exit");
             }
         }
 
 
-        class S4 extends HierarchicalState {
-            @Override protected void enter() {
+        class S4 extends State {
+            @Override
+            public void enter() {
                 // Test that we can do halting in an enter/exit
                 transitionToHaltingState();
                 mS4EnterCount += 1;
                 Log.d(TAG, "S4.enter");
             }
-            @Override protected void exit() {
+            @Override
+            public void exit() {
                 mS4ExitCount += 1;
                 Log.d(TAG, "S4.exit");
             }
@@ -310,7 +320,7 @@
     /**
      * Tests that ProcessedMessage works as a circular buffer.
      */
-    class StateMachine0 extends HierarchicalStateMachine {
+    class StateMachine0 extends StateMachine {
         StateMachine0(String name) {
             super(name);
             mThisSm = this;
@@ -324,8 +334,9 @@
             setInitialState(mS1);
         }
 
-        class S1 extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class S1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_6) {
                     transitionToHaltingState();
                 }
@@ -394,7 +405,7 @@
      * in state mS1. With the first message it transitions to
      * itself which causes it to be exited and reentered.
      */
-    class StateMachine1 extends HierarchicalStateMachine {
+    class StateMachine1 extends StateMachine {
         StateMachine1(String name) {
             super(name);
             mThisSm = this;
@@ -408,12 +419,17 @@
             if (DBG) Log.d(TAG, "StateMachine1: ctor X");
         }
 
-        class S1 extends HierarchicalState {
-            @Override protected void enter() {
+        class S1 extends State {
+            @Override
+            public void enter() {
                 mEnterCount++;
             }
-
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public void exit() {
+                mExitCount++;
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_1) {
                     assertEquals(1, mEnterCount);
                     assertEquals(0, mExitCount);
@@ -425,10 +441,6 @@
                 }
                 return HANDLED;
             }
-
-            @Override protected void exit() {
-                mExitCount++;
-            }
         }
 
         @Override
@@ -493,7 +505,7 @@
      * mS2 then receives both of the deferred messages first TEST_CMD_1 and
      * then TEST_CMD_2.
      */
-    class StateMachine2 extends HierarchicalStateMachine {
+    class StateMachine2 extends StateMachine {
         StateMachine2(String name) {
             super(name);
             mThisSm = this;
@@ -508,26 +520,28 @@
             if (DBG) Log.d(TAG, "StateMachine2: ctor X");
         }
 
-        class S1 extends HierarchicalState {
-            @Override protected void enter() {
+        class S1 extends State {
+            @Override
+            public void enter() {
                 mDidEnter = true;
             }
-
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public void exit() {
+                mDidExit = true;
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 deferMessage(message);
                 if (message.what == TEST_CMD_2) {
                     transitionTo(mS2);
                 }
                 return HANDLED;
             }
-
-            @Override protected void exit() {
-                mDidExit = true;
-            }
         }
 
-        class S2 extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class S2 extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_2) {
                     transitionToHaltingState();
                 }
@@ -598,7 +612,7 @@
      * Test that unhandled messages in a child are handled by the parent.
      * When TEST_CMD_2 is received.
      */
-    class StateMachine3 extends HierarchicalStateMachine {
+    class StateMachine3 extends StateMachine {
         StateMachine3(String name) {
             super(name);
             mThisSm = this;
@@ -615,8 +629,9 @@
             if (DBG) Log.d(TAG, "StateMachine3: ctor X");
         }
 
-        class ParentState extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class ParentState extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_2) {
                     transitionToHaltingState();
                 }
@@ -624,8 +639,9 @@
             }
         }
 
-        class ChildState extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class ChildState extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 return NOT_HANDLED;
             }
         }
@@ -682,7 +698,7 @@
      * with transition from child 1 to child 2 and child 2
      * lets the parent handle the messages.
      */
-    class StateMachine4 extends HierarchicalStateMachine {
+    class StateMachine4 extends StateMachine {
         StateMachine4(String name) {
             super(name);
             mThisSm = this;
@@ -700,8 +716,9 @@
             if (DBG) Log.d(TAG, "StateMachine4: ctor X");
         }
 
-        class ParentState extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class ParentState extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_2) {
                     transitionToHaltingState();
                 }
@@ -709,15 +726,17 @@
             }
         }
 
-        class ChildState1 extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class ChildState1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 transitionTo(mChildState2);
                 return HANDLED;
             }
         }
 
-        class ChildState2 extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class ChildState2 extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 return NOT_HANDLED;
             }
         }
@@ -775,7 +794,7 @@
      * Test transition from one child to another of a "complex"
      * hierarchy with two parents and multiple children.
      */
-    class StateMachine5 extends HierarchicalStateMachine {
+    class StateMachine5 extends StateMachine {
         StateMachine5(String name) {
             super(name);
             mThisSm = this;
@@ -797,23 +816,32 @@
             if (DBG) Log.d(TAG, "StateMachine5: ctor X");
         }
 
-        class ParentState1 extends HierarchicalState {
-            @Override protected void enter() {
+        class ParentState1 extends State {
+            @Override
+            public void enter() {
                 mParentState1EnterCount += 1;
             }
-            @Override protected boolean processMessage(Message message) {
-                return HANDLED;
-            }
-            @Override protected void exit() {
+            @Override
+            public void exit() {
                 mParentState1ExitCount += 1;
             }
+            @Override
+            public boolean processMessage(Message message) {
+                return HANDLED;
+            }
         }
 
-        class ChildState1 extends HierarchicalState {
-            @Override protected void enter() {
+        class ChildState1 extends State {
+            @Override
+            public void enter() {
                 mChildState1EnterCount += 1;
             }
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public void exit() {
+                mChildState1ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 assertEquals(1, mParentState1EnterCount);
                 assertEquals(0, mParentState1ExitCount);
                 assertEquals(1, mChildState1EnterCount);
@@ -832,16 +860,19 @@
                 transitionTo(mChildState2);
                 return HANDLED;
             }
-            @Override protected void exit() {
-                mChildState1ExitCount += 1;
-            }
         }
 
-        class ChildState2 extends HierarchicalState {
-            @Override protected void enter() {
+        class ChildState2 extends State {
+            @Override
+            public void enter() {
                 mChildState2EnterCount += 1;
             }
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public void exit() {
+                mChildState2ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 assertEquals(1, mParentState1EnterCount);
                 assertEquals(0, mParentState1ExitCount);
                 assertEquals(1, mChildState1EnterCount);
@@ -860,16 +891,19 @@
                 transitionTo(mChildState5);
                 return HANDLED;
             }
-            @Override protected void exit() {
-                mChildState2ExitCount += 1;
-            }
         }
 
-        class ParentState2 extends HierarchicalState {
-            @Override protected void enter() {
+        class ParentState2 extends State {
+            @Override
+            public void enter() {
                 mParentState2EnterCount += 1;
             }
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public void exit() {
+                mParentState2ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 assertEquals(1, mParentState1EnterCount);
                 assertEquals(1, mParentState1ExitCount);
                 assertEquals(1, mChildState1EnterCount);
@@ -888,16 +922,19 @@
                 transitionToHaltingState();
                 return HANDLED;
             }
-            @Override protected void exit() {
-                mParentState2ExitCount += 1;
-            }
         }
 
-        class ChildState3 extends HierarchicalState {
-            @Override protected void enter() {
+        class ChildState3 extends State {
+            @Override
+            public void enter() {
                 mChildState3EnterCount += 1;
             }
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public void exit() {
+                mChildState3ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 assertEquals(1, mParentState1EnterCount);
                 assertEquals(1, mParentState1ExitCount);
                 assertEquals(1, mChildState1EnterCount);
@@ -916,16 +953,19 @@
                 transitionTo(mChildState4);
                 return HANDLED;
             }
-            @Override protected void exit() {
-                mChildState3ExitCount += 1;
-            }
         }
 
-        class ChildState4 extends HierarchicalState {
-            @Override protected void enter() {
+        class ChildState4 extends State {
+            @Override
+            public void enter() {
                 mChildState4EnterCount += 1;
             }
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public void exit() {
+                mChildState4ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 assertEquals(1, mParentState1EnterCount);
                 assertEquals(1, mParentState1ExitCount);
                 assertEquals(1, mChildState1EnterCount);
@@ -944,16 +984,19 @@
                 transitionTo(mParentState2);
                 return HANDLED;
             }
-            @Override protected void exit() {
-                mChildState4ExitCount += 1;
-            }
         }
 
-        class ChildState5 extends HierarchicalState {
-            @Override protected void enter() {
+        class ChildState5 extends State {
+            @Override
+            public void enter() {
                 mChildState5EnterCount += 1;
             }
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public void exit() {
+                mChildState5ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 assertEquals(1, mParentState1EnterCount);
                 assertEquals(1, mParentState1ExitCount);
                 assertEquals(1, mChildState1EnterCount);
@@ -972,9 +1015,6 @@
                 transitionTo(mChildState3);
                 return HANDLED;
             }
-            @Override protected void exit() {
-                mChildState5ExitCount += 1;
-            }
         }
 
         @Override
@@ -1089,7 +1129,7 @@
      * after construction and before any other messages arrive and that
      * sendMessageDelayed works.
      */
-    class StateMachine6 extends HierarchicalStateMachine {
+    class StateMachine6 extends StateMachine {
         StateMachine6(String name) {
             super(name);
             mThisSm = this;
@@ -1103,13 +1143,13 @@
             if (DBG) Log.d(TAG, "StateMachine6: ctor X");
         }
 
-        class S1 extends HierarchicalState {
-
-            @Override protected void enter() {
+        class S1 extends State {
+            @Override
+            public void enter() {
                 sendMessage(TEST_CMD_1);
             }
-
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_1) {
                     mArrivalTimeMsg1 = SystemClock.elapsedRealtime();
                 } else if (message.what == TEST_CMD_2) {
@@ -1118,9 +1158,6 @@
                 }
                 return HANDLED;
             }
-
-            @Override protected void exit() {
-            }
         }
 
         @Override
@@ -1178,7 +1215,7 @@
      * Test that enter is invoked immediately after exit. This validates
      * that enter can be used to send a watch dog message for its state.
      */
-    class StateMachine7 extends HierarchicalStateMachine {
+    class StateMachine7 extends StateMachine {
         private final int SM7_DELAY_TIME = 250;
 
         StateMachine7(String name) {
@@ -1195,24 +1232,26 @@
             if (DBG) Log.d(TAG, "StateMachine7: ctor X");
         }
 
-        class S1 extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class S1 extends State {
+            @Override
+            public void exit() {
+                sendMessage(TEST_CMD_2);
+            }
+            @Override
+            public boolean processMessage(Message message) {
                 transitionTo(mS2);
                 return HANDLED;
             }
-            @Override protected void exit() {
-                sendMessage(TEST_CMD_2);
-            }
         }
 
-        class S2 extends HierarchicalState {
-
-            @Override protected void enter() {
+        class S2 extends State {
+            @Override
+            public void enter() {
                 // Send a delayed message as a watch dog
                 sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME);
             }
-
-            @Override protected boolean processMessage(Message message) {
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_2) {
                     mMsgCount += 1;
                     mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
@@ -1226,9 +1265,6 @@
                 }
                 return HANDLED;
             }
-
-            @Override protected void exit() {
-            }
         }
 
         @Override
@@ -1286,7 +1322,7 @@
     /**
      * Test unhandledMessage.
      */
-    class StateMachineUnhandledMessage extends HierarchicalStateMachine {
+    class StateMachineUnhandledMessage extends StateMachine {
         StateMachineUnhandledMessage(String name) {
             super(name);
             mThisSm = this;
@@ -1298,13 +1334,14 @@
             // Set the initial state
             setInitialState(mS1);
         }
-
-        @Override protected void unhandledMessage(Message message) {
+        @Override
+        public void unhandledMessage(Message message) {
             mUnhandledMessageCount += 1;
         }
 
-        class S1 extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class S1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_2) {
                     transitionToHaltingState();
                 }
@@ -1359,7 +1396,7 @@
      * will be used to notify testStateMachineSharedThread that the test is
      * complete.
      */
-    class StateMachineSharedThread extends HierarchicalStateMachine {
+    class StateMachineSharedThread extends StateMachine {
         StateMachineSharedThread(String name, Looper looper, int maxCount) {
             super(name, looper);
             mMaxCount = maxCount;
@@ -1372,8 +1409,9 @@
             setInitialState(mS1);
         }
 
-        class S1 extends HierarchicalState {
-            @Override protected boolean processMessage(Message message) {
+        class S1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
                 if (message.what == TEST_CMD_4) {
                     transitionToHaltingState();
                 }
@@ -1503,7 +1541,7 @@
     }
 }
 
-class Hsm1 extends HierarchicalStateMachine {
+class Hsm1 extends StateMachine {
     private static final String TAG = "hsm1";
 
     public static final int CMD_1 = 1;
@@ -1535,11 +1573,17 @@
         Log.d(TAG, "ctor X");
     }
 
-    class P1 extends HierarchicalState {
-        @Override protected void enter() {
+    class P1 extends State {
+        @Override
+        public void enter() {
             Log.d(TAG, "P1.enter");
         }
-        @Override protected boolean processMessage(Message message) {
+        @Override
+        public void exit() {
+            Log.d(TAG, "P1.exit");
+        }
+        @Override
+        public boolean processMessage(Message message) {
             boolean retVal;
             Log.d(TAG, "P1.processMessage what=" + message.what);
             switch(message.what) {
@@ -1557,16 +1601,19 @@
             }
             return retVal;
         }
-        @Override protected void exit() {
-            Log.d(TAG, "P1.exit");
-        }
     }
 
-    class S1 extends HierarchicalState {
-        @Override protected void enter() {
+    class S1 extends State {
+        @Override
+        public void enter() {
             Log.d(TAG, "S1.enter");
         }
-        @Override protected boolean processMessage(Message message) {
+        @Override
+        public void exit() {
+            Log.d(TAG, "S1.exit");
+        }
+        @Override
+        public boolean processMessage(Message message) {
             Log.d(TAG, "S1.processMessage what=" + message.what);
             if (message.what == CMD_1) {
                 // Transition to ourself to show that enter/exit is called
@@ -1577,16 +1624,19 @@
                 return NOT_HANDLED;
             }
         }
-        @Override protected void exit() {
-            Log.d(TAG, "S1.exit");
-        }
     }
 
-    class S2 extends HierarchicalState {
-        @Override protected void enter() {
+    class S2 extends State {
+        @Override
+        public void enter() {
             Log.d(TAG, "S2.enter");
         }
-        @Override protected boolean processMessage(Message message) {
+        @Override
+        public void exit() {
+            Log.d(TAG, "S2.exit");
+        }
+        @Override
+        public boolean processMessage(Message message) {
             boolean retVal;
             Log.d(TAG, "S2.processMessage what=" + message.what);
             switch(message.what) {
@@ -1605,17 +1655,20 @@
             }
             return retVal;
         }
-        @Override protected void exit() {
-            Log.d(TAG, "S2.exit");
-        }
     }
 
-    class P2 extends HierarchicalState {
-        @Override protected void enter() {
+    class P2 extends State {
+        @Override
+        public void enter() {
             Log.d(TAG, "P2.enter");
             sendMessage(CMD_5);
         }
-        @Override protected boolean processMessage(Message message) {
+        @Override
+        public void exit() {
+            Log.d(TAG, "P2.exit");
+        }
+        @Override
+        public boolean processMessage(Message message) {
             Log.d(TAG, "P2.processMessage what=" + message.what);
             switch(message.what) {
             case(CMD_3):
@@ -1628,9 +1681,6 @@
             }
             return HANDLED;
         }
-        @Override protected void exit() {
-            Log.d(TAG, "P2.exit");
-        }
     }
 
     @Override
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 340daaf..96828c6 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -127,11 +127,28 @@
     // be called from the client.
     status_t setDefaultBufferSize(uint32_t w, uint32_t h);
 
-private:
+    // getCurrentBuffer returns the buffer associated with the current image.
+    sp<GraphicBuffer> getCurrentBuffer() const;
+
+    // getCurrentTextureTarget returns the texture target of the current
+    // texture as returned by updateTexImage().
+    GLenum getCurrentTextureTarget() const;
+
+    // getCurrentCrop returns the cropping rectangle of the current buffer
+    Rect getCurrentCrop() const;
+
+    // getCurrentTransform returns the transform of the current buffer
+    uint32_t getCurrentTransform() const;
+
+protected:
 
     // freeAllBuffers frees the resources (both GraphicBuffer and EGLImage) for
     // all slots.
     void freeAllBuffers();
+    static bool isExternalFormat(uint32_t format);
+    static GLenum getTextureTarget(uint32_t format);
+
+private:
 
     // createImage creates a new EGLImage from a GraphicBuffer.
     EGLImageKHR createImage(EGLDisplay dpy,
@@ -194,6 +211,10 @@
     // reset mCurrentTexture to INVALID_BUFFER_SLOT.
     int mCurrentTexture;
 
+    // mCurrentTextureTarget is the GLES texture target to be used with the
+    // current texture.
+    GLenum mCurrentTextureTarget;
+
     // mCurrentTextureBuf is the graphic buffer of the current texture. It's
     // possible that this buffer is not associated with any buffer slot, so we
     // must track it separately in order to properly use
@@ -256,7 +277,7 @@
     // mMutex is the mutex used to prevent concurrent access to the member
     // variables of SurfaceTexture objects. It must be locked whenever the
     // member variables are accessed.
-    Mutex mMutex;
+    mutable Mutex mMutex;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/gui/SurfaceTextureClient.h b/include/gui/SurfaceTextureClient.h
index df82bf2..fe9b049 100644
--- a/include/gui/SurfaceTextureClient.h
+++ b/include/gui/SurfaceTextureClient.h
@@ -27,6 +27,8 @@
 
 namespace android {
 
+class Surface;
+
 class SurfaceTextureClient
     : public EGLNativeBase<ANativeWindow, SurfaceTextureClient, RefBase>
 {
@@ -36,6 +38,7 @@
     sp<ISurfaceTexture> getISurfaceTexture() const;
 
 private:
+    friend class Surface;
 
     // can't be copied
     SurfaceTextureClient& operator = (const SurfaceTextureClient& rhs);
@@ -78,6 +81,8 @@
 
     void freeAllBuffers();
 
+    int getConnectedApi() const;
+
     enum { MIN_UNDEQUEUED_BUFFERS = SurfaceTexture::MIN_UNDEQUEUED_BUFFERS };
     enum { MIN_BUFFER_SLOTS = SurfaceTexture::MIN_BUFFER_SLOTS };
     enum { NUM_BUFFER_SLOTS = SurfaceTexture::NUM_BUFFER_SLOTS };
@@ -121,10 +126,25 @@
     // a timestamp is auto-generated when queueBuffer is called.
     int64_t mTimestamp;
 
+    // mConnectedApi holds the currently connected API to this surface
+    int mConnectedApi;
+
+    // mQueryWidth is the width returned by query(). It is set to width
+    // of the last dequeued buffer or to mReqWidth if no buffer was dequeued.
+    uint32_t mQueryWidth;
+
+    // mQueryHeight is the height returned by query(). It is set to height
+    // of the last dequeued buffer or to mReqHeight if no buffer was dequeued.
+    uint32_t mQueryHeight;
+
+    // mQueryFormat is the format returned by query(). It is set to the last
+    // dequeued format or to mReqFormat if no buffer was dequeued.
+    uint32_t mQueryFormat;
+
     // mMutex is the mutex used to prevent concurrent access to the member
     // variables of SurfaceTexture objects. It must be locked whenever the
     // member variables are accessed.
-    Mutex mMutex;
+    mutable Mutex mMutex;
 };
 
 }; // namespace android
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 0dc29c8..9b92c73 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -620,6 +620,11 @@
     // Oldest sample to consider when calculating the velocity.
     static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms
 
+    // When the total duration of the window of samples being averaged is less
+    // than the window size, the resulting velocity is scaled to reduce the impact
+    // of overestimation in short traces.
+    static const nsecs_t MIN_WINDOW = 100 * 1000000; // 100 ms
+
     // The minimum duration between samples when estimating velocity.
     static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
 
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index e2346f0..39418f0 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -27,6 +27,8 @@
 
 #include <gui/SurfaceTexture.h>
 
+#include <hardware/hardware.h>
+
 #include <surfaceflinger/ISurfaceComposer.h>
 #include <surfaceflinger/SurfaceComposerClient.h>
 #include <surfaceflinger/IGraphicBufferAlloc.h>
@@ -82,6 +84,7 @@
     mUseDefaultSize(true),
     mBufferCount(MIN_BUFFER_SLOTS),
     mCurrentTexture(INVALID_BUFFER_SLOT),
+    mCurrentTextureTarget(GL_TEXTURE_EXTERNAL_OES),
     mCurrentTransform(0),
     mCurrentTimestamp(0),
     mLastQueued(INVALID_BUFFER_SLOT),
@@ -197,6 +200,7 @@
     if (buffer == NULL) {
         return ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
     }
+
     if ((mUseDefaultSize) &&
         ((uint32_t(buffer->width) != mDefaultWidth) ||
          (uint32_t(buffer->height) != mDefaultHeight))) {
@@ -263,9 +267,6 @@
     LOGV("SurfaceTexture::updateTexImage");
     Mutex::Autolock lock(mMutex);
 
-    // We always bind the texture even if we don't update its contents.
-    glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName);
-
     // Initially both mCurrentTexture and mLastQueued are INVALID_BUFFER_SLOT,
     // so this check will fail until a buffer gets queued.
     if (mCurrentTexture != mLastQueued) {
@@ -283,7 +284,15 @@
         while ((error = glGetError()) != GL_NO_ERROR) {
             LOGE("GL error cleared before updating SurfaceTexture: %#04x", error);
         }
-        glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)image);
+
+        GLenum target = getTextureTarget(
+                mSlots[mLastQueued].mGraphicBuffer->format);
+        if (target != mCurrentTextureTarget) {
+            glDeleteTextures(1, &mTexName);
+        }
+        glBindTexture(target, mTexName);
+        glEGLImageTargetTexture2DOES(target, (GLeglImageOES)image);
+
         bool failed = false;
         while ((error = glGetError()) != GL_NO_ERROR) {
             LOGE("error binding external texture image %p (slot %d): %#04x",
@@ -296,14 +305,53 @@
 
         // Update the SurfaceTexture state.
         mCurrentTexture = mLastQueued;
+        mCurrentTextureTarget = target;
         mCurrentTextureBuf = mSlots[mCurrentTexture].mGraphicBuffer;
         mCurrentCrop = mLastQueuedCrop;
         mCurrentTransform = mLastQueuedTransform;
         mCurrentTimestamp = mLastQueuedTimestamp;
+    } else {
+        // We always bind the texture even if we don't update its contents.
+        glBindTexture(mCurrentTextureTarget, mTexName);
     }
     return OK;
 }
 
+bool SurfaceTexture::isExternalFormat(uint32_t format)
+{
+    switch (format) {
+    // supported YUV formats
+    case HAL_PIXEL_FORMAT_YV12:
+    // Legacy/deprecated YUV formats
+    case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+    case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+    case HAL_PIXEL_FORMAT_YCbCr_422_I:
+        return true;
+    }
+
+    // Any OEM format needs to be considered
+    if (format>=0x100 && format<=0x1FF)
+        return true;
+
+    return false;
+}
+
+GLenum SurfaceTexture::getTextureTarget(uint32_t format)
+{
+    GLenum target = GL_TEXTURE_2D;
+#if defined(GL_OES_EGL_image_external)
+    if (isExternalFormat(format)) {
+        target = GL_TEXTURE_EXTERNAL_OES;
+    }
+#endif
+    return target;
+}
+
+GLenum SurfaceTexture::getCurrentTextureTarget() const {
+    Mutex::Autolock lock(mMutex);
+    return mCurrentTextureTarget;
+}
+
 void SurfaceTexture::getTransformMatrix(float mtx[16]) {
     LOGV("SurfaceTexture::getTransformMatrix");
     Mutex::Autolock lock(mMutex);
@@ -445,6 +493,22 @@
     return image;
 }
 
+sp<GraphicBuffer> SurfaceTexture::getCurrentBuffer() const {
+    Mutex::Autolock lock(mMutex);
+    return mCurrentTextureBuf;
+}
+
+Rect SurfaceTexture::getCurrentCrop() const {
+    Mutex::Autolock lock(mMutex);
+    return mCurrentCrop;
+}
+
+uint32_t SurfaceTexture::getCurrentTransform() const {
+    Mutex::Autolock lock(mMutex);
+    return mCurrentTransform;
+}
+
+
 static void mtxMul(float out[16], const float a[16], const float b[16]) {
     out[0] = a[0]*b[0] + a[4]*b[1] + a[8]*b[2] + a[12]*b[3];
     out[1] = a[1]*b[0] + a[5]*b[1] + a[9]*b[2] + a[13]*b[3];
diff --git a/libs/gui/SurfaceTextureClient.cpp b/libs/gui/SurfaceTextureClient.cpp
index 29fc4d3..f4b2416 100644
--- a/libs/gui/SurfaceTextureClient.cpp
+++ b/libs/gui/SurfaceTextureClient.cpp
@@ -26,8 +26,10 @@
 SurfaceTextureClient::SurfaceTextureClient(
         const sp<ISurfaceTexture>& surfaceTexture):
         mSurfaceTexture(surfaceTexture), mAllocator(0), mReqWidth(0),
-        mReqHeight(0), mReqFormat(DEFAULT_FORMAT), mReqUsage(0),
-        mTimestamp(NATIVE_WINDOW_TIMESTAMP_AUTO), mMutex() {
+        mReqHeight(0), mReqFormat(0), mReqUsage(0),
+        mTimestamp(NATIVE_WINDOW_TIMESTAMP_AUTO), mConnectedApi(0),
+        mQueryWidth(0), mQueryHeight(0), mQueryFormat(0),
+        mMutex() {
     // Initialize the ANativeWindow function pointers.
     ANativeWindow::setSwapInterval  = setSwapInterval;
     ANativeWindow::dequeueBuffer    = dequeueBuffer;
@@ -101,9 +103,10 @@
     }
     sp<GraphicBuffer>& gbuf(mSlots[buf]);
     if (err == ISurfaceTexture::BUFFER_NEEDS_REALLOCATION ||
-        gbuf == 0 || gbuf->getWidth() != mReqWidth ||
-        gbuf->getHeight() != mReqHeight ||
-        uint32_t(gbuf->getPixelFormat()) != mReqFormat ||
+        gbuf == 0 ||
+        (mReqWidth && gbuf->getWidth() != mReqWidth) ||
+        (mReqHeight && gbuf->getHeight() != mReqHeight) ||
+        (mReqFormat && uint32_t(gbuf->getPixelFormat()) != mReqFormat) ||
         (gbuf->getUsage() & mReqUsage) != mReqUsage) {
         gbuf = mSurfaceTexture->requestBuffer(buf, mReqWidth, mReqHeight,
                 mReqFormat, mReqUsage);
@@ -111,6 +114,9 @@
             LOGE("dequeueBuffer: ISurfaceTexture::requestBuffer failed");
             return NO_MEMORY;
         }
+        mQueryWidth  = gbuf->width;
+        mQueryHeight = gbuf->height;
+        mQueryFormat = gbuf->format;
     }
     *buffer = gbuf.get();
     return OK;
@@ -159,13 +165,13 @@
     Mutex::Autolock lock(mMutex);
     switch (what) {
     case NATIVE_WINDOW_WIDTH:
+        *value = mQueryWidth ? mQueryWidth : mReqWidth;
+        return NO_ERROR;
     case NATIVE_WINDOW_HEIGHT:
-        // XXX: How should SurfaceTexture behave if setBuffersGeometry didn't
-        // override the size?
-        *value = 0;
+        *value = mQueryHeight ? mQueryHeight : mReqHeight;
         return NO_ERROR;
     case NATIVE_WINDOW_FORMAT:
-        *value = DEFAULT_FORMAT;
+        *value = mQueryFormat ? mQueryFormat : mReqFormat;
         return NO_ERROR;
     case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
         *value = MIN_UNDEQUEUED_BUFFERS;
@@ -260,16 +266,49 @@
 
 int SurfaceTextureClient::connect(int api) {
     LOGV("SurfaceTextureClient::connect");
-    // XXX: Implement this!
-    return INVALID_OPERATION;
+    Mutex::Autolock lock(mMutex);
+    int err = NO_ERROR;
+    switch (api) {
+        case NATIVE_WINDOW_API_EGL:
+            if (mConnectedApi) {
+                err = -EINVAL;
+            } else {
+                mConnectedApi = api;
+            }
+            break;
+        default:
+            err = -EINVAL;
+            break;
+    }
+    return err;
 }
 
 int SurfaceTextureClient::disconnect(int api) {
     LOGV("SurfaceTextureClient::disconnect");
-    // XXX: Implement this!
-    return INVALID_OPERATION;
+    Mutex::Autolock lock(mMutex);
+    int err = NO_ERROR;
+    switch (api) {
+        case NATIVE_WINDOW_API_EGL:
+            if (mConnectedApi == api) {
+                mConnectedApi = 0;
+            } else {
+                err = -EINVAL;
+            }
+            break;
+        default:
+            err = -EINVAL;
+            break;
+    }
+    return err;
 }
 
+int SurfaceTextureClient::getConnectedApi() const
+{
+    Mutex::Autolock lock(mMutex);
+    return mConnectedApi;
+}
+
+
 int SurfaceTextureClient::setUsage(uint32_t reqUsage)
 {
     LOGV("SurfaceTextureClient::setUsage");
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index bbe579e..a95f432 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -832,6 +832,7 @@
         const Position& oldestPosition =
                 oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
         nsecs_t lastDuration = 0;
+
         while (numTouches-- > 1) {
             if (++index == HISTORY_SIZE) {
                 index = 0;
@@ -858,6 +859,14 @@
 
         // Make sure we used at least one sample.
         if (samplesUsed != 0) {
+            // Scale the velocity linearly if the window of samples is small.
+            nsecs_t totalDuration = newestMovement.eventTime - oldestMovement.eventTime;
+            if (totalDuration < MIN_WINDOW) {
+                float scale = float(totalDuration) / float(MIN_WINDOW);
+                accumVx *= scale;
+                accumVy *= scale;
+            }
+
             *outVx = accumVx;
             *outVy = accumVy;
             return true;
diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
index d5dd126..b54fb9d 100644
--- a/libs/utils/Looper.cpp
+++ b/libs/utils/Looper.cpp
@@ -662,7 +662,8 @@
 #endif
 
 void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {
-    sendMessageAtTime(LLONG_MIN, handler, message);
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    sendMessageAtTime(now, handler, message);
 }
 
 void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 1ca2d6d..f9db1a1 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -377,7 +377,7 @@
             mFileMetaData->setCString(kKeyMIMEType, "audio/mp4");
         }
 
-        mInitCheck = verifyIfStreamable();
+        mInitCheck = OK;
     } else {
         mInitCheck = err;
     }
@@ -1904,7 +1904,7 @@
 
     off64_t offset;
     size_t size;
-    uint32_t dts;
+    uint32_t cts;
     bool isSyncSample;
     bool newBuffer = false;
     if (mBuffer == NULL) {
@@ -1912,7 +1912,7 @@
 
         status_t err =
             mSampleTable->getMetaDataForSample(
-                    mCurrentSampleIndex, &offset, &size, &dts, &isSyncSample);
+                    mCurrentSampleIndex, &offset, &size, &cts, &isSyncSample);
 
         if (err != OK) {
             return err;
@@ -1942,7 +1942,7 @@
             mBuffer->set_range(0, size);
             mBuffer->meta_data()->clear();
             mBuffer->meta_data()->setInt64(
-                    kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
+                    kKeyTime, ((int64_t)cts * 1000000) / mTimescale);
 
             if (targetSampleTimeUs >= 0) {
                 mBuffer->meta_data()->setInt64(
@@ -2060,7 +2060,7 @@
 
         mBuffer->meta_data()->clear();
         mBuffer->meta_data()->setInt64(
-                kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
+                kKeyTime, ((int64_t)cts * 1000000) / mTimescale);
 
         if (targetSampleTimeUs >= 0) {
             mBuffer->meta_data()->setInt64(
@@ -2094,87 +2094,6 @@
     return NULL;
 }
 
-status_t MPEG4Extractor::verifyIfStreamable() {
-    if (!(mDataSource->flags() & DataSource::kIsCachingDataSource)) {
-        return OK;
-    }
-
-    Track *audio = findTrackByMimePrefix("audio/");
-    Track *video = findTrackByMimePrefix("video/");
-
-    if (audio == NULL || video == NULL) {
-        return OK;
-    }
-
-    sp<SampleTable> audioSamples = audio->sampleTable;
-    sp<SampleTable> videoSamples = video->sampleTable;
-
-    off64_t maxOffsetDiff = 0;
-    int64_t maxOffsetTimeUs = -1;
-
-    for (uint32_t i = 0; i < videoSamples->countSamples(); ++i) {
-        off64_t videoOffset;
-        uint32_t videoTime;
-        bool isSync;
-        CHECK_EQ((status_t)OK, videoSamples->getMetaDataForSample(
-                    i, &videoOffset, NULL, &videoTime, &isSync));
-
-        int64_t videoTimeUs = (int64_t)(videoTime * 1E6 / video->timescale);
-
-        uint32_t reqAudioTime = (videoTimeUs * audio->timescale) / 1000000;
-        uint32_t j;
-        if (audioSamples->findSampleAtTime(
-            reqAudioTime, &j, SampleTable::kFlagClosest) != OK) {
-            continue;
-        }
-
-        off64_t audioOffset;
-        uint32_t audioTime;
-        CHECK_EQ((status_t)OK, audioSamples->getMetaDataForSample(
-                    j, &audioOffset, NULL, &audioTime));
-
-        int64_t audioTimeUs = (int64_t)(audioTime * 1E6 / audio->timescale);
-
-        off64_t offsetDiff = videoOffset - audioOffset;
-        if (offsetDiff < 0) {
-            offsetDiff = -offsetDiff;
-        }
-
-#if 0
-        printf("%s%d/%d videoTime %.2f secs audioTime %.2f secs "
-               "videoOffset %lld audioOffset %lld offsetDiff %lld\n",
-               isSync ? "*" : " ",
-               i,
-               j,
-               videoTimeUs / 1E6,
-               audioTimeUs / 1E6,
-               videoOffset,
-               audioOffset,
-               offsetDiff);
-#endif
-
-        if (offsetDiff > maxOffsetDiff) {
-            maxOffsetDiff = offsetDiff;
-            maxOffsetTimeUs = videoTimeUs;
-        }
-    }
-
-#if 0
-    printf("max offset diff: %lld at video time: %.2f secs\n",
-           maxOffsetDiff, maxOffsetTimeUs / 1E6);
-#endif
-
-    if (maxOffsetDiff < 1024 * 1024) {
-        return OK;
-    }
-
-    LOGE("This content is not streamable, "
-         "max offset diff: %lld at video time: %.2f secs",
-         maxOffsetDiff, maxOffsetTimeUs / 1E6);
-
-    return ERROR_UNSUPPORTED;
-}
-
 static bool LegacySniffMPEG4(
         const sp<DataSource> &source, String8 *mimeType, float *confidence) {
     uint8_t header[8];
diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp
index c1aa46e..dc86885 100644
--- a/media/libstagefright/NuCachedSource2.cpp
+++ b/media/libstagefright/NuCachedSource2.cpp
@@ -323,25 +323,28 @@
 }
 
 void NuCachedSource2::restartPrefetcherIfNecessary_l(
-        bool ignoreLowWaterThreshold) {
+        bool ignoreLowWaterThreshold, bool force) {
     static const size_t kGrayArea = 1024 * 1024;
 
     if (mFetching || mFinalStatus != OK) {
         return;
     }
 
-    if (!ignoreLowWaterThreshold
+    if (!ignoreLowWaterThreshold && !force
             && mCacheOffset + mCache->totalSize() - mLastAccessPos
                 >= kLowWaterThreshold) {
         return;
     }
 
     size_t maxBytes = mLastAccessPos - mCacheOffset;
-    if (maxBytes < kGrayArea) {
-        return;
-    }
 
-    maxBytes -= kGrayArea;
+    if (!force) {
+        if (maxBytes < kGrayArea) {
+            return;
+        }
+
+        maxBytes -= kGrayArea;
+    }
 
     size_t actualBytes = mCache->releaseFromStart(maxBytes);
     mCacheOffset += actualBytes;
@@ -413,10 +416,19 @@
 }
 
 ssize_t NuCachedSource2::readInternal(off64_t offset, void *data, size_t size) {
+    CHECK_LE(size, (size_t)kHighWaterThreshold);
+
     LOGV("readInternal offset %lld size %d", offset, size);
 
     Mutex::Autolock autoLock(mLock);
 
+    if (!mFetching) {
+        mLastAccessPos = offset;
+        restartPrefetcherIfNecessary_l(
+                false, // ignoreLowWaterThreshold
+                true); // force
+    }
+
     if (offset < mCacheOffset
             || offset >= (off64_t)(mCacheOffset + mCache->totalSize())) {
         static const off64_t kPadding = 256 * 1024;
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 423df70..08db902 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -53,6 +53,7 @@
       mNumSampleSizes(0),
       mTimeToSampleCount(0),
       mTimeToSample(NULL),
+      mSampleTimeEntries(NULL),
       mCompositionTimeDeltaEntries(NULL),
       mNumCompositionTimeDeltaEntries(0),
       mSyncSampleOffset(-1),
@@ -73,6 +74,9 @@
     delete[] mCompositionTimeDeltaEntries;
     mCompositionTimeDeltaEntries = NULL;
 
+    delete[] mSampleTimeEntries;
+    mSampleTimeEntries = NULL;
+
     delete[] mTimeToSample;
     mTimeToSample = NULL;
 
@@ -381,67 +385,128 @@
     return time1 > time2 ? time1 - time2 : time2 - time1;
 }
 
-status_t SampleTable::findSampleAtTime(
-        uint32_t req_time, uint32_t *sample_index, uint32_t flags) {
-    // XXX this currently uses decoding time, instead of composition time.
+// static
+int SampleTable::CompareIncreasingTime(const void *_a, const void *_b) {
+    const SampleTimeEntry *a = (const SampleTimeEntry *)_a;
+    const SampleTimeEntry *b = (const SampleTimeEntry *)_b;
 
-    *sample_index = 0;
+    if (a->mCompositionTime < b->mCompositionTime) {
+        return -1;
+    } else if (a->mCompositionTime > b->mCompositionTime) {
+        return 1;
+    }
 
+    return 0;
+}
+
+void SampleTable::buildSampleEntriesTable() {
     Mutex::Autolock autoLock(mLock);
 
-    uint32_t cur_sample = 0;
-    uint32_t time = 0;
+    if (mSampleTimeEntries != NULL) {
+        return;
+    }
+
+    mSampleTimeEntries = new SampleTimeEntry[mNumSampleSizes];
+
+    uint32_t sampleIndex = 0;
+    uint32_t sampleTime = 0;
+
     for (uint32_t i = 0; i < mTimeToSampleCount; ++i) {
         uint32_t n = mTimeToSample[2 * i];
         uint32_t delta = mTimeToSample[2 * i + 1];
 
-        if (req_time < time + n * delta) {
-            int j = (req_time - time) / delta;
+        for (uint32_t j = 0; j < n; ++j) {
+            CHECK(sampleIndex < mNumSampleSizes);
 
-            uint32_t time1 = time + j * delta;
-            uint32_t time2 = time1 + delta;
+            mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex;
 
-            uint32_t sampleTime;
-            if (i+1 == mTimeToSampleCount
-                    || (abs_difference(req_time, time1)
-                        < abs_difference(req_time, time2))) {
-                *sample_index = cur_sample + j;
-                sampleTime = time1;
-            } else {
-                *sample_index = cur_sample + j + 1;
-                sampleTime = time2;
-            }
+            mSampleTimeEntries[sampleIndex].mCompositionTime =
+                sampleTime + getCompositionTimeOffset(sampleIndex);
 
-            switch (flags) {
-                case kFlagBefore:
-                {
-                    if (sampleTime > req_time && *sample_index > 0) {
-                        --*sample_index;
-                    }
-                    break;
-                }
-
-                case kFlagAfter:
-                {
-                    if (sampleTime < req_time
-                            && *sample_index + 1 < mNumSampleSizes) {
-                        ++*sample_index;
-                    }
-                    break;
-                }
-
-                default:
-                    break;
-            }
-
-            return OK;
+            ++sampleIndex;
+            sampleTime += delta;
         }
-
-        time += delta * n;
-        cur_sample += n;
     }
 
-    return ERROR_OUT_OF_RANGE;
+    qsort(mSampleTimeEntries, mNumSampleSizes, sizeof(SampleTimeEntry),
+          CompareIncreasingTime);
+}
+
+status_t SampleTable::findSampleAtTime(
+        uint32_t req_time, uint32_t *sample_index, uint32_t flags) {
+    buildSampleEntriesTable();
+
+    uint32_t left = 0;
+    uint32_t right = mNumSampleSizes;
+    while (left < right) {
+        uint32_t center = (left + right) / 2;
+        uint32_t centerTime = mSampleTimeEntries[center].mCompositionTime;
+
+        if (req_time < centerTime) {
+            right = center;
+        } else if (req_time > centerTime) {
+            left = center + 1;
+        } else {
+            left = center;
+            break;
+        }
+    }
+
+    if (left == mNumSampleSizes) {
+        --left;
+    }
+
+    uint32_t closestIndex = left;
+
+    switch (flags) {
+        case kFlagBefore:
+        {
+            while (closestIndex > 0
+                    && mSampleTimeEntries[closestIndex].mCompositionTime
+                            > req_time) {
+                --closestIndex;
+            }
+            break;
+        }
+
+        case kFlagAfter:
+        {
+            while (closestIndex + 1 < mNumSampleSizes
+                    && mSampleTimeEntries[closestIndex].mCompositionTime
+                            < req_time) {
+                ++closestIndex;
+            }
+            break;
+        }
+
+        default:
+        {
+            CHECK(flags == kFlagClosest);
+
+            if (closestIndex > 0) {
+                // Check left neighbour and pick closest.
+                uint32_t absdiff1 =
+                    abs_difference(
+                            mSampleTimeEntries[closestIndex].mCompositionTime,
+                            req_time);
+
+                uint32_t absdiff2 =
+                    abs_difference(
+                            mSampleTimeEntries[closestIndex - 1].mCompositionTime,
+                            req_time);
+
+                if (absdiff1 > absdiff2) {
+                    closestIndex = closestIndex - 1;
+                }
+            }
+
+            break;
+        }
+    }
+
+    *sample_index = mSampleTimeEntries[closestIndex].mSampleIndex;
+
+    return OK;
 }
 
 status_t SampleTable::findSyncSampleNear(
@@ -613,7 +678,7 @@
         uint32_t sampleIndex,
         off64_t *offset,
         size_t *size,
-        uint32_t *decodingTime,
+        uint32_t *compositionTime,
         bool *isSyncSample) {
     Mutex::Autolock autoLock(mLock);
 
@@ -630,8 +695,8 @@
         *size = mSampleIterator->getSampleSize();
     }
 
-    if (decodingTime) {
-        *decodingTime = mSampleIterator->getSampleTime();
+    if (compositionTime) {
+        *compositionTime = mSampleIterator->getSampleTime();
     }
 
     if (isSyncSample) {
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index d9ef208..3bd4c7e 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -92,8 +92,6 @@
 
     Track *findTrackByMimePrefix(const char *mimePrefix);
 
-    status_t verifyIfStreamable();
-
     MPEG4Extractor(const MPEG4Extractor &);
     MPEG4Extractor &operator=(const MPEG4Extractor &);
 };
diff --git a/media/libstagefright/include/NuCachedSource2.h b/media/libstagefright/include/NuCachedSource2.h
index 2128682..ed3e265c 100644
--- a/media/libstagefright/include/NuCachedSource2.h
+++ b/media/libstagefright/include/NuCachedSource2.h
@@ -96,7 +96,9 @@
     status_t seekInternal_l(off64_t offset);
 
     size_t approxDataRemaining_l(status_t *finalStatus);
-    void restartPrefetcherIfNecessary_l(bool ignoreLowWaterThreshold = false);
+
+    void restartPrefetcherIfNecessary_l(
+            bool ignoreLowWaterThreshold = false, bool force = false);
 
     DISALLOW_EVIL_CONSTRUCTORS(NuCachedSource2);
 };
diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h
index 2f95de9..f44e0a2 100644
--- a/media/libstagefright/include/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -63,7 +63,7 @@
             uint32_t sampleIndex,
             off64_t *offset,
             size_t *size,
-            uint32_t *decodingTime,
+            uint32_t *compositionTime,
             bool *isSyncSample = NULL);
 
     enum {
@@ -107,6 +107,12 @@
     uint32_t mTimeToSampleCount;
     uint32_t *mTimeToSample;
 
+    struct SampleTimeEntry {
+        uint32_t mSampleIndex;
+        uint32_t mCompositionTime;
+    };
+    SampleTimeEntry *mSampleTimeEntries;
+
     uint32_t *mCompositionTimeDeltaEntries;
     size_t mNumCompositionTimeDeltaEntries;
 
@@ -130,6 +136,10 @@
 
     uint32_t getCompositionTimeOffset(uint32_t sampleIndex) const;
 
+    static int CompareIncreasingTime(const void *, const void *);
+
+    void buildSampleEntriesTable();
+
     SampleTable(const SampleTable &);
     SampleTable &operator=(const SampleTable &);
 };
diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java
index 0935383..27d8976 100644
--- a/obex/javax/obex/ClientSession.java
+++ b/obex/javax/obex/ClientSession.java
@@ -449,8 +449,8 @@
                 maxPacketSize = (mInput.read() << 8) + mInput.read();
 
                 //check with local max size
-                if (maxPacketSize > ObexHelper.MAX_PACKET_SIZE_INT) {
-                    maxPacketSize = ObexHelper.MAX_PACKET_SIZE_INT;
+                if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
+                    maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
                 }
 
                 if (length > 7) {
diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java
index df0e0fb..8c12a20 100644
--- a/obex/javax/obex/ObexHelper.java
+++ b/obex/javax/obex/ObexHelper.java
@@ -70,6 +70,12 @@
      */
     public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
 
+    /**
+     * Temporary workaround to be able to push files to Windows 7.
+     * TODO: Should be removed as soon as Microsoft updates their driver.
+     */
+    public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
+
     public static final int OBEX_OPCODE_CONNECT = 0x80;
 
     public static final int OBEX_OPCODE_DISCONNECT = 0x81;
diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk
index 7d72729..0747efb 100644
--- a/opengl/libs/Android.mk
+++ b/opengl/libs/Android.mk
@@ -13,8 +13,8 @@
 	EGL/hooks.cpp 	       \
 	EGL/Loader.cpp 	       \
 #
-LOCAL_STATIC_LIBRARIES += libGLESv2_dbg libprotobuf-cpp-2.3.0-lite liblzf
-LOCAL_SHARED_LIBRARIES += libcutils libutils libstlport
+
+LOCAL_SHARED_LIBRARIES += libcutils libutils libGLESv2_dbg
 LOCAL_LDLIBS := -lpthread -ldl
 LOCAL_MODULE:= libEGL
 LOCAL_LDFLAGS += -Wl,--exclude-libs=ALL
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 6474c87..9cf7223 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -46,6 +46,7 @@
 #include "egl_impl.h"
 #include "Loader.h"
 #include "glesv2dbg.h"
+#include "egl_tls.h"
 
 #define setError(_e, _r) setErrorEtc(__FUNCTION__, __LINE__, _e, _r)
 
@@ -58,7 +59,7 @@
 static char const * const gVendorString     = "Android";
 static char const * const gVersionString    = "1.4 Android META-EGL";
 static char const * const gClientApiString  = "OpenGL ES";
-static char const * const gExtensionString  = 
+static char const * const gExtensionString  =
         "EGL_KHR_image "
         "EGL_KHR_image_base "
         "EGL_KHR_image_pixmap "
@@ -221,18 +222,15 @@
 struct egl_context_t : public egl_object_t
 {
     typedef egl_object_t::LocalRef<egl_context_t, EGLContext> Ref;
-    
+
     egl_context_t(EGLDisplay dpy, EGLContext context, EGLConfig config,
-            int impl, egl_connection_t const* cnx, int version) 
-    : egl_object_t(dpy), dpy(dpy), context(context), config(config), read(0), draw(0), 
-      impl(impl), cnx(cnx), version(version), dbg(NULL)
+            int impl, egl_connection_t const* cnx, int version)
+    : egl_object_t(dpy), dpy(dpy), context(context), config(config), read(0), draw(0),
+      impl(impl), cnx(cnx), version(version)
     {
     }
     ~egl_context_t()
     {
-        if (dbg)
-            DestroyDbgContext(dbg);
-        dbg = NULL;
     }
     EGLDisplay                  dpy;
     EGLContext                  context;
@@ -242,7 +240,6 @@
     int                         impl;
     egl_connection_t const*     cnx;
     int                         version;
-    DbgContext *                dbg;
 };
 
 struct egl_image_t : public egl_object_t
@@ -277,15 +274,6 @@
 typedef egl_image_t::Ref    ImageRef;
 typedef egl_sync_t::Ref     SyncRef;
 
-struct tls_t
-{
-    tls_t() : error(EGL_SUCCESS), ctx(0), logCallWithNoContext(EGL_TRUE) { }
-    EGLint      error;
-    EGLContext  ctx;
-    EGLBoolean  logCallWithNoContext;
-};
-
-
 // ----------------------------------------------------------------------------
 
 static egl_connection_t gEGLImpl[IMPL_NUM_IMPLEMENTATIONS];
@@ -323,7 +311,7 @@
     int propertyLevel = atoi(value);
     int applicationLevel = gEGLApplicationTraceLevel;
     gEGLTraceLevel = propertyLevel > applicationLevel ? propertyLevel : applicationLevel;
-    
+
     property_get("debug.egl.debug_proc", value, "");
     long pid = getpid();
     char procPath[128] = {};
@@ -336,14 +324,20 @@
         {
             if (!strcmp(value, cmdline))
                 gEGLDebugLevel = 1;
-        }    
+        }
         fclose(file);
     }
-    
+
     if (gEGLDebugLevel > 0)
     {
         property_get("debug.egl.debug_port", value, "5039");
-        StartDebugServer(atoi(value));
+        const unsigned short port = (unsigned short)atoi(value);
+        property_get("debug.egl.debug_forceUseFile", value, "0");
+        const bool forceUseFile = (bool)atoi(value);
+        property_get("debug.egl.debug_maxFileSize", value, "8");
+        const unsigned int maxFileSize = atoi(value) << 20;
+        property_get("debug.egl.debug_filePath", value, "/data/local/tmp/dump.gles2dbg");
+        StartDebugServer(port, forceUseFile, maxFileSize, value);
     }
 }
 
@@ -586,7 +580,7 @@
 }
 
 static inline
-egl_surface_t* get_surface(EGLSurface surface) {   
+egl_surface_t* get_surface(EGLSurface surface) {
     return egl_to_native_cast<egl_surface_t>(surface);
 }
 
@@ -595,11 +589,6 @@
     return egl_to_native_cast<egl_context_t>(context);
 }
 
-DbgContext * getDbgContextThreadSpecific()
-{
-    return get_context(getContext())->dbg;
-}
-
 static inline
 egl_image_t* get_image(EGLImageKHR image) {
     return egl_to_native_cast<egl_image_t>(image);
@@ -1442,10 +1431,12 @@
         loseCurrent(cur_c);
 
         if (ctx != EGL_NO_CONTEXT) {
-            if (!c->dbg && gEGLDebugLevel > 0)
-                c->dbg = CreateDbgContext(c->version, c->cnx->hooks[c->version]);
             setGLHooksThreadSpecific(c->cnx->hooks[c->version]);
             setContext(ctx);
+            tls_t * const tls = getTLS();
+            if (!tls->dbg && gEGLDebugLevel > 0)
+                tls->dbg = CreateDbgContext(gEGLThreadLocalStorageKey, c->version,
+                                            c->cnx->hooks[c->version]);
             _c.acquire();
             _r.acquire();
             _d.acquire();
diff --git a/opengl/libs/GLES2_dbg/Android.mk b/opengl/libs/GLES2_dbg/Android.mk
index fc40799..9f6e68c 100644
--- a/opengl/libs/GLES2_dbg/Android.mk
+++ b/opengl/libs/GLES2_dbg/Android.mk
@@ -11,7 +11,7 @@
     src/server.cpp \
     src/vertex.cpp
 
-LOCAL_C_INCLUDES :=	\
+LOCAL_C_INCLUDES := \
     $(LOCAL_PATH) \
     $(LOCAL_PATH)/../ \
     external/stlport/stlport \
@@ -21,7 +21,8 @@
 
 #LOCAL_CFLAGS += -O0 -g -DDEBUG -UNDEBUG
 LOCAL_CFLAGS := -DGOOGLE_PROTOBUF_NO_RTTI
-
+LOCAL_STATIC_LIBRARIES := libprotobuf-cpp-2.3.0-lite liblzf
+LOCAL_SHARED_LIBRARIES := libcutils libutils libstlport
 ifeq ($(TARGET_ARCH),arm)
 	LOCAL_CFLAGS += -fstrict-aliasing
 endif
@@ -43,4 +44,6 @@
 LOCAL_MODULE:= libGLESv2_dbg
 LOCAL_MODULE_TAGS := optional
 
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_SHARED_LIBRARY)
+
+include $(LOCAL_PATH)/test/Android.mk
diff --git a/opengl/libs/GLES2_dbg/generate_api_cpp.py b/opengl/libs/GLES2_dbg/generate_api_cpp.py
index 66c110f..aeba213 100755
--- a/opengl/libs/GLES2_dbg/generate_api_cpp.py
+++ b/opengl/libs/GLES2_dbg/generate_api_cpp.py
@@ -26,36 +26,36 @@
         return line.replace(annotation, "*")
     else:
         return line
-    
+
 def generate_api(lines):
     externs = []
     i = 0
     # these have been hand written
-    skipFunctions = ["glReadPixels", "glDrawArrays", "glDrawElements"]
-    
+    skipFunctions = ["glDrawArrays", "glDrawElements"]
+
     # these have an EXTEND_Debug_* macro for getting data
-    extendFunctions = ["glCopyTexImage2D", "glCopyTexSubImage2D", "glShaderSource",
-"glTexImage2D", "glTexSubImage2D"]
-    
+    extendFunctions = ["glCopyTexImage2D", "glCopyTexSubImage2D", "glReadPixels",
+"glShaderSource", "glTexImage2D", "glTexSubImage2D"]
+
     # these also needs to be forwarded to DbgContext
     contextFunctions = ["glUseProgram", "glEnableVertexAttribArray", "glDisableVertexAttribArray", 
 "glVertexAttribPointer", "glBindBuffer", "glBufferData", "glBufferSubData", "glDeleteBuffers",]
-    
+
     for line in lines:
         if line.find("API_ENTRY(") >= 0: # a function prototype
             returnType = line[0: line.find(" API_ENTRY(")]
             functionName = line[line.find("(") + 1: line.find(")")] #extract GL function name
             parameterList = line[line.find(")(") + 2: line.find(") {")]
-            
+
             #if line.find("*") >= 0:
             #    extern = "%s Debug_%s(%s);" % (returnType, functionName, parameterList)
             #    externs.append(extern)
             #    continue
-            
+
             if functionName in skipFunctions:
                 sys.stderr.write("!\n! skipping function '%s'\n!\n" % (functionName))
                 continue
-                
+
             parameters = parameterList.split(',')
             paramIndex = 0
             if line.find("*") >= 0 and (line.find("*") < line.find(":") or line.find("*") > line.rfind(":")): # unannotated pointer
@@ -65,21 +65,21 @@
                     sys.stderr.write("%s should be hand written\n" % (extern))
                     print "// FIXME: this function has pointers, it should be hand written"
                     externs.append(extern)
-                
+
             print "%s Debug_%s(%s)\n{" % (returnType, functionName, RemoveAnnotation(parameterList))
             print "    glesv2debugger::Message msg;"
-    
+
             if parameterList == "void":
                 parameters = []
             arguments = ""
             paramNames = []
             inout = ""
             getData = ""
-            
+
             callerMembers = ""
             setCallerMembers = ""
             setMsgParameters = ""
-            
+
             for parameter in parameters:
                 const = parameter.find("const")
                 parameter = parameter.replace("const", "")
@@ -107,7 +107,7 @@
                         annotation = "strlen(%s)" % (paramName)
                     else:
                         count = int(annotation)
-            
+
                     setMsgParameters += "    msg.set_arg%d(ToInt(%s));\n" % (paramIndex, paramName)
                     if paramType.find("void") >= 0:
                         getData += "    msg.mutable_data()->assign(reinterpret_cast<const char *>(%s), %s * sizeof(char));" % (paramName, annotation)
@@ -127,7 +127,7 @@
                 paramIndex += 1
                 callerMembers += "        %s %s;\n" % (paramType, paramName)
                 setCallerMembers += "    caller.%s = %s;\n" % (paramName, paramName)
-            
+
             print "    struct : public FunctionCall {"
             print callerMembers
             print "        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {"
@@ -141,6 +141,11 @@
             if inout in ["out", "inout"]:
                 print "            msg.set_time((systemTime(timeMode) - c0) * 1e-6f);"
                 print "        " + getData
+            if functionName in extendFunctions:
+                print "\
+#ifdef EXTEND_AFTER_CALL_Debug_%s\n\
+            EXTEND_AFTER_CALL_Debug_%s;\n\
+#endif" % (functionName, functionName)
             if functionName in contextFunctions:
                 print "            getDbgContextThreadSpecific()->%s(%s);" % (functionName, arguments)
             if returnType == "void":
@@ -157,7 +162,10 @@
             if inout in ["in", "inout"]:
                 print getData
             if functionName in extendFunctions:
-                print "    EXTEND_Debug_%s;" % (functionName) 
+                print "\
+#ifdef EXTEND_Debug_%s\n\
+    EXTEND_Debug_%s;\n\
+#endif" % (functionName, functionName)
             print "    int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_%s);"\
                 % (functionName)
             if returnType != "void":
@@ -166,8 +174,8 @@
                 else:
                     print "    return reinterpret_cast<%s>(ret);" % (returnType)
             print "}\n"
-                        
-            
+
+
     print "// FIXME: the following functions should be written by hand"
     for extern in externs:
         print extern
@@ -189,18 +197,23 @@
  ** See the License for the specific language governing permissions and
  ** limitations under the License.
  */
- 
+
 // auto generated by generate_api_cpp.py
 
+#include <utils/Debug.h>
+
 #include "src/header.h"
 #include "src/api.h"
 
-template<typename T> static int ToInt(const T & t) { STATIC_ASSERT(sizeof(T) == sizeof(int), bitcast); return (int &)t; }
-template<typename T> static T FromInt(const int & t) { STATIC_ASSERT(sizeof(T) == sizeof(int), bitcast); return (T &)t; }
-"""    
+template<typename T> static int ToInt(const T & t)
+{
+    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(sizeof(T) == sizeof(int));
+    return (int &)t;
+}
+"""
     lines = open("gl2_api_annotated.in").readlines()
     generate_api(lines)
     #lines = open("gl2ext_api.in").readlines()
     #generate_api(lines)
-            
+
 
diff --git a/opengl/libs/GLES2_dbg/generate_caller_cpp.py b/opengl/libs/GLES2_dbg/generate_caller_cpp.py
index eac2292..ee4208d 100755
--- a/opengl/libs/GLES2_dbg/generate_caller_cpp.py
+++ b/opengl/libs/GLES2_dbg/generate_caller_cpp.py
@@ -177,7 +177,6 @@
 {
     LOGD("GenerateCall function=%u", cmd.function());
     const int * ret = prevRet; // only some functions have return value
-    gl_hooks_t::gl_t const * const _c = &getGLTraceThreadSpecific()->gl;
     nsecs_t c0 = systemTime(timeMode);
     switch (cmd.function()) {""")
     
diff --git a/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py b/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
index 466c447..57e008c 100755
--- a/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
+++ b/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
@@ -70,41 +70,43 @@
 """)
 
     i = 0;
-    
+
     lines = open("gl2_api_annotated.in").readlines()
     i = generate_gl_entries(output, lines, i)
     output.write("        // end of GL functions\n")
-    
+
     #lines = open("gl2ext_api.in").readlines()
     #i = generate_gl_entries(output, lines, i)
     #output.write("        // end of GL EXT functions\n")
-    
+
     lines = open("../EGL/egl_entries.in").readlines()
     i = generate_egl_entries(output, lines, i)
     output.write("        // end of GL EXT functions\n")
-    
+
     output.write("        ACK = %d;\n" % (i))
     i += 1
-    
+
     output.write("        NEG = %d;\n" % (i))
     i += 1
-    
+
     output.write("        CONTINUE = %d;\n" % (i))
     i += 1
-    
+
     output.write("        SKIP = %d;\n" % (i))
     i += 1
-    
+
     output.write("        SETPROP = %d;\n" % (i))
     i += 1
-    
+
     output.write("""    }
     required Function function = 2 [default = NEG]; // type/function of message
     enum Type
     {
         BeforeCall = 0;
         AfterCall = 1;
-        Response = 2; // currently used for misc messages
+        AfterGeneratedCall = 2;
+        Response = 3; // currently used for misc messages
+        CompleteCall = 4; // BeforeCall and AfterCall merged
     }
     required Type type = 3;
     required bool expect_response = 4;
@@ -125,16 +127,22 @@
         ReferencedImage = 0; // for image sourced from ReadPixels
         NonreferencedImage = 1; // for image sourced from ReadPixels
     };
-    optional DataType data_type = 23; // most data types can be inferred from function
-    optional int32 pixel_format = 24; // used for image data if format and type 
-    optional int32 pixel_type = 25;   //     cannot be determined from arg 
-    
+    // most data types can be inferred from function
+    optional DataType data_type = 23;
+    // these are used for image data when they cannot be determined from args
+    optional int32 pixel_format = 24;
+    optional int32 pixel_type = 25;
+    optional int32 image_width = 26;
+    optional int32 image_height = 27;
+
     optional float time = 11; // duration of previous GL call (ms)
     enum Prop
     {
-        Capture = 0; // arg0 = true | false
+        CaptureDraw = 0; // arg0 = number of glDrawArrays/Elements to glReadPixels
         TimeMode = 1; // arg0 = SYSTEM_TIME_* in utils/Timers.h
         ExpectResponse = 2; // arg0 = enum Function, arg1 = true/false
+        CaptureSwap = 3; // arg0 = number of eglSwapBuffers to glReadPixels
+        GLConstant = 4; // arg0 = GLenum, arg1 = constant; send GL impl. constants
     };
     optional Prop prop = 21; // used with SETPROP, value in arg0
     optional float clock = 22; // wall clock in seconds
@@ -142,6 +150,6 @@
 """)
 
     output.close()
-    
+
     os.system("aprotoc --cpp_out=src --java_out=../../../../../development/tools/glesv2debugger/src debugger_message.proto")
     os.system('mv -f "src/debugger_message.pb.cc" "src/debugger_message.pb.cpp"')
diff --git a/opengl/libs/GLES2_dbg/src/api.cpp b/opengl/libs/GLES2_dbg/src/api.cpp
index 130ca7e..c483547 100644
--- a/opengl/libs/GLES2_dbg/src/api.cpp
+++ b/opengl/libs/GLES2_dbg/src/api.cpp
@@ -16,11 +16,16 @@
  
 // auto generated by generate_api_cpp.py
 
+#include <utils/Debug.h>
+
 #include "src/header.h"
 #include "src/api.h"
 
-template<typename T> static int ToInt(const T & t) { STATIC_ASSERT(sizeof(T) == sizeof(int), bitcast); return (int &)t; }
-template<typename T> static T FromInt(const int & t) { STATIC_ASSERT(sizeof(T) == sizeof(int), bitcast); return (T &)t; }
+template<typename T> static int ToInt(const T & t)
+{
+    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(sizeof(T) == sizeof(int));
+    return (int &)t;
+}
 
 void Debug_glActiveTexture(GLenum texture)
 {
@@ -592,6 +597,9 @@
 
         const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
             _c->glCopyTexImage2D(target, level, internalformat, x, y, width, height, border);
+#ifdef EXTEND_AFTER_CALL_Debug_glCopyTexImage2D
+            EXTEND_AFTER_CALL_Debug_glCopyTexImage2D;
+#endif
             return 0;
         }
     } caller;
@@ -613,7 +621,9 @@
     msg.set_arg6(height);
     msg.set_arg7(border);
 
+#ifdef EXTEND_Debug_glCopyTexImage2D
     EXTEND_Debug_glCopyTexImage2D;
+#endif
     int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glCopyTexImage2D);
 }
 
@@ -632,6 +642,9 @@
 
         const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
             _c->glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
+#ifdef EXTEND_AFTER_CALL_Debug_glCopyTexSubImage2D
+            EXTEND_AFTER_CALL_Debug_glCopyTexSubImage2D;
+#endif
             return 0;
         }
     } caller;
@@ -653,7 +666,9 @@
     msg.set_arg6(width);
     msg.set_arg7(height);
 
+#ifdef EXTEND_Debug_glCopyTexSubImage2D
     EXTEND_Debug_glCopyTexSubImage2D;
+#endif
     int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glCopyTexSubImage2D);
 }
 
@@ -2164,6 +2179,49 @@
     int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glPolygonOffset);
 }
 
+void Debug_glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
+{
+    glesv2debugger::Message msg;
+    struct : public FunctionCall {
+        GLint x;
+        GLint y;
+        GLsizei width;
+        GLsizei height;
+        GLenum format;
+        GLenum type;
+        GLvoid* pixels;
+
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            _c->glReadPixels(x, y, width, height, format, type, pixels);
+#ifdef EXTEND_AFTER_CALL_Debug_glReadPixels
+            EXTEND_AFTER_CALL_Debug_glReadPixels;
+#endif
+            return 0;
+        }
+    } caller;
+    caller.x = x;
+    caller.y = y;
+    caller.width = width;
+    caller.height = height;
+    caller.format = format;
+    caller.type = type;
+    caller.pixels = pixels;
+
+    msg.set_arg0(x);
+    msg.set_arg1(y);
+    msg.set_arg2(width);
+    msg.set_arg3(height);
+    msg.set_arg4(format);
+    msg.set_arg5(type);
+    msg.set_arg6(ToInt(pixels));
+
+    // FIXME: check for pointer usage
+#ifdef EXTEND_Debug_glReadPixels
+    EXTEND_Debug_glReadPixels;
+#endif
+    int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glReadPixels);
+}
+
 void Debug_glReleaseShaderCompiler(void)
 {
     glesv2debugger::Message msg;
@@ -2297,6 +2355,9 @@
 
         const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
             _c->glShaderSource(shader, count, string, length);
+#ifdef EXTEND_AFTER_CALL_Debug_glShaderSource
+            EXTEND_AFTER_CALL_Debug_glShaderSource;
+#endif
             return 0;
         }
     } caller;
@@ -2311,7 +2372,9 @@
     msg.set_arg3(ToInt(length));
 
     // FIXME: check for pointer usage
+#ifdef EXTEND_Debug_glShaderSource
     EXTEND_Debug_glShaderSource;
+#endif
     int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glShaderSource);
 }
 
@@ -2472,6 +2535,9 @@
 
         const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
             _c->glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
+#ifdef EXTEND_AFTER_CALL_Debug_glTexImage2D
+            EXTEND_AFTER_CALL_Debug_glTexImage2D;
+#endif
             return 0;
         }
     } caller;
@@ -2496,7 +2562,9 @@
     msg.set_arg8(ToInt(pixels));
 
     // FIXME: check for pointer usage
+#ifdef EXTEND_Debug_glTexImage2D
     EXTEND_Debug_glTexImage2D;
+#endif
     int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glTexImage2D);
 }
 
@@ -2616,6 +2684,9 @@
 
         const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
             _c->glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
+#ifdef EXTEND_AFTER_CALL_Debug_glTexSubImage2D
+            EXTEND_AFTER_CALL_Debug_glTexSubImage2D;
+#endif
             return 0;
         }
     } caller;
@@ -2640,7 +2711,9 @@
     msg.set_arg8(ToInt(pixels));
 
     // FIXME: check for pointer usage
+#ifdef EXTEND_Debug_glTexSubImage2D
     EXTEND_Debug_glTexSubImage2D;
+#endif
     int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glTexSubImage2D);
 }
 
diff --git a/opengl/libs/GLES2_dbg/src/api.h b/opengl/libs/GLES2_dbg/src/api.h
index b9fc341..0b227bc 100644
--- a/opengl/libs/GLES2_dbg/src/api.h
+++ b/opengl/libs/GLES2_dbg/src/api.h
@@ -16,19 +16,29 @@
 
 #define EXTEND_Debug_glCopyTexImage2D \
     DbgContext * const dbg = getDbgContextThreadSpecific(); \
-    GLint readFormat, readType; \
-    dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat); \
-    dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType); \
-    unsigned readSize = GetBytesPerPixel(readFormat, readType) * width * height; \
-    void * readData = dbg->GetReadPixelsBuffer(readSize); \
-    dbg->hooks->gl.glReadPixels(x, y, width, height, readFormat, readType, readData); \
+    void * readData = dbg->GetReadPixelsBuffer(4 * width * height); \
+    /* pick easy format for client to convert */ \
+    dbg->hooks->gl.glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, readData); \
     dbg->CompressReadPixelBuffer(msg.mutable_data()); \
     msg.set_data_type(msg.ReferencedImage); \
-    msg.set_pixel_format(readFormat); \
-    msg.set_pixel_type(readType);
+    msg.set_pixel_format(GL_RGBA); \
+    msg.set_pixel_type(GL_UNSIGNED_BYTE);
 
 #define EXTEND_Debug_glCopyTexSubImage2D EXTEND_Debug_glCopyTexImage2D
 
+#define EXTEND_AFTER_CALL_Debug_glReadPixels \
+    { \
+        DbgContext * const dbg = getDbgContextThreadSpecific(); \
+        if (dbg->IsReadPixelBuffer(pixels)) { \
+            dbg->CompressReadPixelBuffer(msg.mutable_data()); \
+            msg.set_data_type(msg.ReferencedImage); \
+        } else { \
+            const unsigned int size = width * height * GetBytesPerPixel(format, type); \
+            dbg->Compress(pixels, size, msg.mutable_data()); \
+            msg.set_data_type(msg.NonreferencedImage); \
+        } \
+    }
+
 #define EXTEND_Debug_glShaderSource \
     std::string * const data = msg.mutable_data(); \
     for (unsigned i = 0; i < count; i++) \
diff --git a/opengl/libs/GLES2_dbg/src/caller.cpp b/opengl/libs/GLES2_dbg/src/caller.cpp
index 9992f05..6b72751 100644
--- a/opengl/libs/GLES2_dbg/src/caller.cpp
+++ b/opengl/libs/GLES2_dbg/src/caller.cpp
@@ -105,7 +105,6 @@
 {
     LOGD("GenerateCall function=%u", cmd.function());
     const int * ret = prevRet; // only some functions have return value
-    gl_hooks_t::gl_t const * const _c = &getGLTraceThreadSpecific()->gl;
     nsecs_t c0 = systemTime(timeMode);
     switch (cmd.function()) {    case glesv2debugger::Message_Function_glActiveTexture:
         dbg->hooks->gl.glActiveTexture(
@@ -772,7 +771,7 @@
     msg.set_time((systemTime(timeMode) - c0) * 1e-6f);
     msg.set_context_id(reinterpret_cast<int>(dbg));
     msg.set_function(cmd.function());
-    msg.set_type(glesv2debugger::Message_Type_AfterCall);
+    msg.set_type(glesv2debugger::Message_Type_AfterGeneratedCall);
     return ret;
 }
 
diff --git a/opengl/libs/GLES2_dbg/src/caller.h b/opengl/libs/GLES2_dbg/src/caller.h
index 5447757..e8111b3 100644
--- a/opengl/libs/GLES2_dbg/src/caller.h
+++ b/opengl/libs/GLES2_dbg/src/caller.h
@@ -138,7 +138,9 @@
         const glesv2debugger::Message & cmd,
         glesv2debugger::Message & msg, const int * const prevRet)
 {
-    assert(0);
+    GLint params = -1;
+    dbg->hooks->gl.glGetProgramiv(cmd.arg0(), cmd.arg1(), &params);
+    msg.mutable_data()->append(reinterpret_cast<char *>(&params), sizeof(params));
     return prevRet;
 }
 
@@ -146,7 +148,10 @@
         const glesv2debugger::Message & cmd,
         glesv2debugger::Message & msg, const int * const prevRet)
 {
-    assert(0);
+    const GLsizei bufSize = static_cast<GLsizei>(dbg->GetBufferSize());
+    GLsizei length = -1;
+    dbg->hooks->gl.glGetProgramInfoLog(cmd.arg0(), bufSize, &length, dbg->GetBuffer());
+    msg.mutable_data()->append(dbg->GetBuffer(), length);
     return prevRet;
 }
 
@@ -162,7 +167,9 @@
                                         const glesv2debugger::Message & cmd,
                                         glesv2debugger::Message & msg, const int * const prevRet)
 {
-    assert(0);
+    GLint params = -1;
+    dbg->hooks->gl.glGetShaderiv(cmd.arg0(), cmd.arg1(), &params);
+    msg.mutable_data()->append(reinterpret_cast<char *>(&params), sizeof(params));
     return prevRet;
 }
 
@@ -170,7 +177,10 @@
         const glesv2debugger::Message & cmd,
         glesv2debugger::Message & msg, const int * const prevRet)
 {
-    assert(0);
+    const GLsizei bufSize = static_cast<GLsizei>(dbg->GetBufferSize());
+    GLsizei length = -1;
+    dbg->hooks->gl.glGetShaderInfoLog(cmd.arg0(), bufSize, &length, dbg->GetBuffer());
+    msg.mutable_data()->append(dbg->GetBuffer(), length);
     return prevRet;
 }
 
diff --git a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
index cc7336c..7f5b27b 100644
--- a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
+++ b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "header.h"
+#include "egl_tls.h"
 
 extern "C"
 {
@@ -24,11 +25,23 @@
 namespace android
 {
 
+pthread_key_t dbgEGLThreadLocalStorageKey = -1;
+
+DbgContext * getDbgContextThreadSpecific()
+{
+    tls_t* tls = (tls_t*)pthread_getspecific(dbgEGLThreadLocalStorageKey);
+    return tls->dbg;
+}
+
 DbgContext::DbgContext(const unsigned version, const gl_hooks_t * const hooks,
-                       const unsigned MAX_VERTEX_ATTRIBS)
+                       const unsigned MAX_VERTEX_ATTRIBS, const GLenum readFormat,
+                       const GLenum readType)
         : lzf_buf(NULL), lzf_readIndex(0), lzf_refSize(0), lzf_refBufSize(0)
         , version(version), hooks(hooks)
         , MAX_VERTEX_ATTRIBS(MAX_VERTEX_ATTRIBS)
+        , readFormat(readFormat), readType(readType)
+        , readBytesPerPixel(GetBytesPerPixel(readFormat, readType))
+        , captureSwap(0), captureDraw(0)
         , vertexAttribs(new VertexAttrib[MAX_VERTEX_ATTRIBS])
         , hasNonVBOAttribs(false), indexBuffers(NULL), indexBuffer(NULL)
         , program(0), maxAttrib(0)
@@ -47,13 +60,35 @@
     free(lzf_ref[1]);
 }
 
-DbgContext * CreateDbgContext(const unsigned version, const gl_hooks_t * const hooks)
+DbgContext * CreateDbgContext(const pthread_key_t EGLThreadLocalStorageKey,
+                              const unsigned version, const gl_hooks_t * const hooks)
 {
+    dbgEGLThreadLocalStorageKey = EGLThreadLocalStorageKey;
     assert(version < 2);
     assert(GL_NO_ERROR == hooks->gl.glGetError());
     GLint MAX_VERTEX_ATTRIBS = 0;
     hooks->gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &MAX_VERTEX_ATTRIBS);
-    return new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS);
+    GLint readFormat, readType;
+    hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
+    hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
+    DbgContext * const dbg = new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS, readFormat, readType);
+
+    glesv2debugger::Message msg, cmd;
+    msg.set_context_id(reinterpret_cast<int>(dbg));
+    msg.set_expect_response(false);
+    msg.set_type(msg.Response);
+    msg.set_function(msg.SETPROP);
+    msg.set_prop(msg.GLConstant);
+    msg.set_arg0(GL_MAX_VERTEX_ATTRIBS);
+    msg.set_arg1(MAX_VERTEX_ATTRIBS);
+    Send(msg, cmd);
+
+    GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0;
+    hooks->gl.glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+    msg.set_arg0(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+    msg.set_arg1(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+    Send(msg, cmd);
+    return dbg;
 }
 
 void DestroyDbgContext(DbgContext * const dbg)
@@ -113,6 +148,7 @@
 {
     if (!lzf_buf)
         lzf_buf = (char *)malloc(LZF_CHUNK_SIZE);
+    assert(lzf_buf);
     const uint32_t totalDecompSize = in_len;
     outStr->append((const char *)&totalDecompSize, sizeof(totalDecompSize));
     for (unsigned int i = 0; i < in_len; i += LZF_CHUNK_SIZE) {
@@ -130,13 +166,46 @@
     }
 }
 
+unsigned char * DbgContext::Decompress(const void * in, const unsigned int inLen,
+                                       unsigned int * const outLen)
+{
+    assert(inLen > 4 * 3);
+    if (inLen < 4 * 3)
+        return NULL;
+    *outLen = *(uint32_t *)in;
+    unsigned char * const out = (unsigned char *)malloc(*outLen);
+    unsigned int outPos = 0;
+    const unsigned char * const end = (const unsigned char *)in + inLen;
+    for (const unsigned char * inData = (const unsigned char *)in + 4; inData < end; ) {
+        const uint32_t chunkOut = *(uint32_t *)inData;
+        inData += 4;
+        const uint32_t chunkIn = *(uint32_t *)inData;
+        inData += 4;
+        if (chunkIn > 0) {
+            assert(inData + chunkIn <= end);
+            assert(outPos + chunkOut <= *outLen);
+            outPos += lzf_decompress(inData, chunkIn, out + outPos, chunkOut);
+            inData += chunkIn;
+        } else {
+            assert(inData + chunkOut <= end);
+            assert(outPos + chunkOut <= *outLen);
+            memcpy(out + outPos, inData, chunkOut);
+            inData += chunkOut;
+            outPos += chunkOut;
+        }
+    }
+    return out;
+}
+
 void * DbgContext::GetReadPixelsBuffer(const unsigned size)
 {
     if (lzf_refBufSize < size + 8) {
         lzf_refBufSize = size + 8;
         lzf_ref[0] = (unsigned *)realloc(lzf_ref[0], lzf_refBufSize);
+        assert(lzf_ref[0]);
         memset(lzf_ref[0], 0, lzf_refBufSize);
         lzf_ref[1] = (unsigned *)realloc(lzf_ref[1], lzf_refBufSize);
+        assert(lzf_ref[1]);
         memset(lzf_ref[1], 0, lzf_refBufSize);
     }
     if (lzf_refSize != size) // need to clear unused ref to maintain consistency
@@ -151,6 +220,7 @@
 
 void DbgContext::CompressReadPixelBuffer(std::string * const outStr)
 {
+    assert(lzf_ref[0] && lzf_ref[1]);
     unsigned * const ref = lzf_ref[lzf_readIndex ^ 1];
     unsigned * const src = lzf_ref[lzf_readIndex];
     for (unsigned i = 0; i < lzf_refSize / sizeof(*ref) + 1; i++)
@@ -158,13 +228,34 @@
     Compress(ref, lzf_refSize, outStr);
 }
 
+char * DbgContext::GetBuffer()
+{
+    if (!lzf_buf)
+        lzf_buf = (char *)malloc(LZF_CHUNK_SIZE);
+    assert(lzf_buf);
+    return lzf_buf;
+}
+
+unsigned int DbgContext::GetBufferSize()
+{
+    if (!lzf_buf)
+        lzf_buf = (char *)malloc(LZF_CHUNK_SIZE);
+    assert(lzf_buf);
+    if (lzf_buf)
+        return LZF_CHUNK_SIZE;
+    else
+        return 0;
+}
+
 void DbgContext::glUseProgram(GLuint program)
 {
     while (GLenum error = hooks->gl.glGetError())
-        LOGD("DbgContext::glUseProgram: before glGetError() = 0x%.4X", error);
-
+        LOGD("DbgContext::glUseProgram(%u): before glGetError() = 0x%.4X",
+             program, error);
     this->program = program;
-
+    maxAttrib = 0;
+    if (program == 0)
+        return;
     GLint activeAttributes = 0;
     hooks->gl.glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &activeAttributes);
     maxAttrib = 0;
@@ -202,9 +293,9 @@
             maxAttrib = slot;
     }
     delete name;
-
     while (GLenum error = hooks->gl.glGetError())
-        LOGD("DbgContext::glUseProgram: after glGetError() = 0x%.4X", error);
+        LOGD("DbgContext::glUseProgram(%u): after glGetError() = 0x%.4X",
+             program, error);
 }
 
 static bool HasNonVBOAttribs(const DbgContext * const ctx)
@@ -254,14 +345,16 @@
 
 void DbgContext::glEnableVertexAttribArray(GLuint index)
 {
-    assert(index < MAX_VERTEX_ATTRIBS);
+    if (index >= MAX_VERTEX_ATTRIBS)
+        return;
     vertexAttribs[index].enabled = true;
     hasNonVBOAttribs = HasNonVBOAttribs(this);
 }
 
 void DbgContext::glDisableVertexAttribArray(GLuint index)
 {
-    assert(index < MAX_VERTEX_ATTRIBS);
+    if (index >= MAX_VERTEX_ATTRIBS)
+        return;
     vertexAttribs[index].enabled = false;
     hasNonVBOAttribs = HasNonVBOAttribs(this);
 }
diff --git a/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp b/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
index 046c954..50f70f7 100644
--- a/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
+++ b/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
@@ -436,6 +436,8 @@
     case 0:
     case 1:
     case 2:
+    case 3:
+    case 4:
       return true;
     default:
       return false;
@@ -445,7 +447,9 @@
 #ifndef _MSC_VER
 const Message_Type Message::BeforeCall;
 const Message_Type Message::AfterCall;
+const Message_Type Message::AfterGeneratedCall;
 const Message_Type Message::Response;
+const Message_Type Message::CompleteCall;
 const Message_Type Message::Type_MIN;
 const Message_Type Message::Type_MAX;
 const int Message::Type_ARRAYSIZE;
@@ -472,6 +476,8 @@
     case 0:
     case 1:
     case 2:
+    case 3:
+    case 4:
       return true;
     default:
       return false;
@@ -479,9 +485,11 @@
 }
 
 #ifndef _MSC_VER
-const Message_Prop Message::Capture;
+const Message_Prop Message::CaptureDraw;
 const Message_Prop Message::TimeMode;
 const Message_Prop Message::ExpectResponse;
+const Message_Prop Message::CaptureSwap;
+const Message_Prop Message::GLConstant;
 const Message_Prop Message::Prop_MIN;
 const Message_Prop Message::Prop_MAX;
 const int Message::Prop_ARRAYSIZE;
@@ -506,6 +514,8 @@
 const int Message::kDataTypeFieldNumber;
 const int Message::kPixelFormatFieldNumber;
 const int Message::kPixelTypeFieldNumber;
+const int Message::kImageWidthFieldNumber;
+const int Message::kImageHeightFieldNumber;
 const int Message::kTimeFieldNumber;
 const int Message::kPropFieldNumber;
 const int Message::kClockFieldNumber;
@@ -545,6 +555,8 @@
   data_type_ = 0;
   pixel_format_ = 0;
   pixel_type_ = 0;
+  image_width_ = 0;
+  image_height_ = 0;
   time_ = 0;
   prop_ = 0;
   clock_ = 0;
@@ -606,6 +618,8 @@
   if (_has_bits_[16 / 32] & (0xffu << (16 % 32))) {
     pixel_format_ = 0;
     pixel_type_ = 0;
+    image_width_ = 0;
+    image_height_ = 0;
     time_ = 0;
     prop_ = 0;
     clock_ = 0;
@@ -790,7 +804,7 @@
           DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
                    float, ::google::protobuf::internal::WireFormatLite::TYPE_FLOAT>(
                  input, &time_)));
-          _set_bit(18);
+          _set_bit(20);
         } else {
           goto handle_uninterpreted;
         }
@@ -905,7 +919,7 @@
           DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
                    float, ::google::protobuf::internal::WireFormatLite::TYPE_FLOAT>(
                  input, &clock_)));
-          _set_bit(20);
+          _set_bit(22);
         } else {
           goto handle_uninterpreted;
         }
@@ -960,6 +974,38 @@
         } else {
           goto handle_uninterpreted;
         }
+        if (input->ExpectTag(208)) goto parse_image_width;
+        break;
+      }
+      
+      // optional int32 image_width = 26;
+      case 26: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
+         parse_image_width:
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   ::google::protobuf::int32, ::google::protobuf::internal::WireFormatLite::TYPE_INT32>(
+                 input, &image_width_)));
+          _set_bit(18);
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectTag(216)) goto parse_image_height;
+        break;
+      }
+      
+      // optional int32 image_height = 27;
+      case 27: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
+         parse_image_height:
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   ::google::protobuf::int32, ::google::protobuf::internal::WireFormatLite::TYPE_INT32>(
+                 input, &image_height_)));
+          _set_bit(19);
+        } else {
+          goto handle_uninterpreted;
+        }
         if (input->ExpectAtEnd()) return true;
         break;
       }
@@ -1035,7 +1081,7 @@
   }
   
   // optional float time = 11;
-  if (_has_bit(18)) {
+  if (_has_bit(20)) {
     ::google::protobuf::internal::WireFormatLite::WriteFloat(11, this->time(), output);
   }
   
@@ -1065,13 +1111,13 @@
   }
   
   // optional .com.android.glesv2debugger.Message.Prop prop = 21;
-  if (_has_bit(19)) {
+  if (_has_bit(21)) {
     ::google::protobuf::internal::WireFormatLite::WriteEnum(
       21, this->prop(), output);
   }
   
   // optional float clock = 22;
-  if (_has_bit(20)) {
+  if (_has_bit(22)) {
     ::google::protobuf::internal::WireFormatLite::WriteFloat(22, this->clock(), output);
   }
   
@@ -1091,6 +1137,16 @@
     ::google::protobuf::internal::WireFormatLite::WriteInt32(25, this->pixel_type(), output);
   }
   
+  // optional int32 image_width = 26;
+  if (_has_bit(18)) {
+    ::google::protobuf::internal::WireFormatLite::WriteInt32(26, this->image_width(), output);
+  }
+  
+  // optional int32 image_height = 27;
+  if (_has_bit(19)) {
+    ::google::protobuf::internal::WireFormatLite::WriteInt32(27, this->image_height(), output);
+  }
+  
 }
 
 int Message::ByteSize() const {
@@ -1222,6 +1278,20 @@
           this->pixel_type());
     }
     
+    // optional int32 image_width = 26;
+    if (has_image_width()) {
+      total_size += 2 +
+        ::google::protobuf::internal::WireFormatLite::Int32Size(
+          this->image_width());
+    }
+    
+    // optional int32 image_height = 27;
+    if (has_image_height()) {
+      total_size += 2 +
+        ::google::protobuf::internal::WireFormatLite::Int32Size(
+          this->image_height());
+    }
+    
     // optional float time = 11;
     if (has_time()) {
       total_size += 1 + 4;
@@ -1312,12 +1382,18 @@
       set_pixel_type(from.pixel_type());
     }
     if (from._has_bit(18)) {
-      set_time(from.time());
+      set_image_width(from.image_width());
     }
     if (from._has_bit(19)) {
-      set_prop(from.prop());
+      set_image_height(from.image_height());
     }
     if (from._has_bit(20)) {
+      set_time(from.time());
+    }
+    if (from._has_bit(21)) {
+      set_prop(from.prop());
+    }
+    if (from._has_bit(22)) {
       set_clock(from.clock());
     }
   }
@@ -1355,6 +1431,8 @@
     std::swap(data_type_, other->data_type_);
     std::swap(pixel_format_, other->pixel_format_);
     std::swap(pixel_type_, other->pixel_type_);
+    std::swap(image_width_, other->image_width_);
+    std::swap(image_height_, other->image_height_);
     std::swap(time_, other->time_);
     std::swap(prop_, other->prop_);
     std::swap(clock_, other->clock_);
diff --git a/opengl/libs/GLES2_dbg/src/debugger_message.pb.h b/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
index b2ec5a0..5c94664 100644
--- a/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
+++ b/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
@@ -236,11 +236,13 @@
 enum Message_Type {
   Message_Type_BeforeCall = 0,
   Message_Type_AfterCall = 1,
-  Message_Type_Response = 2
+  Message_Type_AfterGeneratedCall = 2,
+  Message_Type_Response = 3,
+  Message_Type_CompleteCall = 4
 };
 bool Message_Type_IsValid(int value);
 const Message_Type Message_Type_Type_MIN = Message_Type_BeforeCall;
-const Message_Type Message_Type_Type_MAX = Message_Type_Response;
+const Message_Type Message_Type_Type_MAX = Message_Type_CompleteCall;
 const int Message_Type_Type_ARRAYSIZE = Message_Type_Type_MAX + 1;
 
 enum Message_DataType {
@@ -253,13 +255,15 @@
 const int Message_DataType_DataType_ARRAYSIZE = Message_DataType_DataType_MAX + 1;
 
 enum Message_Prop {
-  Message_Prop_Capture = 0,
+  Message_Prop_CaptureDraw = 0,
   Message_Prop_TimeMode = 1,
-  Message_Prop_ExpectResponse = 2
+  Message_Prop_ExpectResponse = 2,
+  Message_Prop_CaptureSwap = 3,
+  Message_Prop_GLConstant = 4
 };
 bool Message_Prop_IsValid(int value);
-const Message_Prop Message_Prop_Prop_MIN = Message_Prop_Capture;
-const Message_Prop Message_Prop_Prop_MAX = Message_Prop_ExpectResponse;
+const Message_Prop Message_Prop_Prop_MIN = Message_Prop_CaptureDraw;
+const Message_Prop Message_Prop_Prop_MAX = Message_Prop_GLConstant;
 const int Message_Prop_Prop_ARRAYSIZE = Message_Prop_Prop_MAX + 1;
 
 // ===================================================================
@@ -510,7 +514,9 @@
   typedef Message_Type Type;
   static const Type BeforeCall = Message_Type_BeforeCall;
   static const Type AfterCall = Message_Type_AfterCall;
+  static const Type AfterGeneratedCall = Message_Type_AfterGeneratedCall;
   static const Type Response = Message_Type_Response;
+  static const Type CompleteCall = Message_Type_CompleteCall;
   static inline bool Type_IsValid(int value) {
     return Message_Type_IsValid(value);
   }
@@ -535,9 +541,11 @@
     Message_DataType_DataType_ARRAYSIZE;
   
   typedef Message_Prop Prop;
-  static const Prop Capture = Message_Prop_Capture;
+  static const Prop CaptureDraw = Message_Prop_CaptureDraw;
   static const Prop TimeMode = Message_Prop_TimeMode;
   static const Prop ExpectResponse = Message_Prop_ExpectResponse;
+  static const Prop CaptureSwap = Message_Prop_CaptureSwap;
+  static const Prop GLConstant = Message_Prop_GLConstant;
   static inline bool Prop_IsValid(int value) {
     return Message_Prop_IsValid(value);
   }
@@ -679,6 +687,20 @@
   inline ::google::protobuf::int32 pixel_type() const;
   inline void set_pixel_type(::google::protobuf::int32 value);
   
+  // optional int32 image_width = 26;
+  inline bool has_image_width() const;
+  inline void clear_image_width();
+  static const int kImageWidthFieldNumber = 26;
+  inline ::google::protobuf::int32 image_width() const;
+  inline void set_image_width(::google::protobuf::int32 value);
+  
+  // optional int32 image_height = 27;
+  inline bool has_image_height() const;
+  inline void clear_image_height();
+  static const int kImageHeightFieldNumber = 27;
+  inline ::google::protobuf::int32 image_height() const;
+  inline void set_image_height(::google::protobuf::int32 value);
+  
   // optional float time = 11;
   inline bool has_time() const;
   inline void clear_time();
@@ -723,6 +745,8 @@
   int data_type_;
   ::google::protobuf::int32 pixel_format_;
   ::google::protobuf::int32 pixel_type_;
+  ::google::protobuf::int32 image_width_;
+  ::google::protobuf::int32 image_height_;
   float time_;
   int prop_;
   float clock_;
@@ -730,7 +754,7 @@
   friend void protobuf_AssignDesc_debugger_5fmessage_2eproto();
   friend void protobuf_ShutdownFile_debugger_5fmessage_2eproto();
   
-  ::google::protobuf::uint32 _has_bits_[(21 + 31) / 32];
+  ::google::protobuf::uint32 _has_bits_[(23 + 31) / 32];
   
   // WHY DOES & HAVE LOWER PRECEDENCE THAN != !?
   inline bool _has_bit(int index) const {
@@ -1070,52 +1094,84 @@
   pixel_type_ = value;
 }
 
+// optional int32 image_width = 26;
+inline bool Message::has_image_width() const {
+  return _has_bit(18);
+}
+inline void Message::clear_image_width() {
+  image_width_ = 0;
+  _clear_bit(18);
+}
+inline ::google::protobuf::int32 Message::image_width() const {
+  return image_width_;
+}
+inline void Message::set_image_width(::google::protobuf::int32 value) {
+  _set_bit(18);
+  image_width_ = value;
+}
+
+// optional int32 image_height = 27;
+inline bool Message::has_image_height() const {
+  return _has_bit(19);
+}
+inline void Message::clear_image_height() {
+  image_height_ = 0;
+  _clear_bit(19);
+}
+inline ::google::protobuf::int32 Message::image_height() const {
+  return image_height_;
+}
+inline void Message::set_image_height(::google::protobuf::int32 value) {
+  _set_bit(19);
+  image_height_ = value;
+}
+
 // optional float time = 11;
 inline bool Message::has_time() const {
-  return _has_bit(18);
+  return _has_bit(20);
 }
 inline void Message::clear_time() {
   time_ = 0;
-  _clear_bit(18);
+  _clear_bit(20);
 }
 inline float Message::time() const {
   return time_;
 }
 inline void Message::set_time(float value) {
-  _set_bit(18);
+  _set_bit(20);
   time_ = value;
 }
 
 // optional .com.android.glesv2debugger.Message.Prop prop = 21;
 inline bool Message::has_prop() const {
-  return _has_bit(19);
+  return _has_bit(21);
 }
 inline void Message::clear_prop() {
   prop_ = 0;
-  _clear_bit(19);
+  _clear_bit(21);
 }
 inline ::com::android::glesv2debugger::Message_Prop Message::prop() const {
   return static_cast< ::com::android::glesv2debugger::Message_Prop >(prop_);
 }
 inline void Message::set_prop(::com::android::glesv2debugger::Message_Prop value) {
   GOOGLE_DCHECK(::com::android::glesv2debugger::Message_Prop_IsValid(value));
-  _set_bit(19);
+  _set_bit(21);
   prop_ = value;
 }
 
 // optional float clock = 22;
 inline bool Message::has_clock() const {
-  return _has_bit(20);
+  return _has_bit(22);
 }
 inline void Message::clear_clock() {
   clock_ = 0;
-  _clear_bit(20);
+  _clear_bit(22);
 }
 inline float Message::clock() const {
   return clock_;
 }
 inline void Message::set_clock(float value) {
-  _set_bit(20);
+  _set_bit(22);
   clock_ = value;
 }
 
diff --git a/opengl/libs/GLES2_dbg/src/egl.cpp b/opengl/libs/GLES2_dbg/src/egl.cpp
index 3a20e21..eb28d06 100644
--- a/opengl/libs/GLES2_dbg/src/egl.cpp
+++ b/opengl/libs/GLES2_dbg/src/egl.cpp
@@ -18,6 +18,7 @@
 
 EGLBoolean Debug_eglSwapBuffers(EGLDisplay dpy, EGLSurface draw)
 {
+    DbgContext * const dbg = getDbgContextThreadSpecific();
     glesv2debugger::Message msg;
     struct : public FunctionCall {
         EGLDisplay dpy;
@@ -33,7 +34,21 @@
 
     msg.set_arg0(reinterpret_cast<int>(dpy));
     msg.set_arg1(reinterpret_cast<int>(draw));
-
+    if (dbg->captureSwap > 0) {
+        dbg->captureSwap--;
+        int viewport[4] = {};
+        dbg->hooks->gl.glGetIntegerv(GL_VIEWPORT, viewport);
+        void * pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
+                        dbg->readBytesPerPixel);
+        dbg->hooks->gl.glReadPixels(viewport[0], viewport[1], viewport[2],
+                                    viewport[3], dbg->readFormat, dbg->readType, pixels);
+        dbg->CompressReadPixelBuffer(msg.mutable_data());
+        msg.set_data_type(msg.ReferencedImage);
+        msg.set_pixel_format(dbg->readFormat);
+        msg.set_pixel_type(dbg->readType);
+        msg.set_image_width(viewport[2]);
+        msg.set_image_height(viewport[3]);
+    }
     int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_eglSwapBuffers);
     return static_cast<EGLBoolean>(reinterpret_cast<int>(ret));
 }
diff --git a/opengl/libs/GLES2_dbg/src/header.h b/opengl/libs/GLES2_dbg/src/header.h
index 9218da5..f2b1fa6 100644
--- a/opengl/libs/GLES2_dbg/src/header.h
+++ b/opengl/libs/GLES2_dbg/src/header.h
@@ -14,6 +14,9 @@
  ** limitations under the License.
  */
 
+#ifndef ANDROID_GLES2_DBG_HEADER_H
+#define ANDROID_GLES2_DBG_HEADER_H
+
 #include <stdlib.h>
 #include <ctype.h>
 #include <string.h>
@@ -24,9 +27,7 @@
 
 #include <cutils/log.h>
 #include <utils/Timers.h>
-#include <../../../libcore/include/StaticAssert.h>
 
-#define EGL_TRACE 1
 #include "hooks.h"
 
 #include "glesv2dbg.h"
@@ -39,8 +40,6 @@
 using namespace android;
 using namespace com::android;
 
-#define API_ENTRY(_api) Debug_##_api
-
 #ifndef __location__
 #define __HIERALLOC_STRING_0__(s)   #s
 #define __HIERALLOC_STRING_1__(s)   __HIERALLOC_STRING_0__(s)
@@ -74,9 +73,10 @@
 };
 
 struct DbgContext {
-private:
     static const unsigned int LZF_CHUNK_SIZE = 256 * 1024;
-    char * lzf_buf; // malloc / free; for lzf chunk compression
+
+private:
+    char * lzf_buf; // malloc / free; for lzf chunk compression and other uses
 
     // used as buffer and reference frame for ReadPixels; malloc/free
     unsigned * lzf_ref [2];
@@ -84,9 +84,14 @@
     unsigned lzf_refSize, lzf_refBufSize; // bytes
 
 public:
-    const unsigned version; // 0 is GLES1, 1 is GLES2
+    const unsigned int version; // 0 is GLES1, 1 is GLES2
     const gl_hooks_t * const hooks;
-    const unsigned MAX_VERTEX_ATTRIBS;
+    const unsigned int MAX_VERTEX_ATTRIBS;
+    const GLenum readFormat, readType; // implementation supported glReadPixels
+    const unsigned int readBytesPerPixel;
+
+    unsigned int captureSwap; // number of eglSwapBuffers to glReadPixels
+    unsigned int captureDraw; // number of glDrawArrays/Elements to glReadPixels
 
     GLFunctionBitfield expectResponse;
 
@@ -119,16 +124,21 @@
     unsigned maxAttrib; // number of slots used by program
 
     DbgContext(const unsigned version, const gl_hooks_t * const hooks,
-               const unsigned MAX_VERTEX_ATTRIBS);
+               const unsigned MAX_VERTEX_ATTRIBS, const GLenum readFormat,
+               const GLenum readType);
     ~DbgContext();
 
     void Fetch(const unsigned index, std::string * const data) const;
     void Compress(const void * in_data, unsigned in_len, std::string * const outStr);
+    static unsigned char * Decompress(const void * in, const unsigned int inLen,
+                                      unsigned int * const outLen); // malloc/free
     void * GetReadPixelsBuffer(const unsigned size);
     bool IsReadPixelBuffer(const void * const ptr)  {
         return ptr == lzf_ref[lzf_readIndex];
     }
     void CompressReadPixelBuffer(std::string * const outStr);
+    char * GetBuffer(); // allocates lzf_buf if NULL
+    unsigned int GetBufferSize(); // allocates lzf_buf if NULL
 
     void glUseProgram(GLuint program);
     void glEnableVertexAttribArray(GLuint index);
@@ -141,9 +151,7 @@
     void glDeleteBuffers(GLsizei n, const GLuint *buffers);
 };
 
-
 DbgContext * getDbgContextThreadSpecific();
-#define DBGCONTEXT(ctx) DbgContext * const ctx = getDbgContextThreadSpecific();
 
 struct FunctionCall {
     virtual const int * operator()(gl_hooks_t::gl_t const * const _c,
@@ -152,7 +160,6 @@
 };
 
 // move these into DbgContext as static
-extern bool capture;
 extern int timeMode; // SYSTEM_TIME_
 
 extern int clientSock, serverSock;
@@ -169,3 +176,5 @@
 const int * GenerateCall(DbgContext * const dbg, const glesv2debugger::Message & cmd,
                          glesv2debugger::Message & msg, const int * const prevRet);
 }; // namespace android {
+
+#endif // #ifndef ANDROID_GLES2_DBG_HEADER_H
diff --git a/opengl/libs/GLES2_dbg/src/server.cpp b/opengl/libs/GLES2_dbg/src/server.cpp
index 7039c84..0c711bf 100644
--- a/opengl/libs/GLES2_dbg/src/server.cpp
+++ b/opengl/libs/GLES2_dbg/src/server.cpp
@@ -28,7 +28,8 @@
 {
 
 int serverSock = -1, clientSock = -1;
-
+FILE * file = NULL;
+unsigned int MAX_FILE_SIZE = 0;
 int timeMode = SYSTEM_TIME_THREAD;
 
 static void Die(const char * msg)
@@ -38,18 +39,25 @@
     exit(1);
 }
 
-void StartDebugServer(unsigned short port)
+void StartDebugServer(const unsigned short port, const bool forceUseFile,
+                      const unsigned int maxFileSize, const char * const filePath)
 {
+    MAX_FILE_SIZE = maxFileSize;
+
     LOGD("GLESv2_dbg: StartDebugServer");
-    if (serverSock >= 0)
+    if (serverSock >= 0 || file)
         return;
 
     LOGD("GLESv2_dbg: StartDebugServer create socket");
     struct sockaddr_in server = {}, client = {};
 
     /* Create the TCP socket */
-    if ((serverSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
-        Die("Failed to create socket");
+    if (forceUseFile || (serverSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+        file = fopen(filePath, "wb");
+        if (!file)
+            Die("Failed to create socket and file");
+        else
+            return;
     }
     /* Construct the server sockaddr_in structure */
     server.sin_family = AF_INET;                  /* Internet/IP */
@@ -92,13 +100,17 @@
         close(serverSock);
         serverSock = -1;
     }
-
+    if (file) {
+        fclose(file);
+        file = NULL;
+    }
 }
 
 void Receive(glesv2debugger::Message & cmd)
 {
+    if (clientSock < 0)
+        return;
     unsigned len = 0;
-
     int received = recv(clientSock, &len, 4, MSG_WAITALL);
     if (received < 0)
         Die("Failed to receive response length");
@@ -106,7 +118,6 @@
         LOGD("received %dB: %.8X", received, len);
         Die("Received length mismatch, expected 4");
     }
-    len = ntohl(len);
     static void * buffer = NULL;
     static unsigned bufferSize = 0;
     if (bufferSize < len) {
@@ -125,6 +136,8 @@
 
 bool TryReceive(glesv2debugger::Message & cmd)
 {
+    if (clientSock < 0)
+        return false;
     fd_set readSet;
     FD_ZERO(&readSet);
     FD_SET(clientSock, &readSet);
@@ -146,14 +159,34 @@
 
 float Send(const glesv2debugger::Message & msg, glesv2debugger::Message & cmd)
 {
+    // TODO: use per DbgContext send/receive buffer and async socket
+    //  instead of mutex and blocking io; watch out for large messages
     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-    pthread_mutex_lock(&mutex); // TODO: this is just temporary
+    struct Autolock {
+        Autolock() {
+            pthread_mutex_lock(&mutex);
+        }
+        ~Autolock() {
+            pthread_mutex_unlock(&mutex);
+        }
+    } autolock;
 
     if (msg.function() != glesv2debugger::Message_Function_ACK)
         assert(msg.has_context_id() && msg.context_id() != 0);
     static std::string str;
     msg.SerializeToString(&str);
-    uint32_t len = htonl(str.length());
+    const uint32_t len = str.length();
+    if (clientSock < 0) {
+        if (file) {
+            fwrite(&len, sizeof(len), 1, file);
+            fwrite(str.data(), len, 1, file);
+            if (ftell(file) >= MAX_FILE_SIZE) {
+                fclose(file);
+                Die("MAX_FILE_SIZE reached");
+            }
+        }
+        return 0;
+    }
     int sent = -1;
     sent = send(clientSock, &len, sizeof(len), 0);
     if (sent != sizeof(len)) {
@@ -161,36 +194,37 @@
         Die("Failed to send message length");
     }
     nsecs_t c0 = systemTime(timeMode);
-    sent = send(clientSock, str.c_str(), str.length(), 0);
+    sent = send(clientSock, str.data(), str.length(), 0);
     float t = (float)ns2ms(systemTime(timeMode) - c0);
     if (sent != str.length()) {
         LOGD("actual sent=%d expected=%d clientSock=%d", sent, str.length(), clientSock);
         Die("Failed to send message");
     }
-
+    // TODO: factor Receive & TryReceive out and into MessageLoop, or add control argument.
+    // mean while, if server is sending a SETPROP then don't try to receive,
+    //  because server will not be processing received command
+    if (msg.function() == msg.SETPROP)
+        return t;
     // try to receive commands even though not expecting response,
-    // since client can send SETPROP commands anytime
+    //  since client can send SETPROP and other commands anytime
     if (!msg.expect_response()) {
         if (TryReceive(cmd)) {
-            LOGD("Send: TryReceived");
             if (glesv2debugger::Message_Function_SETPROP == cmd.function())
-                LOGD("Send: received SETPROP");
+                LOGD("Send: TryReceived SETPROP");
             else
-                LOGD("Send: received something else");
+                LOGD("Send: TryReceived %u", cmd.function());
         }
     } else
         Receive(cmd);
-
-    pthread_mutex_unlock(&mutex);
     return t;
 }
 
 void SetProp(DbgContext * const dbg, const glesv2debugger::Message & cmd)
 {
     switch (cmd.prop()) {
-    case glesv2debugger::Message_Prop_Capture:
-        LOGD("SetProp Message_Prop_Capture %d", cmd.arg0());
-        capture = cmd.arg0();
+    case glesv2debugger::Message_Prop_CaptureDraw:
+        LOGD("SetProp Message_Prop_CaptureDraw %d", cmd.arg0());
+        dbg->captureDraw = cmd.arg0();
         break;
     case glesv2debugger::Message_Prop_TimeMode:
         LOGD("SetProp Message_Prop_TimeMode %d", cmd.arg0());
@@ -200,6 +234,10 @@
         LOGD("SetProp Message_Prop_ExpectResponse %d=%d", cmd.arg0(), cmd.arg1());
         dbg->expectResponse.Bit((glesv2debugger::Message_Function)cmd.arg0(), cmd.arg1());
         break;
+    case glesv2debugger::Message_Prop_CaptureSwap:
+        LOGD("SetProp CaptureSwap %d", cmd.arg0());
+        dbg->captureSwap = cmd.arg0();
+        break;
     default:
         assert(0);
     }
@@ -213,12 +251,17 @@
     glesv2debugger::Message cmd;
     msg.set_context_id(reinterpret_cast<int>(dbg));
     msg.set_type(glesv2debugger::Message_Type_BeforeCall);
-    const bool expectResponse = dbg->expectResponse.Bit(function);
+    bool expectResponse = dbg->expectResponse.Bit(function);
     msg.set_expect_response(expectResponse);
     msg.set_function(function);
-    if (!expectResponse)
-        cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+
+    // when not exectResponse, set cmd to CONTINUE then SKIP
+    // cmd will be overwritten by received command
+    cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+    cmd.set_expect_response(expectResponse);
+    glesv2debugger::Message_Function oldCmd = cmd.function();
     Send(msg, cmd);
+    expectResponse = cmd.expect_response();
     while (true) {
         msg.Clear();
         nsecs_t c0 = systemTime(timeMode);
@@ -233,22 +276,34 @@
             msg.set_function(function);
             msg.set_type(glesv2debugger::Message_Type_AfterCall);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
+            if (!expectResponse) {
                 cmd.set_function(glesv2debugger::Message_Function_SKIP);
+                cmd.set_expect_response(false);
+            }
+            oldCmd = cmd.function();
             Send(msg, cmd);
+            expectResponse = cmd.expect_response();
             break;
         case glesv2debugger::Message_Function_SKIP:
             return const_cast<int *>(ret);
         case glesv2debugger::Message_Function_SETPROP:
             SetProp(dbg, cmd);
-            Receive(cmd);
+            expectResponse = cmd.expect_response();
+            if (!expectResponse) // SETPROP is "out of band"
+                cmd.set_function(oldCmd);
+            else
+                Receive(cmd);
             break;
         default:
             ret = GenerateCall(dbg, cmd, msg, ret);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
+            if (!expectResponse) {
                 cmd.set_function(cmd.SKIP);
+                cmd.set_expect_response(expectResponse);
+            }
+            oldCmd = cmd.function();
             Send(msg, cmd);
+            expectResponse = cmd.expect_response();
             break;
         }
     }
diff --git a/opengl/libs/GLES2_dbg/src/vertex.cpp b/opengl/libs/GLES2_dbg/src/vertex.cpp
index 471e5ad..029ee3b 100644
--- a/opengl/libs/GLES2_dbg/src/vertex.cpp
+++ b/opengl/libs/GLES2_dbg/src/vertex.cpp
@@ -21,74 +21,13 @@
 bool capture; // capture after each glDraw*
 }
 
-void Debug_glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
-{
-    DbgContext * const dbg = getDbgContextThreadSpecific();
-    glesv2debugger::Message msg, cmd;
-    msg.set_context_id(reinterpret_cast<int>(dbg));
-    msg.set_type(glesv2debugger::Message_Type_BeforeCall);
-    const bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glReadPixels);
-    msg.set_expect_response(expectResponse);
-    msg.set_function(glesv2debugger::Message_Function_glReadPixels);
-    msg.set_arg0(x);
-    msg.set_arg1(y);
-    msg.set_arg2(width);
-    msg.set_arg3(height);
-    msg.set_arg4(format);
-    msg.set_arg5(type);
-    msg.set_arg6(reinterpret_cast<int>(pixels));
-
-    const unsigned size = width * height * GetBytesPerPixel(format, type);
-    if (!expectResponse)
-        cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
-    Send(msg, cmd);
-    float t = 0;
-    while (true) {
-        msg.Clear();
-        nsecs_t c0 = systemTime(timeMode);
-        switch (cmd.function()) {
-        case glesv2debugger::Message_Function_CONTINUE:
-            dbg->hooks->gl.glReadPixels(x, y, width, height, format, type, pixels);
-            msg.set_time((systemTime(timeMode) - c0) * 1e-6f);
-            msg.set_context_id(reinterpret_cast<int>(dbg));
-            msg.set_function(glesv2debugger::Message_Function_glReadPixels);
-            msg.set_type(glesv2debugger::Message_Type_AfterCall);
-            msg.set_expect_response(expectResponse);
-            if (dbg->IsReadPixelBuffer(pixels)) {
-                dbg->CompressReadPixelBuffer(msg.mutable_data());
-                msg.set_data_type(msg.ReferencedImage);
-            } else {
-                dbg->Compress(pixels, size, msg.mutable_data());
-                msg.set_data_type(msg.NonreferencedImage);
-            }
-            if (!expectResponse)
-                cmd.set_function(glesv2debugger::Message_Function_SKIP);
-            Send(msg, cmd);
-            break;
-        case glesv2debugger::Message_Function_SKIP:
-            return;
-        case glesv2debugger::Message_Function_SETPROP:
-            SetProp(dbg, cmd);
-            Receive(cmd);
-            break;
-        default:
-            GenerateCall(dbg, cmd, msg, NULL);
-            msg.set_expect_response(expectResponse);
-            if (!expectResponse)
-                cmd.set_function(cmd.SKIP);
-            Send(msg, cmd);
-            break;
-        }
-    }
-}
-
 void Debug_glDrawArrays(GLenum mode, GLint first, GLsizei count)
 {
     DbgContext * const dbg = getDbgContextThreadSpecific();
     glesv2debugger::Message msg, cmd;
     msg.set_context_id(reinterpret_cast<int>(dbg));
     msg.set_type(glesv2debugger::Message_Type_BeforeCall);
-    const bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glDrawArrays);
+    bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glDrawArrays);
     msg.set_expect_response(expectResponse);
     msg.set_function(glesv2debugger::Message_Function_glDrawArrays);
     msg.set_arg0(mode);
@@ -103,11 +42,12 @@
     }
 
     void * pixels = NULL;
-    GLint readFormat = 0, readType = 0;
     int viewport[4] = {};
-    if (!expectResponse)
-        cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+    cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+    cmd.set_expect_response(expectResponse);
+    glesv2debugger::Message_Function oldCmd = cmd.function();
     Send(msg, cmd);
+    expectResponse = cmd.expect_response();
     while (true) {
         msg.Clear();
         nsecs_t c0 = systemTime(timeMode);
@@ -119,33 +59,47 @@
             msg.set_function(glesv2debugger::Message_Function_glDrawArrays);
             msg.set_type(glesv2debugger::Message_Type_AfterCall);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
+            if (!expectResponse) {
                 cmd.set_function(glesv2debugger::Message_Function_SKIP);
+                cmd.set_expect_response(false);
+            }
+            oldCmd = cmd.function();
             Send(msg, cmd);
-            if (capture) {
+            expectResponse = cmd.expect_response();
+            // TODO: pack glReadPixels data with vertex data instead of
+            //  relying on sperate call for transport, this would allow
+            //  auto generated message loop using EXTEND_Debug macro
+            if (dbg->captureDraw > 0) {
+                dbg->captureDraw--;
                 dbg->hooks->gl.glGetIntegerv(GL_VIEWPORT, viewport);
-                dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
-                dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
 //                LOGD("glDrawArrays CAPTURE: x=%d y=%d width=%d height=%d format=0x%.4X type=0x%.4X",
 //                     viewport[0], viewport[1], viewport[2], viewport[3], readFormat, readType);
                 pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
-                                                  GetBytesPerPixel(readFormat, readType));
+                                                  dbg->readBytesPerPixel);
                 Debug_glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
-                                   readFormat, readType, pixels);
+                                   dbg->readFormat, dbg->readType, pixels);
             }
             break;
         case glesv2debugger::Message_Function_SKIP:
             return;
         case glesv2debugger::Message_Function_SETPROP:
             SetProp(dbg, cmd);
-            Receive(cmd);
+            expectResponse = cmd.expect_response();
+            if (!expectResponse) // SETPROP is "out of band"
+                cmd.set_function(oldCmd);
+            else
+                Receive(cmd);
             break;
         default:
             GenerateCall(dbg, cmd, msg, NULL);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
+            if (!expectResponse) {
                 cmd.set_function(cmd.SKIP);
+                cmd.set_expect_response(expectResponse);
+            }
+            oldCmd = cmd.function();
             Send(msg, cmd);
+            expectResponse = cmd.expect_response();
             break;
         }
     }
@@ -169,7 +123,7 @@
     glesv2debugger::Message msg, cmd;
     msg.set_context_id(reinterpret_cast<int>(dbg));
     msg.set_type(glesv2debugger::Message_Type_BeforeCall);
-    const bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glDrawElements);
+    bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glDrawElements);
     msg.set_expect_response(expectResponse);
     msg.set_function(glesv2debugger::Message_Function_glDrawElements);
     msg.set_arg0(mode);
@@ -195,11 +149,12 @@
         assert(0);
 
     void * pixels = NULL;
-    GLint readFormat = 0, readType = 0;
     int viewport[4] = {};
-    if (!expectResponse)
-        cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+    cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+    cmd.set_expect_response(expectResponse);
+    glesv2debugger::Message_Function oldCmd = cmd.function();
     Send(msg, cmd);
+    expectResponse = cmd.expect_response();
     while (true) {
         msg.Clear();
         nsecs_t c0 = systemTime(timeMode);
@@ -211,33 +166,45 @@
             msg.set_function(glesv2debugger::Message_Function_glDrawElements);
             msg.set_type(glesv2debugger::Message_Type_AfterCall);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
+            if (!expectResponse) {
                 cmd.set_function(glesv2debugger::Message_Function_SKIP);
+                cmd.set_expect_response(false);
+            }
+            oldCmd = cmd.function();
             Send(msg, cmd);
-            if (capture) {
+            expectResponse = cmd.expect_response();
+            // TODO: pack glReadPixels data with vertex data instead of
+            //  relying on sperate call for transport, this would allow
+            //  auto generated message loop using EXTEND_Debug macro
+            if (dbg->captureDraw > 0) {
+                dbg->captureDraw--;
                 dbg->hooks->gl.glGetIntegerv(GL_VIEWPORT, viewport);
-                dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
-                dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
-//                LOGD("glDrawArrays CAPTURE: x=%d y=%d width=%d height=%d format=0x%.4X type=0x%.4X",
-//                     viewport[0], viewport[1], viewport[2], viewport[3], readFormat, readType);
                 pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
-                                                  GetBytesPerPixel(readFormat, readType));
+                                                  dbg->readBytesPerPixel);
                 Debug_glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
-                                   readFormat, readType, pixels);
+                                   dbg->readFormat, dbg->readType, pixels);
             }
             break;
         case glesv2debugger::Message_Function_SKIP:
             return;
         case glesv2debugger::Message_Function_SETPROP:
             SetProp(dbg, cmd);
-            Receive(cmd);
+            expectResponse = cmd.expect_response();
+            if (!expectResponse) // SETPROP is "out of band"
+                cmd.set_function(oldCmd);
+            else
+                Receive(cmd);
             break;
         default:
             GenerateCall(dbg, cmd, msg, NULL);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
+            if (!expectResponse) {
                 cmd.set_function(cmd.SKIP);
+                cmd.set_expect_response(expectResponse);
+            }
+            oldCmd = cmd.function();
             Send(msg, cmd);
+            expectResponse = cmd.expect_response();
             break;
         }
     }
diff --git a/opengl/libs/GLES2_dbg/test/Android.mk b/opengl/libs/GLES2_dbg/test/Android.mk
new file mode 100644
index 0000000..14a84b4
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/Android.mk
@@ -0,0 +1,39 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH) \
+    $(LOCAL_PATH)/../src \
+    $(LOCAL_PATH)/../../ \
+    external/gtest/include \
+    external/stlport/stlport \
+    external/protobuf/src \
+    bionic \
+    external \
+#
+
+LOCAL_SRC_FILES:= \
+    test_main.cpp \
+    test_server.cpp \
+    test_socket.cpp \
+#
+
+LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2_dbg libstlport
+LOCAL_STATIC_LIBRARIES := libgtest libprotobuf-cpp-2.3.0-lite liblzf
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE:= libGLESv2_dbg_test
+
+ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true)
+    LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
+endif
+ifneq ($(TARGET_SIMULATOR),true)
+    LOCAL_C_INCLUDES += bionic/libc/private
+endif
+
+LOCAL_CFLAGS += -DLOG_TAG=\"libEGL\"
+LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+LOCAL_CFLAGS += -fvisibility=hidden
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/opengl/libs/GLES2_dbg/test/test_main.cpp b/opengl/libs/GLES2_dbg/test/test_main.cpp
new file mode 100644
index 0000000..058bea4
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_main.cpp
@@ -0,0 +1,234 @@
+/*
+ ** 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.
+ */
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "hooks.h"
+
+namespace
+{
+
+// The fixture for testing class Foo.
+class DbgContextTest : public ::testing::Test
+{
+protected:
+    android::DbgContext dbg;
+    gl_hooks_t hooks;
+
+    DbgContextTest()
+            : dbg(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE) {
+        // You can do set-up work for each test here.
+        hooks.gl.glGetError = GetError;
+    }
+
+    static GLenum GetError() {
+        return GL_NO_ERROR;
+    }
+
+    virtual ~DbgContextTest() {
+        // You can do clean-up work that doesn't throw exceptions here.
+    }
+
+    // If the constructor and destructor are not enough for setting up
+    // and cleaning up each test, you can define the following methods:
+
+    virtual void SetUp() {
+        // Code here will be called immediately after the constructor (right
+        // before each test).
+    }
+
+    virtual void TearDown() {
+        // Code here will be called immediately after each test (right
+        // before the destructor).
+    }
+};
+
+TEST_F(DbgContextTest, GetReadPixelBuffer)
+{
+    const unsigned int bufferSize = 512;
+    // test that it's allocating two buffers and swapping them
+    void * const buffer0 = dbg.GetReadPixelsBuffer(bufferSize);
+    ASSERT_NE((void *)NULL, buffer0);
+    for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) {
+        EXPECT_EQ(0, ((unsigned int *)buffer0)[i])
+        << "GetReadPixelsBuffer should allocate and zero";
+        ((unsigned int *)buffer0)[i] = i * 13;
+    }
+
+    void * const buffer1 = dbg.GetReadPixelsBuffer(bufferSize);
+    ASSERT_NE((void *)NULL, buffer1);
+    EXPECT_NE(buffer0, buffer1);
+    for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) {
+        EXPECT_EQ(0, ((unsigned int *)buffer1)[i])
+        << "GetReadPixelsBuffer should allocate and zero";
+        ((unsigned int *)buffer1)[i] = i * 17;
+    }
+
+    void * const buffer2 = dbg.GetReadPixelsBuffer(bufferSize);
+    EXPECT_EQ(buffer2, buffer0);
+    for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++)
+        EXPECT_EQ(i * 13, ((unsigned int *)buffer2)[i])
+        << "GetReadPixelsBuffer should swap buffers";
+
+    void * const buffer3 = dbg.GetReadPixelsBuffer(bufferSize);
+    EXPECT_EQ(buffer3, buffer1);
+    for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++)
+        EXPECT_EQ(i * 17, ((unsigned int *)buffer3)[i])
+        << "GetReadPixelsBuffer should swap buffers";
+
+    void * const buffer4 = dbg.GetReadPixelsBuffer(bufferSize);
+    EXPECT_NE(buffer3, buffer4);
+    EXPECT_EQ(buffer0, buffer2);
+    EXPECT_EQ(buffer1, buffer3);
+    EXPECT_EQ(buffer2, buffer4);
+
+    // it reallocs as necessary; 0 size may result in NULL
+    for (unsigned int i = 0; i < 42; i++) {
+        void * const buffer = dbg.GetReadPixelsBuffer(((i & 7)) << 20);
+        EXPECT_NE((void *)NULL, buffer)
+        << "should be able to get a variety of reasonable sizes";
+        EXPECT_TRUE(dbg.IsReadPixelBuffer(buffer));
+    }
+}
+
+TEST_F(DbgContextTest, CompressReadPixelBuffer)
+{
+    const unsigned int bufferSize = dbg.LZF_CHUNK_SIZE * 4 + 33;
+    std::string out;
+    unsigned char * buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        buffer[i] = i * 13;
+    dbg.CompressReadPixelBuffer(&out);
+    uint32_t decompSize = 0;
+    ASSERT_LT(12, out.length()); // at least written chunk header
+    ASSERT_EQ(bufferSize, *(uint32_t *)out.data())
+    << "total decompressed size should be as requested in GetReadPixelsBuffer";
+    for (unsigned int i = 4; i < out.length();) {
+        const uint32_t outSize = *(uint32_t *)(out.data() + i);
+        i += 4;
+        const uint32_t inSize = *(uint32_t *)(out.data() + i);
+        i += 4;
+        if (inSize == 0)
+            i += outSize; // chunk not compressed
+        else
+            i += inSize; // skip the actual compressed chunk
+        decompSize += outSize;
+    }
+    ASSERT_EQ(bufferSize, decompSize);
+    decompSize = 0;
+
+    unsigned char * decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+    ASSERT_EQ(decompSize, bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        EXPECT_EQ((unsigned char)(i * 13), decomp[i]) << "xor with 0 ref is identity";
+    free(decomp);
+
+    buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        buffer[i] = i * 13;
+    out.clear();
+    dbg.CompressReadPixelBuffer(&out);
+    decompSize = 0;
+    decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+    ASSERT_EQ(decompSize, bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        EXPECT_EQ(0, decomp[i]) << "xor with same ref is 0";
+    free(decomp);
+
+    buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        buffer[i] = i * 19;
+    out.clear();
+    dbg.CompressReadPixelBuffer(&out);
+    decompSize = 0;
+    decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+    ASSERT_EQ(decompSize, bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        EXPECT_EQ((unsigned char)(i * 13) ^ (unsigned char)(i * 19), decomp[i])
+        << "xor ref";
+    free(decomp);
+}
+
+TEST_F(DbgContextTest, UseProgram)
+{
+    static const GLuint _program = 74568;
+    static const struct Attribute {
+        const char * name;
+        GLint location;
+        GLint size;
+        GLenum type;
+    } _attributes [] = {
+        {"aaa", 2, 2, GL_FLOAT_VEC2},
+        {"bb", 6, 2, GL_FLOAT_MAT2},
+        {"c", 1, 1, GL_FLOAT},
+    };
+    static const unsigned int _attributeCount = sizeof(_attributes) / sizeof(*_attributes);
+    struct GL {
+        static void GetProgramiv(GLuint program, GLenum pname, GLint* params) {
+            EXPECT_EQ(_program, program);
+            ASSERT_NE((GLint *)NULL, params);
+            switch (pname) {
+            case GL_ACTIVE_ATTRIBUTES:
+                *params = _attributeCount;
+                return;
+            case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH:
+                *params = 4; // includes NULL terminator
+                return;
+            default:
+                ADD_FAILURE() << "not handled pname: " << pname;
+            }
+        }
+
+        static GLint GetAttribLocation(GLuint program, const GLchar* name) {
+            EXPECT_EQ(_program, program);
+            for (unsigned int i = 0; i < _attributeCount; i++)
+                if (!strcmp(name, _attributes[i].name))
+                    return _attributes[i].location;
+            ADD_FAILURE() << "unknown attribute name: " << name;
+            return -1;
+        }
+
+        static void GetActiveAttrib(GLuint program, GLuint index, GLsizei bufsize,
+                                    GLsizei* length, GLint* size, GLenum* type, GLchar* name) {
+            EXPECT_EQ(_program, program);
+            ASSERT_LT(index, _attributeCount);
+            const Attribute & att = _attributes[index];
+            ASSERT_GE(bufsize, strlen(att.name) + 1);
+            ASSERT_NE((GLint *)NULL, size);
+            ASSERT_NE((GLenum *)NULL, type);
+            ASSERT_NE((GLchar *)NULL, name);
+            strcpy(name, att.name);
+            if (length)
+                *length = strlen(name) + 1;
+            *size = att.size;
+            *type = att.type;
+        }
+    };
+    hooks.gl.glGetProgramiv = GL::GetProgramiv;
+    hooks.gl.glGetAttribLocation = GL::GetAttribLocation;
+    hooks.gl.glGetActiveAttrib = GL::GetActiveAttrib;
+    dbg.glUseProgram(_program);
+    EXPECT_EQ(10, dbg.maxAttrib);
+    dbg.glUseProgram(0);
+    EXPECT_EQ(0, dbg.maxAttrib);
+}
+}  // namespace
+
+int main(int argc, char **argv)
+{
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/opengl/libs/GLES2_dbg/test/test_server.cpp b/opengl/libs/GLES2_dbg/test/test_server.cpp
new file mode 100644
index 0000000..b6401e0
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_server.cpp
@@ -0,0 +1,251 @@
+/*
+ ** 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.
+ */
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "egl_tls.h"
+#include "hooks.h"
+
+namespace android
+{
+extern FILE * file;
+extern unsigned int MAX_FILE_SIZE;
+extern pthread_key_t dbgEGLThreadLocalStorageKey;
+};
+
+// tmpfile fails, so need to manually make a writable file first
+static const char * filePath = "/data/local/tmp/dump.gles2dbg";
+
+class ServerFileTest : public ::testing::Test
+{
+protected:
+    ServerFileTest() { }
+
+    virtual ~ServerFileTest() { }
+
+    virtual void SetUp() {
+        MAX_FILE_SIZE = 8 << 20;
+        ASSERT_EQ((FILE *)NULL, file);
+        file = fopen("/data/local/tmp/dump.gles2dbg", "wb+");
+        ASSERT_NE((FILE *)NULL, file) << "make sure file is writable: "
+        << filePath;
+    }
+
+    virtual void TearDown() {
+        ASSERT_NE((FILE *)NULL, file);
+        fclose(file);
+        file = NULL;
+    }
+
+    void Read(glesv2debugger::Message & msg) const {
+        msg.Clear();
+        uint32_t len = 0;
+        ASSERT_EQ(sizeof(len), fread(&len, 1, sizeof(len), file));
+        ASSERT_GT(len, 0u);
+        char * buffer = new char [len];
+        ASSERT_EQ(len, fread(buffer, 1, len, file));
+        msg.ParseFromArray(buffer, len);
+        delete buffer;
+    }
+
+    void CheckNoAvailable() {
+        const long pos = ftell(file);
+        fseek(file, 0, SEEK_END);
+        EXPECT_EQ(pos, ftell(file)) << "check no available";
+    }
+};
+
+TEST_F(ServerFileTest, Send)
+{
+    glesv2debugger::Message msg, cmd, read;
+    msg.set_context_id(1);
+    msg.set_function(msg.glFinish);
+    msg.set_expect_response(false);
+    msg.set_type(msg.BeforeCall);
+    rewind(file);
+    android::Send(msg, cmd);
+    rewind(file);
+    Read(read);
+    EXPECT_EQ(msg.context_id(), read.context_id());
+    EXPECT_EQ(msg.function(), read.function());
+    EXPECT_EQ(msg.expect_response(), read.expect_response());
+    EXPECT_EQ(msg.type(), read.type());
+}
+
+TEST_F(ServerFileTest, CreateDbgContext)
+{
+    gl_hooks_t hooks;
+    struct Constant {
+        GLenum pname;
+        GLint param;
+    };
+    static const Constant constants [] = {
+        {GL_MAX_VERTEX_ATTRIBS, 16},
+        {GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, 32},
+        {GL_IMPLEMENTATION_COLOR_READ_FORMAT, GL_RGBA},
+        {GL_IMPLEMENTATION_COLOR_READ_TYPE, GL_UNSIGNED_BYTE},
+    };
+    struct HookMock {
+        static void GetIntegerv(GLenum pname, GLint* params) {
+            ASSERT_TRUE(params != NULL);
+            for (unsigned int i = 0; i < sizeof(constants) / sizeof(*constants); i++)
+                if (pname == constants[i].pname) {
+                    *params = constants[i].param;
+                    return;
+                }
+            FAIL() << "GetIntegerv unknown pname: " << pname;
+        }
+        static GLenum GetError() {
+            return GL_NO_ERROR;
+        }
+    };
+    hooks.gl.glGetError = HookMock::GetError;
+    hooks.gl.glGetIntegerv = HookMock::GetIntegerv;
+    DbgContext * const dbg = CreateDbgContext(-1, 1, &hooks);
+    ASSERT_TRUE(dbg != NULL);
+    EXPECT_TRUE(dbg->vertexAttribs != NULL);
+
+    rewind(file);
+    glesv2debugger::Message read;
+    for (unsigned int i = 0; i < 2; i++) {
+        Read(read);
+        EXPECT_EQ(reinterpret_cast<int>(dbg), read.context_id());
+        EXPECT_FALSE(read.expect_response());
+        EXPECT_EQ(read.Response, read.type());
+        EXPECT_EQ(read.SETPROP, read.function());
+        EXPECT_EQ(read.GLConstant, read.prop());
+        GLint expectedConstant = 0;
+        HookMock::GetIntegerv(read.arg0(), &expectedConstant);
+        EXPECT_EQ(expectedConstant, read.arg1());
+    }
+    CheckNoAvailable();
+    DestroyDbgContext(dbg);
+}
+
+void * glNoop()
+{
+    return 0;
+}
+
+class ServerFileContextTest : public ServerFileTest
+{
+protected:
+    tls_t tls;
+    gl_hooks_t hooks;
+
+    ServerFileContextTest() { }
+
+    virtual ~ServerFileContextTest() { }
+
+    virtual void SetUp() {
+        ServerFileTest::SetUp();
+
+        if (dbgEGLThreadLocalStorageKey == -1)
+            pthread_key_create(&dbgEGLThreadLocalStorageKey, NULL);
+        ASSERT_NE(-1, dbgEGLThreadLocalStorageKey);
+        tls.dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE);
+        ASSERT_NE((void *)NULL, tls.dbg);
+        pthread_setspecific(dbgEGLThreadLocalStorageKey, &tls);
+        for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
+            ((void **)&hooks)[i] = reinterpret_cast<void *>(glNoop);
+    }
+
+    virtual void TearDown() {
+        ServerFileTest::TearDown();
+    }
+};
+
+TEST_F(ServerFileContextTest, MessageLoop)
+{
+    static const int arg0 = 45;
+    static const float arg7 = -87.2331f;
+    static const int arg8 = -3;
+    static const int * ret = reinterpret_cast<int *>(870);
+
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            msg.set_arg0(arg0);
+            msg.set_arg7((int &)arg7);
+            msg.set_arg8(arg8);
+            return ret;
+        }
+    } caller;
+    const int contextId = reinterpret_cast<int>(tls.dbg);
+    glesv2debugger::Message msg, read;
+
+    EXPECT_EQ(ret, MessageLoop(caller, msg, msg.glFinish));
+
+    rewind(file);
+    Read(read);
+    EXPECT_EQ(contextId, read.context_id());
+    EXPECT_EQ(read.glFinish, read.function());
+    EXPECT_EQ(false, read.expect_response());
+    EXPECT_EQ(read.BeforeCall, read.type());
+
+    Read(read);
+    EXPECT_EQ(contextId, read.context_id());
+    EXPECT_EQ(read.glFinish, read.function());
+    EXPECT_EQ(false, read.expect_response());
+    EXPECT_EQ(read.AfterCall, read.type());
+    EXPECT_TRUE(read.has_time());
+    EXPECT_EQ(arg0, read.arg0());
+    const int readArg7 = read.arg7();
+    EXPECT_EQ(arg7, (float &)readArg7);
+    EXPECT_EQ(arg8, read.arg8());
+
+    const long pos = ftell(file);
+    fseek(file, 0, SEEK_END);
+    EXPECT_EQ(pos, ftell(file))
+    << "should only write the BeforeCall and AfterCall messages";
+}
+
+TEST_F(ServerFileContextTest, DisableEnableVertexAttribArray)
+{
+    Debug_glEnableVertexAttribArray(tls.dbg->MAX_VERTEX_ATTRIBS + 2); // should just ignore invalid index
+
+    glesv2debugger::Message read;
+    rewind(file);
+    Read(read);
+    EXPECT_EQ(read.glEnableVertexAttribArray, read.function());
+    EXPECT_EQ(tls.dbg->MAX_VERTEX_ATTRIBS + 2, read.arg0());
+    Read(read);
+
+    rewind(file);
+    Debug_glDisableVertexAttribArray(tls.dbg->MAX_VERTEX_ATTRIBS + 4); // should just ignore invalid index
+    rewind(file);
+    Read(read);
+    Read(read);
+
+    for (unsigned int i = 0; i < tls.dbg->MAX_VERTEX_ATTRIBS; i += 5) {
+        rewind(file);
+        Debug_glEnableVertexAttribArray(i);
+        EXPECT_TRUE(tls.dbg->vertexAttribs[i].enabled);
+        rewind(file);
+        Read(read);
+        EXPECT_EQ(read.glEnableVertexAttribArray, read.function());
+        EXPECT_EQ(i, read.arg0());
+        Read(read);
+
+        rewind(file);
+        Debug_glDisableVertexAttribArray(i);
+        EXPECT_FALSE(tls.dbg->vertexAttribs[i].enabled);
+        rewind(file);
+        Read(read);
+        EXPECT_EQ(read.glDisableVertexAttribArray, read.function());
+        EXPECT_EQ(i, read.arg0());
+        Read(read);
+    }
+}
diff --git a/opengl/libs/GLES2_dbg/test/test_socket.cpp b/opengl/libs/GLES2_dbg/test/test_socket.cpp
new file mode 100644
index 0000000..617292e
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_socket.cpp
@@ -0,0 +1,474 @@
+/*
+ ** 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.
+ */
+
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "egl_tls.h"
+#include "hooks.h"
+
+namespace android
+{
+extern int serverSock, clientSock;
+extern pthread_key_t dbgEGLThreadLocalStorageKey;
+};
+
+void * glNoop();
+
+class SocketContextTest : public ::testing::Test
+{
+protected:
+    tls_t tls;
+    gl_hooks_t hooks;
+    int sock;
+    char * buffer;
+    unsigned int bufferSize;
+
+    SocketContextTest() : sock(-1) {
+    }
+
+    virtual ~SocketContextTest() {
+    }
+
+    virtual void SetUp() {
+        if (dbgEGLThreadLocalStorageKey == -1)
+            pthread_key_create(&dbgEGLThreadLocalStorageKey, NULL);
+        ASSERT_NE(-1, dbgEGLThreadLocalStorageKey);
+        tls.dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE);
+        ASSERT_TRUE(tls.dbg != NULL);
+        pthread_setspecific(dbgEGLThreadLocalStorageKey, &tls);
+        for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
+            ((void **)&hooks)[i] = (void *)glNoop;
+
+        int socks[2] = {-1, -1};
+        ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socks));
+        clientSock = socks[0];
+        sock = socks[1];
+
+        bufferSize = 128;
+        buffer = new char [128];
+        ASSERT_NE((char *)NULL, buffer);
+    }
+
+    virtual void TearDown() {
+        close(sock);
+        close(clientSock);
+        clientSock = -1;
+        delete buffer;
+    }
+
+    void Write(glesv2debugger::Message & msg) const {
+        msg.set_context_id((int)tls.dbg);
+        msg.set_type(msg.Response);
+        ASSERT_TRUE(msg.has_context_id());
+        ASSERT_TRUE(msg.has_function());
+        ASSERT_TRUE(msg.has_type());
+        ASSERT_TRUE(msg.has_expect_response());
+        static std::string str;
+        msg.SerializeToString(&str);
+        const uint32_t len = str.length();
+        ASSERT_EQ(sizeof(len), send(sock, &len, sizeof(len), 0));
+        ASSERT_EQ(str.length(), send(sock, str.data(), str.length(), 0));
+    }
+
+    void Read(glesv2debugger::Message & msg) {
+        int available = 0;
+        ASSERT_EQ(0, ioctl(sock, FIONREAD, &available));
+        ASSERT_GT(available, 0);
+        uint32_t len = 0;
+        ASSERT_EQ(sizeof(len), recv(sock, &len, sizeof(len), 0));
+        if (len > bufferSize) {
+            bufferSize = len;
+            buffer = new char[bufferSize];
+            ASSERT_TRUE(buffer != NULL);
+        }
+        ASSERT_EQ(len, recv(sock, buffer, len, 0));
+        msg.Clear();
+        msg.ParseFromArray(buffer, len);
+        ASSERT_TRUE(msg.has_context_id());
+        ASSERT_TRUE(msg.has_function());
+        ASSERT_TRUE(msg.has_type());
+        ASSERT_TRUE(msg.has_expect_response());
+    }
+
+    void CheckNoAvailable() {
+        int available = 0;
+        ASSERT_EQ(0, ioctl(sock, FIONREAD, &available));
+        ASSERT_EQ(available, 0);
+    }
+};
+
+TEST_F(SocketContextTest, MessageLoopSkip)
+{
+    static const int arg0 = 45;
+    static const float arg7 = -87.2331f;
+    static const int arg8 = -3;
+    static const int * ret = (int *)870;
+
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            msg.set_arg0(arg0);
+            msg.set_arg7((int &)arg7);
+            msg.set_arg8(arg8);
+            return ret;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    tls.dbg->expectResponse.Bit(msg.glFinish, true);
+
+    cmd.set_function(cmd.SKIP);
+    cmd.set_expect_response(false);
+    Write(cmd);
+
+    EXPECT_NE(ret, MessageLoop(caller, msg, msg.glFinish));
+
+    Read(read);
+    EXPECT_EQ(read.glFinish, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+    EXPECT_NE(arg0, read.arg0());
+    EXPECT_NE((int &)arg7, read.arg7());
+    EXPECT_NE(arg8, read.arg8());
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopContinue)
+{
+    static const int arg0 = GL_FRAGMENT_SHADER;
+    static const int ret = -342;
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            msg.set_ret(ret);
+            return (int *)ret;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    tls.dbg->expectResponse.Bit(msg.glCreateShader, true);
+
+    cmd.set_function(cmd.CONTINUE);
+    cmd.set_expect_response(false); // MessageLoop should automatically skip after continue
+    Write(cmd);
+
+    msg.set_arg0(arg0);
+    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateShader));
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+    EXPECT_EQ(arg0, read.arg0());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+    EXPECT_EQ(ret, read.ret());
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopGenerateCall)
+{
+    static const int ret = -342;
+    static unsigned int createShader, createProgram;
+    createShader = 0;
+    createProgram = 0;
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            const int r = (int)_c->glCreateProgram();
+            msg.set_ret(r);
+            return (int *)r;
+        }
+        static GLuint CreateShader(const GLenum type) {
+            createShader++;
+            return type;
+        }
+        static GLuint CreateProgram() {
+            createProgram++;
+            return ret;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    hooks.gl.glCreateShader = caller.CreateShader;
+    hooks.gl.glCreateProgram = caller.CreateProgram;
+    tls.dbg->expectResponse.Bit(msg.glCreateProgram, true);
+
+    cmd.set_function(cmd.glCreateShader);
+    cmd.set_arg0(GL_FRAGMENT_SHADER);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.Clear();
+    cmd.set_function(cmd.CONTINUE);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.set_function(cmd.glCreateShader);
+    cmd.set_arg0(GL_VERTEX_SHADER);
+    cmd.set_expect_response(false); // MessageLoop should automatically skip afterwards
+    Write(cmd);
+
+    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateProgram));
+
+    Read(read);
+    EXPECT_EQ(read.glCreateProgram, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterGeneratedCall, read.type());
+    EXPECT_EQ(GL_FRAGMENT_SHADER, read.ret());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateProgram, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+    EXPECT_EQ(ret, read.ret());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterGeneratedCall, read.type());
+    EXPECT_EQ(GL_VERTEX_SHADER, read.ret());
+
+    EXPECT_EQ(2, createShader);
+    EXPECT_EQ(1, createProgram);
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopSetProp)
+{
+    static const int ret = -342;
+    static unsigned int createShader, createProgram;
+    createShader = 0;
+    createProgram = 0;
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            const int r = (int)_c->glCreateProgram();
+            msg.set_ret(r);
+            return (int *)r;
+        }
+        static GLuint CreateShader(const GLenum type) {
+            createShader++;
+            return type;
+        }
+        static GLuint CreateProgram() {
+            createProgram++;
+            return ret;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    hooks.gl.glCreateShader = caller.CreateShader;
+    hooks.gl.glCreateProgram = caller.CreateProgram;
+    tls.dbg->expectResponse.Bit(msg.glCreateProgram, false);
+
+    cmd.set_function(cmd.SETPROP);
+    cmd.set_prop(cmd.ExpectResponse);
+    cmd.set_arg0(cmd.glCreateProgram);
+    cmd.set_arg1(true);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.Clear();
+    cmd.set_function(cmd.glCreateShader);
+    cmd.set_arg0(GL_FRAGMENT_SHADER);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.set_function(cmd.SETPROP);
+    cmd.set_prop(cmd.CaptureDraw);
+    cmd.set_arg0(819);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.Clear();
+    cmd.set_function(cmd.CONTINUE);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.set_function(cmd.glCreateShader);
+    cmd.set_arg0(GL_VERTEX_SHADER);
+    cmd.set_expect_response(false); // MessageLoop should automatically skip afterwards
+    Write(cmd);
+
+    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateProgram));
+
+    EXPECT_TRUE(tls.dbg->expectResponse.Bit(msg.glCreateProgram));
+    EXPECT_EQ(819, tls.dbg->captureDraw);
+
+    Read(read);
+    EXPECT_EQ(read.glCreateProgram, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterGeneratedCall, read.type());
+    EXPECT_EQ(GL_FRAGMENT_SHADER, read.ret());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateProgram, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+    EXPECT_EQ(ret, read.ret());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterGeneratedCall, read.type());
+    EXPECT_EQ(GL_VERTEX_SHADER, read.ret());
+
+    EXPECT_EQ(2, createShader);
+    EXPECT_EQ(1, createProgram);
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, TexImage2D)
+{
+    static const GLenum _target = GL_TEXTURE_2D;
+    static const GLint _level = 1, _internalformat = GL_RGBA;
+    static const GLsizei _width = 2, _height = 2;
+    static const GLint _border = 333;
+    static const GLenum _format = GL_RGB, _type = GL_UNSIGNED_SHORT_5_6_5;
+    static const short _pixels [_width * _height] = {11, 22, 33, 44};
+    static unsigned int texImage2D;
+    texImage2D = 0;
+
+    struct Caller {
+        static void TexImage2D(GLenum target, GLint level, GLint internalformat,
+                               GLsizei width, GLsizei height, GLint border,
+                               GLenum format, GLenum type, const GLvoid* pixels) {
+            EXPECT_EQ(_target, target);
+            EXPECT_EQ(_level, level);
+            EXPECT_EQ(_internalformat, internalformat);
+            EXPECT_EQ(_width, width);
+            EXPECT_EQ(_height, height);
+            EXPECT_EQ(_border, border);
+            EXPECT_EQ(_format, format);
+            EXPECT_EQ(_type, type);
+            EXPECT_EQ(0, memcmp(_pixels, pixels, sizeof(_pixels)));
+            texImage2D++;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    hooks.gl.glTexImage2D = caller.TexImage2D;
+    tls.dbg->expectResponse.Bit(msg.glTexImage2D, false);
+
+    Debug_glTexImage2D(_target, _level, _internalformat, _width, _height, _border,
+                       _format, _type, _pixels);
+    EXPECT_EQ(1, texImage2D);
+
+    Read(read);
+    EXPECT_EQ(read.glTexImage2D, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+    EXPECT_EQ(_target, read.arg0());
+    EXPECT_EQ(_level, read.arg1());
+    EXPECT_EQ(_internalformat, read.arg2());
+    EXPECT_EQ(_width, read.arg3());
+    EXPECT_EQ(_height, read.arg4());
+    EXPECT_EQ(_border, read.arg5());
+    EXPECT_EQ(_format, read.arg6());
+    EXPECT_EQ(_type, read.arg7());
+
+    EXPECT_TRUE(read.has_data());
+    uint32_t dataLen = 0;
+    const unsigned char * data = tls.dbg->Decompress(read.data().data(),
+                                 read.data().length(), &dataLen);
+    EXPECT_EQ(sizeof(_pixels), dataLen);
+    if (sizeof(_pixels) == dataLen)
+        EXPECT_EQ(0, memcmp(_pixels, data, sizeof(_pixels)));
+
+    Read(read);
+    EXPECT_EQ(read.glTexImage2D, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, CopyTexImage2D)
+{
+    static const GLenum _target = GL_TEXTURE_2D;
+    static const GLint _level = 1, _internalformat = GL_RGBA;
+    static const GLint _x = 9, _y = 99;
+    static const GLsizei _width = 2, _height = 3;
+    static const GLint _border = 333;
+    static const int _pixels [_width * _height] = {11, 22, 33, 44, 55, 66};
+    static unsigned int copyTexImage2D, readPixels;
+    copyTexImage2D = 0, readPixels = 0;
+
+    struct Caller {
+        static void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat,
+                                   GLint x, GLint y, GLsizei width, GLsizei height, GLint border) {
+            EXPECT_EQ(_target, target);
+            EXPECT_EQ(_level, level);
+            EXPECT_EQ(_internalformat, internalformat);
+            EXPECT_EQ(_x, x);
+            EXPECT_EQ(_y, y);
+            EXPECT_EQ(_width, width);
+            EXPECT_EQ(_height, height);
+            EXPECT_EQ(_border, border);
+            copyTexImage2D++;
+        }
+        static void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
+                               GLenum format, GLenum type, GLvoid* pixels) {
+            EXPECT_EQ(_x, x);
+            EXPECT_EQ(_y, y);
+            EXPECT_EQ(_width, width);
+            EXPECT_EQ(_height, height);
+            EXPECT_EQ(GL_RGBA, format);
+            EXPECT_EQ(GL_UNSIGNED_BYTE, type);
+            ASSERT_TRUE(pixels != NULL);
+            memcpy(pixels, _pixels, sizeof(_pixels));
+            readPixels++;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    hooks.gl.glCopyTexImage2D = caller.CopyTexImage2D;
+    hooks.gl.glReadPixels = caller.ReadPixels;
+    tls.dbg->expectResponse.Bit(msg.glCopyTexImage2D, false);
+
+    Debug_glCopyTexImage2D(_target, _level, _internalformat, _x, _y, _width, _height,
+                           _border);
+    ASSERT_EQ(1, copyTexImage2D);
+    ASSERT_EQ(1, readPixels);
+
+    Read(read);
+    EXPECT_EQ(read.glCopyTexImage2D, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+    EXPECT_EQ(_target, read.arg0());
+    EXPECT_EQ(_level, read.arg1());
+    EXPECT_EQ(_internalformat, read.arg2());
+    EXPECT_EQ(_x, read.arg3());
+    EXPECT_EQ(_y, read.arg4());
+    EXPECT_EQ(_width, read.arg5());
+    EXPECT_EQ(_height, read.arg6());
+    EXPECT_EQ(_border, read.arg7());
+
+    EXPECT_TRUE(read.has_data());
+    EXPECT_EQ(read.ReferencedImage, read.data_type());
+    EXPECT_EQ(GL_RGBA, read.pixel_format());
+    EXPECT_EQ(GL_UNSIGNED_BYTE, read.pixel_type());
+    uint32_t dataLen = 0;
+    unsigned char * const data = tls.dbg->Decompress(read.data().data(),
+                                 read.data().length(), &dataLen);
+    ASSERT_EQ(sizeof(_pixels), dataLen);
+    for (unsigned i = 0; i < sizeof(_pixels) / sizeof(*_pixels); i++)
+        EXPECT_EQ(_pixels[i], ((const int *)data)[i]) << "xor with 0 ref is identity";
+    free(data);
+
+    Read(read);
+    EXPECT_EQ(read.glCopyTexImage2D, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+
+    CheckNoAvailable();
+}
diff --git a/opengl/libs/egl_tls.h b/opengl/libs/egl_tls.h
new file mode 100644
index 0000000..087989a
--- /dev/null
+++ b/opengl/libs/egl_tls.h
@@ -0,0 +1,40 @@
+/*
+ ** 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.
+ */
+
+#ifndef ANDROID_EGL_TLS_H
+#define ANDROID_EGL_TLS_H
+
+#include <EGL/egl.h>
+
+#include "glesv2dbg.h"
+
+namespace android
+{
+struct tls_t {
+    tls_t() : error(EGL_SUCCESS), ctx(0), logCallWithNoContext(EGL_TRUE), dbg(0) { }
+    ~tls_t() {
+        if (dbg)
+            DestroyDbgContext(dbg);
+    }
+
+    EGLint      error;
+    EGLContext  ctx;
+    EGLBoolean  logCallWithNoContext;
+    DbgContext* dbg;
+};
+}
+
+#endif
diff --git a/opengl/libs/glesv2dbg.h b/opengl/libs/glesv2dbg.h
index 8029dce..ee2c011 100644
--- a/opengl/libs/glesv2dbg.h
+++ b/opengl/libs/glesv2dbg.h
@@ -13,20 +13,27 @@
  ** See the License for the specific language governing permissions and
  ** limitations under the License.
  */
- 
+
 #ifndef _GLESV2_DBG_H_
 #define _GLESV2_DBG_H_
 
+#include <pthread.h>
+
 namespace android
 {
-    struct DbgContext;
-    
-    DbgContext * CreateDbgContext(const unsigned version, const gl_hooks_t * const hooks);
-    void DestroyDbgContext(DbgContext * const dbg);
-    
-    void StartDebugServer(unsigned short port); // create and bind socket if haven't already
-    void StopDebugServer(); // close socket if open
-    
+struct DbgContext;
+
+DbgContext * CreateDbgContext(const pthread_key_t EGLThreadLocalStorageKey,
+                              const unsigned version, const gl_hooks_t * const hooks);
+
+void DestroyDbgContext(DbgContext * const dbg);
+
+// create and bind socket if haven't already, if failed to create socket or
+//  forceUseFile, then open /data/local/tmp/dump.gles2dbg, exit when size reached
+void StartDebugServer(const unsigned short port, const bool forceUseFile,
+                      const unsigned int maxFileSize, const char * const filePath);
+void StopDebugServer(); // close socket if open
+
 }; // namespace android
 
 #endif // #ifndef _GLESV2_DBG_H_
diff --git a/services/input/Android.mk b/services/input/Android.mk
index f9f8623..836c081 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -23,7 +23,6 @@
     InputReader.cpp \
     InputWindow.cpp \
     PointerController.cpp \
-    SpotController.cpp \
     SpriteController.cpp
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 6db445e..1bc1bd1 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -36,7 +36,6 @@
 // Log debug messages about gesture detection.
 #define DEBUG_GESTURES 0
 
-
 #include "InputReader.h"
 
 #include <cutils/log.h>
@@ -71,23 +70,47 @@
 
 // Tap gesture delay time.
 // The time between down and up must be less than this to be considered a tap.
-static const nsecs_t TAP_INTERVAL = 100 * 1000000; // 100 ms
+static const nsecs_t TAP_INTERVAL = 150 * 1000000; // 150 ms
 
 // The distance in pixels that the pointer is allowed to move from initial down
 // to up and still be called a tap.
 static const float TAP_SLOP = 5.0f; // 5 pixels
 
-// The transition from INDETERMINATE_MULTITOUCH to SWIPE or FREEFORM gesture mode is made when
-// all of the pointers have traveled this number of pixels from the start point.
-static const float MULTITOUCH_MIN_TRAVEL = 5.0f;
+// Time after the first touch points go down to settle on an initial centroid.
+// This is intended to be enough time to handle cases where the user puts down two
+// fingers at almost but not quite exactly the same time.
+static const nsecs_t MULTITOUCH_SETTLE_INTERVAL = 100 * 1000000; // 100ms
 
-// The transition from INDETERMINATE_MULTITOUCH to SWIPE gesture mode can only occur when the
+// The transition from PRESS to SWIPE or FREEFORM gesture mode is made when
+// both of the pointers are moving at least this fast.
+static const float MULTITOUCH_MIN_SPEED = 150.0f; // pixels per second
+
+// The transition from PRESS to SWIPE gesture mode can only occur when the
 // cosine of the angle between the two vectors is greater than or equal to than this value
 // which indicates that the vectors are oriented in the same direction.
 // When the vectors are oriented in the exactly same direction, the cosine is 1.0.
 // (In exactly opposite directions, the cosine is -1.0.)
 static const float SWIPE_TRANSITION_ANGLE_COSINE = 0.5f; // cosine of 45 degrees
 
+// The transition from PRESS to SWIPE gesture mode can only occur when the
+// fingers are no more than this far apart relative to the diagonal size of
+// the touch pad.  For example, a ratio of 0.5 means that the fingers must be
+// no more than half the diagonal size of the touch pad apart.
+static const float SWIPE_MAX_WIDTH_RATIO = 0.333f; // 1/3
+
+// The gesture movement speed factor relative to the size of the display.
+// Movement speed applies when the fingers are moving in the same direction.
+// Without acceleration, a full swipe of the touch pad diagonal in movement mode
+// will cover this portion of the display diagonal.
+static const float GESTURE_MOVEMENT_SPEED_RATIO = 0.8f;
+
+// The gesture zoom speed factor relative to the size of the display.
+// Zoom speed applies when the fingers are mostly moving relative to each other
+// to execute a scale gesture or similar.
+// Without acceleration, a full swipe of the touch pad diagonal in zoom mode
+// will cover this portion of the display diagonal.
+static const float GESTURE_ZOOM_SPEED_RATIO = 0.3f;
+
 
 // --- Static Functions ---
 
@@ -112,14 +135,8 @@
     return (x + y) / 2;
 }
 
-inline static float pythag(float x, float y) {
-    return sqrtf(x * x + y * y);
-}
-
-inline static int32_t distanceSquared(int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
-    int32_t dx = x1 - x2;
-    int32_t dy = y1 - y2;
-    return dx * dx + dy * dy;
+inline static float distance(float x1, float y1, float x2, float y2) {
+    return hypotf(x1 - x2, y1 - y2);
 }
 
 inline static int32_t signExtendNybble(int32_t value) {
@@ -224,6 +241,33 @@
     return edgeFlags;
 }
 
+static void clampPositionUsingPointerBounds(
+        const sp<PointerControllerInterface>& pointerController, float* x, float* y) {
+    float minX, minY, maxX, maxY;
+    if (pointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+        if (*x < minX) {
+            *x = minX;
+        } else if (*x > maxX) {
+            *x = maxX;
+        }
+        if (*y < minY) {
+            *y = minY;
+        } else if (*y > maxY) {
+            *y = maxY;
+        }
+    }
+}
+
+static float calculateCommonVector(float a, float b) {
+    if (a > 0 && b > 0) {
+        return a < b ? a : b;
+    } else if (a < 0 && b < 0) {
+        return a > b ? a : b;
+    } else {
+        return 0;
+    }
+}
+
 
 // --- InputReader ---
 
@@ -1553,10 +1597,32 @@
 
         motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE;
 
+        if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
+            vscroll = mAccumulator.relWheel;
+        } else {
+            vscroll = 0;
+        }
+        if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
+            hscroll = mAccumulator.relHWheel;
+        } else {
+            hscroll = 0;
+        }
+
         if (mPointerController != NULL) {
-            mPointerController->move(deltaX, deltaY);
-            if (buttonsChanged) {
-                mPointerController->setButtonState(mLocked.buttonState);
+            if (deltaX != 0 || deltaY != 0 || vscroll != 0 || hscroll != 0
+                    || buttonsChanged) {
+                mPointerController->setPresentation(
+                        PointerControllerInterface::PRESENTATION_POINTER);
+
+                if (deltaX != 0 || deltaY != 0) {
+                    mPointerController->move(deltaX, deltaY);
+                }
+
+                if (buttonsChanged) {
+                    mPointerController->setButtonState(mLocked.buttonState);
+                }
+
+                mPointerController->unfade();
             }
 
             float x, y;
@@ -1574,20 +1640,6 @@
         }
 
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
-
-        if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
-            vscroll = mAccumulator.relWheel;
-        } else {
-            vscroll = 0;
-        }
-        if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
-            hscroll = mAccumulator.relHWheel;
-        } else {
-            hscroll = 0;
-        }
-        if (hscroll != 0 || vscroll != 0) {
-            mPointerController->unfade();
-        }
     } // release lock
 
     // Moving an external trackball or mouse should wake the device.
@@ -1751,8 +1803,8 @@
                     mLocked.pointerGestureXZoomScale);
             dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n",
                     mLocked.pointerGestureYZoomScale);
-            dump.appendFormat(INDENT4 "MaxSwipeWidthSquared: %d\n",
-                    mLocked.pointerGestureMaxSwipeWidthSquared);
+            dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n",
+                    mLocked.pointerGestureMaxSwipeWidth);
         }
     } // release lock
 }
@@ -1825,6 +1877,10 @@
     mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
     mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime();
 
+    // TODO: Make this configurable.
+    //mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER;
+    mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS;
+
     if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X)
             || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
         // The device is a cursor device with a touch pad attached.
@@ -1983,7 +2039,7 @@
         mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale);
 
         // Size of diagonal axis.
-        float diagonalSize = pythag(width, height);
+        float diagonalSize = hypotf(width, height);
 
         // TouchMajor and TouchMinor factors.
         if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) {
@@ -2178,30 +2234,39 @@
         if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
             int32_t rawWidth = mRawAxes.x.maxValue - mRawAxes.x.minValue + 1;
             int32_t rawHeight = mRawAxes.y.maxValue - mRawAxes.y.minValue + 1;
+            float rawDiagonal = hypotf(rawWidth, rawHeight);
+            float displayDiagonal = hypotf(mLocked.associatedDisplayWidth,
+                    mLocked.associatedDisplayHeight);
 
-            // Scale movements such that one whole swipe of the touch pad covers a portion
-            // of the display along whichever axis of the touch pad is longer.
+            // Scale movements such that one whole swipe of the touch pad covers a
+            // given area relative to the diagonal size of the display.
             // Assume that the touch pad has a square aspect ratio such that movements in
             // X and Y of the same number of raw units cover the same physical distance.
             const float scaleFactor = 0.8f;
 
-            mLocked.pointerGestureXMovementScale = rawWidth > rawHeight
-                    ? scaleFactor * float(mLocked.associatedDisplayWidth) / rawWidth
-                    : scaleFactor * float(mLocked.associatedDisplayHeight) / rawHeight;
+            mLocked.pointerGestureXMovementScale = GESTURE_MOVEMENT_SPEED_RATIO
+                    * displayDiagonal / rawDiagonal;
             mLocked.pointerGestureYMovementScale = mLocked.pointerGestureXMovementScale;
 
             // Scale zooms to cover a smaller range of the display than movements do.
             // This value determines the area around the pointer that is affected by freeform
             // pointer gestures.
-            mLocked.pointerGestureXZoomScale = mLocked.pointerGestureXMovementScale * 0.4f;
-            mLocked.pointerGestureYZoomScale = mLocked.pointerGestureYMovementScale * 0.4f;
+            mLocked.pointerGestureXZoomScale = GESTURE_ZOOM_SPEED_RATIO
+                    * displayDiagonal / rawDiagonal;
+            mLocked.pointerGestureYZoomScale = mLocked.pointerGestureXZoomScale;
 
-            // Max width between pointers to detect a swipe gesture is 3/4 of the short
-            // axis of the touch pad.  Touches that are wider than this are translated
-            // into freeform gestures.
-            mLocked.pointerGestureMaxSwipeWidthSquared = min(rawWidth, rawHeight) * 3 / 4;
-            mLocked.pointerGestureMaxSwipeWidthSquared *=
-                    mLocked.pointerGestureMaxSwipeWidthSquared;
+            // Max width between pointers to detect a swipe gesture is more than some fraction
+            // of the diagonal axis of the touch pad.  Touches that are wider than this are
+            // translated into freeform gestures.
+            mLocked.pointerGestureMaxSwipeWidth = SWIPE_MAX_WIDTH_RATIO * rawDiagonal;
+
+            // Reset the current pointer gesture.
+            mPointerGesture.reset();
+
+            // Remove any current spots.
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerController->clearSpots();
+            }
         }
     }
 
@@ -2628,6 +2693,11 @@
     { // acquire lock
         AutoMutex _l(mLock);
         initializeLocked();
+
+        if (mPointerController != NULL
+                && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerController->clearSpots();
+        }
     } // release lock
 
     InputMapper::reset();
@@ -3070,7 +3140,7 @@
             int32_t c2 = signExtendNybble(in.orientation & 0x0f);
             if (c1 != 0 || c2 != 0) {
                 orientation = atan2f(c1, c2) * 0.5f;
-                float scale = 1.0f + pythag(c1, c2) / 16.0f;
+                float scale = 1.0f + hypotf(c1, c2) / 16.0f;
                 touchMajor *= scale;
                 touchMinor /= scale;
                 toolMajor *= scale;
@@ -3155,22 +3225,35 @@
 }
 
 void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags) {
+    // Switch pointer presentation.
+    mPointerController->setPresentation(
+            mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+                    ? PointerControllerInterface::PRESENTATION_SPOT
+                    : PointerControllerInterface::PRESENTATION_POINTER);
+
     // Update current gesture coordinates.
     bool cancelPreviousGesture, finishPreviousGesture;
     preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture);
 
+    // Show the pointer if needed.
+    if (mPointerGesture.currentGestureMode != PointerGesture::NEUTRAL
+            && mPointerGesture.currentGestureMode != PointerGesture::QUIET) {
+        mPointerController->unfade();
+    }
+
     // Send events!
     uint32_t metaState = getContext()->getGlobalMetaState();
 
     // Update last coordinates of pointers that have moved so that we observe the new
     // pointer positions at the same time as other pointers that have just gone up.
     bool down = mPointerGesture.currentGestureMode == PointerGesture::CLICK_OR_DRAG
+            || mPointerGesture.currentGestureMode == PointerGesture::PRESS
             || mPointerGesture.currentGestureMode == PointerGesture::SWIPE
             || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
     bool moveNeeded = false;
     if (down && !cancelPreviousGesture && !finishPreviousGesture
-            && mPointerGesture.lastGesturePointerCount != 0
-            && mPointerGesture.currentGesturePointerCount != 0) {
+            && !mPointerGesture.lastGestureIdBits.isEmpty()
+            && !mPointerGesture.currentGestureIdBits.isEmpty()) {
         BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value
                 & mPointerGesture.lastGestureIdBits.value);
         moveNeeded = updateMovedPointerCoords(
@@ -3284,11 +3367,8 @@
     // Update state.
     mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
     if (!down) {
-        mPointerGesture.lastGesturePointerCount = 0;
         mPointerGesture.lastGestureIdBits.clear();
     } else {
-        uint32_t currentGesturePointerCount = mPointerGesture.currentGesturePointerCount;
-        mPointerGesture.lastGesturePointerCount = currentGesturePointerCount;
         mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
         for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) {
             uint32_t id = idBits.firstMarkedBit();
@@ -3328,77 +3408,51 @@
     // Choose an arbitrary pointer that just went down, if there is one.
     // Otherwise choose an arbitrary remaining pointer.
     // This guarantees we always have an active touch id when there is at least one pointer.
-    // We always switch to the newest pointer down because that's usually where the user's
-    // attention is focused.
-    int32_t activeTouchId;
-    BitSet32 downTouchIdBits(mCurrentTouch.idBits.value & ~mLastTouch.idBits.value);
-    if (!downTouchIdBits.isEmpty()) {
-        activeTouchId = mPointerGesture.activeTouchId = downTouchIdBits.firstMarkedBit();
-    } else {
-        activeTouchId = mPointerGesture.activeTouchId;
-        if (activeTouchId < 0 || !mCurrentTouch.idBits.hasBit(activeTouchId)) {
-            if (!mCurrentTouch.idBits.isEmpty()) {
-                activeTouchId = mPointerGesture.activeTouchId =
-                        mCurrentTouch.idBits.firstMarkedBit();
-            } else {
-                activeTouchId = mPointerGesture.activeTouchId = -1;
-            }
+    // We keep the same active touch id for as long as possible.
+    bool activeTouchChanged = false;
+    int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
+    int32_t activeTouchId = lastActiveTouchId;
+    if (activeTouchId < 0) {
+        if (!mCurrentTouch.idBits.isEmpty()) {
+            activeTouchChanged = true;
+            activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit();
+            mPointerGesture.firstTouchTime = when;
         }
-    }
-
-    // Update the touch origin data to track where each finger originally went down.
-    if (mCurrentTouch.pointerCount == 0 || mPointerGesture.touchOrigin.pointerCount == 0) {
-        // Fast path when all fingers have gone up or down.
-        mPointerGesture.touchOrigin.copyFrom(mCurrentTouch);
-    } else {
-        // Slow path when only some fingers have gone up or down.
-        for (BitSet32 idBits(mPointerGesture.touchOrigin.idBits.value
-                & ~mCurrentTouch.idBits.value); !idBits.isEmpty(); ) {
-            uint32_t id = idBits.firstMarkedBit();
-            idBits.clearBit(id);
-            mPointerGesture.touchOrigin.idBits.clearBit(id);
-            uint32_t index = mPointerGesture.touchOrigin.idToIndex[id];
-            uint32_t count = --mPointerGesture.touchOrigin.pointerCount;
-            while (index < count) {
-                mPointerGesture.touchOrigin.pointers[index] =
-                        mPointerGesture.touchOrigin.pointers[index + 1];
-                uint32_t movedId = mPointerGesture.touchOrigin.pointers[index].id;
-                mPointerGesture.touchOrigin.idToIndex[movedId] = index;
-                index += 1;
-            }
-        }
-        for (BitSet32 idBits(mCurrentTouch.idBits.value
-                & ~mPointerGesture.touchOrigin.idBits.value); !idBits.isEmpty(); ) {
-            uint32_t id = idBits.firstMarkedBit();
-            idBits.clearBit(id);
-            mPointerGesture.touchOrigin.idBits.markBit(id);
-            uint32_t index = mPointerGesture.touchOrigin.pointerCount++;
-            mPointerGesture.touchOrigin.pointers[index] =
-                    mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
-            mPointerGesture.touchOrigin.idToIndex[id] = index;
+    } else if (!mCurrentTouch.idBits.hasBit(activeTouchId)) {
+        activeTouchChanged = true;
+        if (!mCurrentTouch.idBits.isEmpty()) {
+            activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit();
+        } else {
+            activeTouchId = mPointerGesture.activeTouchId = -1;
         }
     }
 
     // Determine whether we are in quiet time.
-    bool isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL;
-    if (!isQuietTime) {
-        if ((mPointerGesture.lastGestureMode == PointerGesture::SWIPE
-                || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
-                && mCurrentTouch.pointerCount < 2) {
-            // Enter quiet time when exiting swipe or freeform state.
-            // This is to prevent accidentally entering the hover state and flinging the
-            // pointer when finishing a swipe and there is still one pointer left onscreen.
-            isQuietTime = true;
-        } else if (mPointerGesture.lastGestureMode == PointerGesture::CLICK_OR_DRAG
-                && mCurrentTouch.pointerCount >= 2
-                && !isPointerDown(mCurrentTouch.buttonState)) {
-            // Enter quiet time when releasing the button and there are still two or more
-            // fingers down.  This may indicate that one finger was used to press the button
-            // but it has not gone up yet.
-            isQuietTime = true;
-        }
-        if (isQuietTime) {
-            mPointerGesture.quietTime = when;
+    bool isQuietTime = false;
+    if (activeTouchId < 0) {
+        mPointerGesture.resetQuietTime();
+    } else {
+        isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL;
+        if (!isQuietTime) {
+            if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS
+                    || mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+                    || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
+                    && mCurrentTouch.pointerCount < 2) {
+                // Enter quiet time when exiting swipe or freeform state.
+                // This is to prevent accidentally entering the hover state and flinging the
+                // pointer when finishing a swipe and there is still one pointer left onscreen.
+                isQuietTime = true;
+            } else if (mPointerGesture.lastGestureMode == PointerGesture::CLICK_OR_DRAG
+                    && mCurrentTouch.pointerCount >= 2
+                    && !isPointerDown(mCurrentTouch.buttonState)) {
+                // Enter quiet time when releasing the button and there are still two or more
+                // fingers down.  This may indicate that one finger was used to press the button
+                // but it has not gone up yet.
+                isQuietTime = true;
+            }
+            if (isQuietTime) {
+                mPointerGesture.quietTime = when;
+            }
         }
     }
 
@@ -3413,10 +3467,17 @@
 
         mPointerGesture.activeGestureId = -1;
         mPointerGesture.currentGestureMode = PointerGesture::QUIET;
-        mPointerGesture.currentGesturePointerCount = 0;
         mPointerGesture.currentGestureIdBits.clear();
+
+        mPointerController->setButtonState(0);
+
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+            mPointerGesture.spotIdBits.clear();
+            moveSpotsLocked();
+        }
     } else if (isPointerDown(mCurrentTouch.buttonState)) {
-        // Case 2: Button is pressed. (DRAG)
+        // Case 2: Button is pressed. (CLICK_OR_DRAG)
         // The pointer follows the active touch point.
         // Emit DOWN, MOVE, UP events at the pointer location.
         //
@@ -3449,7 +3510,7 @@
                     uint32_t id = mCurrentTouch.pointers[i].id;
                     float vx, vy;
                     if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
-                        float speed = pythag(vx, vy);
+                        float speed = hypotf(vx, vy);
                         if (speed > bestSpeed) {
                             bestId = id;
                             bestSpeed = speed;
@@ -3458,6 +3519,7 @@
                 }
                 if (bestId >= 0 && bestId != activeTouchId) {
                     mPointerGesture.activeTouchId = activeTouchId = bestId;
+                    activeTouchChanged = true;
 #if DEBUG_GESTURES
                     LOGD("Gestures: CLICK_OR_DRAG switched pointers, "
                             "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed);
@@ -3474,6 +3536,10 @@
                         * mLocked.pointerGestureXMovementScale;
                 float deltaY = (currentPointer.y - lastPointer.y)
                         * mLocked.pointerGestureYMovementScale;
+
+                // Move the pointer using a relative motion.
+                // When using spots, the click will occur at the position of the anchor
+                // spot and all other spots will move there.
                 mPointerController->move(deltaX, deltaY);
             }
         }
@@ -3482,7 +3548,6 @@
         mPointerController->getPosition(&x, &y);
 
         mPointerGesture.currentGestureMode = PointerGesture::CLICK_OR_DRAG;
-        mPointerGesture.currentGesturePointerCount = 1;
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@@ -3490,26 +3555,49 @@
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+        mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            if (activeTouchId >= 0) {
+                // Collapse all spots into one point at the pointer location.
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_DRAG;
+                mPointerGesture.spotIdBits.clear();
+                for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
+                    uint32_t id = mCurrentTouch.pointers[i].id;
+                    mPointerGesture.spotIdBits.markBit(id);
+                    mPointerGesture.spotIdToIndex[id] = i;
+                    mPointerGesture.spotCoords[i] = mPointerGesture.currentGestureCoords[0];
+                }
+            } else {
+                // No fingers.  Generate a spot at the pointer location so the
+                // anchor appears to be pressed.
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_CLICK;
+                mPointerGesture.spotIdBits.clear();
+                mPointerGesture.spotIdBits.markBit(0);
+                mPointerGesture.spotIdToIndex[0] = 0;
+                mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+            }
+            moveSpotsLocked();
+        }
     } else if (mCurrentTouch.pointerCount == 0) {
         // Case 3. No fingers down and button is not pressed. (NEUTRAL)
         *outFinishPreviousGesture = true;
 
-        // Watch for taps coming out of HOVER or INDETERMINATE_MULTITOUCH mode.
+        // Watch for taps coming out of HOVER mode.
         bool tapped = false;
         if (mPointerGesture.lastGestureMode == PointerGesture::HOVER
-                || mPointerGesture.lastGestureMode
-                        == PointerGesture::INDETERMINATE_MULTITOUCH) {
+                && mLastTouch.pointerCount == 1) {
             if (when <= mPointerGesture.tapTime + TAP_INTERVAL) {
                 float x, y;
                 mPointerController->getPosition(&x, &y);
-                if (fabs(x - mPointerGesture.initialPointerX) <= TAP_SLOP
-                        && fabs(y - mPointerGesture.initialPointerY) <= TAP_SLOP) {
+                if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP
+                        && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) {
 #if DEBUG_GESTURES
                     LOGD("Gestures: TAP");
 #endif
                     mPointerGesture.activeGestureId = 0;
                     mPointerGesture.currentGestureMode = PointerGesture::TAP;
-                    mPointerGesture.currentGesturePointerCount = 1;
                     mPointerGesture.currentGestureIdBits.clear();
                     mPointerGesture.currentGestureIdBits.markBit(
                             mPointerGesture.activeGestureId);
@@ -3517,17 +3605,30 @@
                             mPointerGesture.activeGestureId] = 0;
                     mPointerGesture.currentGestureCoords[0].clear();
                     mPointerGesture.currentGestureCoords[0].setAxisValue(
-                            AMOTION_EVENT_AXIS_X, mPointerGesture.initialPointerX);
+                            AMOTION_EVENT_AXIS_X, mPointerGesture.tapX);
                     mPointerGesture.currentGestureCoords[0].setAxisValue(
-                            AMOTION_EVENT_AXIS_Y, mPointerGesture.initialPointerY);
+                            AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY);
                     mPointerGesture.currentGestureCoords[0].setAxisValue(
                             AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+                    mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+                    mPointerController->setButtonState(0);
+
+                    if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                        mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_TAP;
+                        mPointerGesture.spotIdBits.clear();
+                        mPointerGesture.spotIdBits.markBit(lastActiveTouchId);
+                        mPointerGesture.spotIdToIndex[lastActiveTouchId] = 0;
+                        mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+                        moveSpotsLocked();
+                    }
+
                     tapped = true;
                 } else {
 #if DEBUG_GESTURES
                     LOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f",
-                            x - mPointerGesture.initialPointerX,
-                            y - mPointerGesture.initialPointerY);
+                            x - mPointerGesture.tapX,
+                            y - mPointerGesture.tapY);
 #endif
                 }
             } else {
@@ -3537,14 +3638,22 @@
 #endif
             }
         }
+
         if (!tapped) {
 #if DEBUG_GESTURES
             LOGD("Gestures: NEUTRAL");
 #endif
             mPointerGesture.activeGestureId = -1;
             mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
-            mPointerGesture.currentGesturePointerCount = 0;
             mPointerGesture.currentGestureIdBits.clear();
+
+            mPointerController->setButtonState(0);
+
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+                mPointerGesture.spotIdBits.clear();
+                moveSpotsLocked();
+            }
         }
     } else if (mCurrentTouch.pointerCount == 1) {
         // Case 4. Exactly one finger down, button is not pressed. (HOVER)
@@ -3565,6 +3674,9 @@
                     * mLocked.pointerGestureXMovementScale;
             float deltaY = (currentPointer.y - lastPointer.y)
                     * mLocked.pointerGestureYMovementScale;
+
+            // Move the pointer using a relative motion.
+            // When using spots, the hover will occur at the position of the anchor spot.
             mPointerController->move(deltaX, deltaY);
         }
 
@@ -3575,7 +3687,6 @@
         mPointerController->getPosition(&x, &y);
 
         mPointerGesture.currentGestureMode = PointerGesture::HOVER;
-        mPointerGesture.currentGesturePointerCount = 1;
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@@ -3586,159 +3697,268 @@
 
         if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
             mPointerGesture.tapTime = when;
-            mPointerGesture.initialPointerX = x;
-            mPointerGesture.initialPointerY = y;
+            mPointerGesture.tapX = x;
+            mPointerGesture.tapY = y;
+        }
+
+        mPointerController->setButtonState(0);
+
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_HOVER;
+            mPointerGesture.spotIdBits.clear();
+            mPointerGesture.spotIdBits.markBit(activeTouchId);
+            mPointerGesture.spotIdToIndex[activeTouchId] = 0;
+            mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+            moveSpotsLocked();
         }
     } else {
-        // Case 5. At least two fingers down, button is not pressed. (SWIPE or FREEFORM
-        // or INDETERMINATE_MULTITOUCH)
-        // Initially we watch and wait for something interesting to happen so as to
-        // avoid making a spurious guess as to the nature of the gesture.  For example,
-        // the fingers may be in transition to some other state such as pressing or
-        // releasing the button or we may be performing a two finger tap.
+        // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
+        // We need to provide feedback for each finger that goes down so we cannot wait
+        // for the fingers to move before deciding what to do.
         //
-        // Fix the centroid of the figure when the gesture actually starts.
-        // We do not recalculate the centroid at any other time during the gesture because
-        // it would affect the relationship of the touch points relative to the pointer location.
+        // The ambiguous case is deciding what to do when there are two fingers down but they
+        // have not moved enough to determine whether they are part of a drag or part of a
+        // freeform gesture, or just a press or long-press at the pointer location.
+        //
+        // When there are two fingers we start with the PRESS hypothesis and we generate a
+        // down at the pointer location.
+        //
+        // When the two fingers move enough or when additional fingers are added, we make
+        // a decision to transition into SWIPE or FREEFORM mode accordingly.
         LOG_ASSERT(activeTouchId >= 0);
 
-        uint32_t currentTouchPointerCount = mCurrentTouch.pointerCount;
-        if (currentTouchPointerCount > MAX_POINTERS) {
-            currentTouchPointerCount = MAX_POINTERS;
-        }
-
-        if (mPointerGesture.lastGestureMode != PointerGesture::INDETERMINATE_MULTITOUCH
+        bool needReference = false;
+        bool settled = when >= mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL;
+        if (mPointerGesture.lastGestureMode != PointerGesture::PRESS
                 && mPointerGesture.lastGestureMode != PointerGesture::SWIPE
                 && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
-            mPointerGesture.currentGestureMode = PointerGesture::INDETERMINATE_MULTITOUCH;
-
             *outFinishPreviousGesture = true;
-            mPointerGesture.activeGestureId = -1;
+            mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+            mPointerGesture.activeGestureId = 0;
 
-            // Remember the initial pointer location.
-            // Everything we do will be relative to this location.
-            mPointerController->getPosition(&mPointerGesture.initialPointerX,
-                    &mPointerGesture.initialPointerY);
-
-            // Track taps.
-            if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
-                mPointerGesture.tapTime = when;
+            if (settled && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+                    && mLastTouch.idBits.hasBit(mPointerGesture.activeTouchId)) {
+                // The spot is already visible and has settled, use it as the reference point
+                // for the gesture.  Other spots will be positioned relative to this one.
+#if DEBUG_GESTURES
+                LOGD("Gestures: Using active spot as reference for MULTITOUCH, "
+                        "settle time expired %0.3fms ago",
+                        (when - mPointerGesture.firstTouchTime - MULTITOUCH_SETTLE_INTERVAL)
+                                * 0.000001f);
+#endif
+                const PointerData& d = mLastTouch.pointers[mLastTouch.idToIndex[
+                        mPointerGesture.activeTouchId]];
+                mPointerGesture.referenceTouchX = d.x;
+                mPointerGesture.referenceTouchY = d.y;
+                const PointerCoords& c = mPointerGesture.spotCoords[mPointerGesture.spotIdToIndex[
+                        mPointerGesture.activeTouchId]];
+                mPointerGesture.referenceGestureX = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+                mPointerGesture.referenceGestureY = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+            } else {
+#if DEBUG_GESTURES
+                LOGD("Gestures: Using centroid as reference for MULTITOUCH, "
+                        "settle time remaining %0.3fms",
+                        (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when)
+                                * 0.000001f);
+#endif
+                needReference = true;
             }
-
-            // Reset the touch origin to be relative to exactly where the fingers are now
-            // in case they have moved some distance away as part of a previous gesture.
-            // We want to know how far the fingers have traveled since we started considering
-            // a multitouch gesture.
-            mPointerGesture.touchOrigin.copyFrom(mCurrentTouch);
+        } else if (!settled && mCurrentTouch.pointerCount > mLastTouch.pointerCount) {
+            // Additional pointers have gone down but not yet settled.
+            // Reset the gesture.
+#if DEBUG_GESTURES
+            LOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
+                    "settle time remaining %0.3fms",
+                    (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when)
+                            * 0.000001f);
+#endif
+            *outCancelPreviousGesture = true;
+            mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+            mPointerGesture.activeGestureId = 0;
         } else {
+            // Continue previous gesture.
             mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
         }
 
-        if (mPointerGesture.currentGestureMode == PointerGesture::INDETERMINATE_MULTITOUCH) {
-            // Wait for the pointers to start moving before doing anything.
-            bool decideNow = true;
-            for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
-                const PointerData& current = mCurrentTouch.pointers[i];
-                const PointerData& origin = mPointerGesture.touchOrigin.pointers[
-                        mPointerGesture.touchOrigin.idToIndex[current.id]];
-                float distance = pythag(
-                        (current.x - origin.x) * mLocked.pointerGestureXZoomScale,
-                        (current.y - origin.y) * mLocked.pointerGestureYZoomScale);
-                if (distance < MULTITOUCH_MIN_TRAVEL) {
-                    decideNow = false;
-                    break;
-                }
-            }
+        if (needReference) {
+            // Use the centroid and pointer location as the reference points for the gesture.
+            mCurrentTouch.getCentroid(&mPointerGesture.referenceTouchX,
+                    &mPointerGesture.referenceTouchY);
+            mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+                    &mPointerGesture.referenceGestureY);
+        }
 
-            if (decideNow) {
+        if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+            float d;
+            if (mCurrentTouch.pointerCount > 2) {
+                // There are more than two pointers, switch to FREEFORM.
+#if DEBUG_GESTURES
+                LOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+                        mCurrentTouch.pointerCount);
+#endif
+                *outCancelPreviousGesture = true;
                 mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
-                if (currentTouchPointerCount == 2
-                        && distanceSquared(
-                                mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y,
-                                mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y)
-                                <= mLocked.pointerGestureMaxSwipeWidthSquared) {
-                    const PointerData& current1 = mCurrentTouch.pointers[0];
-                    const PointerData& current2 = mCurrentTouch.pointers[1];
-                    const PointerData& origin1 = mPointerGesture.touchOrigin.pointers[
-                            mPointerGesture.touchOrigin.idToIndex[current1.id]];
-                    const PointerData& origin2 = mPointerGesture.touchOrigin.pointers[
-                            mPointerGesture.touchOrigin.idToIndex[current2.id]];
+            } else if (((d = distance(
+                    mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y,
+                    mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y))
+                            > mLocked.pointerGestureMaxSwipeWidth)) {
+                // There are two pointers but they are too far apart, switch to FREEFORM.
+#if DEBUG_GESTURES
+                LOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+                        d, mLocked.pointerGestureMaxSwipeWidth);
+#endif
+                *outCancelPreviousGesture = true;
+                mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+            } else {
+                // There are two pointers.  Wait for both pointers to start moving
+                // before deciding whether this is a SWIPE or FREEFORM gesture.
+                uint32_t id1 = mCurrentTouch.pointers[0].id;
+                uint32_t id2 = mCurrentTouch.pointers[1].id;
 
-                    float x1 = (current1.x - origin1.x) * mLocked.pointerGestureXZoomScale;
-                    float y1 = (current1.y - origin1.y) * mLocked.pointerGestureYZoomScale;
-                    float x2 = (current2.x - origin2.x) * mLocked.pointerGestureXZoomScale;
-                    float y2 = (current2.y - origin2.y) * mLocked.pointerGestureYZoomScale;
-                    float magnitude1 = pythag(x1, y1);
-                    float magnitude2 = pythag(x2, y2);
+                float vx1, vy1, vx2, vy2;
+                mPointerGesture.velocityTracker.getVelocity(id1, &vx1, &vy1);
+                mPointerGesture.velocityTracker.getVelocity(id2, &vx2, &vy2);
 
-                    // Calculate the dot product of the vectors.
+                float speed1 = hypotf(vx1, vy1);
+                float speed2 = hypotf(vx2, vy2);
+                if (speed1 >= MULTITOUCH_MIN_SPEED && speed2 >= MULTITOUCH_MIN_SPEED) {
+                    // Calculate the dot product of the velocity vectors.
                     // When the vectors are oriented in approximately the same direction,
                     // the angle betweeen them is near zero and the cosine of the angle
                     // approches 1.0.  Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2).
-                    // We know that the magnitude is at least MULTITOUCH_MIN_TRAVEL because
-                    // we checked it above.
-                    float dot = x1 * x2 + y1 * y2;
-                    float cosine = dot / (magnitude1 * magnitude2); // denominator always > 0
-                    if (cosine > SWIPE_TRANSITION_ANGLE_COSINE) {
+                    float dot = vx1 * vx2 + vy1 * vy2;
+                    float cosine = dot / (speed1 * speed2); // denominator always > 0
+                    if (cosine >= SWIPE_TRANSITION_ANGLE_COSINE) {
+                        // Pointers are moving in the same direction.  Switch to SWIPE.
+#if DEBUG_GESTURES
+                        LOGD("Gestures: PRESS transitioned to SWIPE, "
+                                "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, "
+                                "cosine %0.3f >= %0.3f",
+                                speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED,
+                                cosine, SWIPE_TRANSITION_ANGLE_COSINE);
+#endif
                         mPointerGesture.currentGestureMode = PointerGesture::SWIPE;
+                    } else {
+                        // Pointers are moving in different directions.  Switch to FREEFORM.
+#if DEBUG_GESTURES
+                        LOGD("Gestures: PRESS transitioned to FREEFORM, "
+                                "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, "
+                                "cosine %0.3f < %0.3f",
+                                speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED,
+                                cosine, SWIPE_TRANSITION_ANGLE_COSINE);
+#endif
+                        *outCancelPreviousGesture = true;
+                        mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
                     }
                 }
-
-                // Remember the initial centroid for the duration of the gesture.
-                mPointerGesture.initialCentroidX = 0;
-                mPointerGesture.initialCentroidY = 0;
-                for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
-                    const PointerData& touch = mCurrentTouch.pointers[i];
-                    mPointerGesture.initialCentroidX += touch.x;
-                    mPointerGesture.initialCentroidY += touch.y;
-                }
-                mPointerGesture.initialCentroidX /= int32_t(currentTouchPointerCount);
-                mPointerGesture.initialCentroidY /= int32_t(currentTouchPointerCount);
-
-                mPointerGesture.activeGestureId = 0;
             }
         } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
-            // Switch to FREEFORM if additional pointers go down.
-            if (currentTouchPointerCount > 2) {
+            // Switch from SWIPE to FREEFORM if additional pointers go down.
+            // Cancel previous gesture.
+            if (mCurrentTouch.pointerCount > 2) {
+#if DEBUG_GESTURES
+                LOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+                        mCurrentTouch.pointerCount);
+#endif
                 *outCancelPreviousGesture = true;
                 mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
             }
         }
 
-        if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
-            // SWIPE mode.
+        // Move the reference points based on the overall group motion of the fingers.
+        // The objective is to calculate a vector delta that is common to the movement
+        // of all fingers.
+        BitSet32 commonIdBits(mLastTouch.idBits.value & mCurrentTouch.idBits.value);
+        if (!commonIdBits.isEmpty()) {
+            float commonDeltaX = 0, commonDeltaY = 0;
+            for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) {
+                bool first = (idBits == commonIdBits);
+                uint32_t id = idBits.firstMarkedBit();
+                idBits.clearBit(id);
+
+                const PointerData& cpd = mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
+                const PointerData& lpd = mLastTouch.pointers[mLastTouch.idToIndex[id]];
+                float deltaX = cpd.x - lpd.x;
+                float deltaY = cpd.y - lpd.y;
+
+                if (first) {
+                    commonDeltaX = deltaX;
+                    commonDeltaY = deltaY;
+                } else {
+                    commonDeltaX = calculateCommonVector(commonDeltaX, deltaX);
+                    commonDeltaY = calculateCommonVector(commonDeltaY, deltaY);
+                }
+            }
+
+            mPointerGesture.referenceTouchX += commonDeltaX;
+            mPointerGesture.referenceTouchY += commonDeltaY;
+            mPointerGesture.referenceGestureX +=
+                    commonDeltaX * mLocked.pointerGestureXMovementScale;
+            mPointerGesture.referenceGestureY +=
+                    commonDeltaY * mLocked.pointerGestureYMovementScale;
+            clampPositionUsingPointerBounds(mPointerController,
+                    &mPointerGesture.referenceGestureX,
+                    &mPointerGesture.referenceGestureY);
+        }
+
+        // Report gestures.
+        if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+            // PRESS mode.
 #if DEBUG_GESTURES
-            LOGD("Gestures: SWIPE activeTouchId=%d,"
+            LOGD("Gestures: PRESS activeTouchId=%d,"
                     "activeGestureId=%d, currentTouchPointerCount=%d",
-                    activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount);
+                    activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
 #endif
             LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
 
-            float x = (mCurrentTouch.pointers[0].x + mCurrentTouch.pointers[1].x
-                    - mPointerGesture.initialCentroidX * 2) * 0.5f
-                    * mLocked.pointerGestureXMovementScale + mPointerGesture.initialPointerX;
-            float y = (mCurrentTouch.pointers[0].y + mCurrentTouch.pointers[1].y
-                    - mPointerGesture.initialCentroidY * 2) * 0.5f
-                    * mLocked.pointerGestureYMovementScale + mPointerGesture.initialPointerY;
-
-            mPointerGesture.currentGesturePointerCount = 1;
             mPointerGesture.currentGestureIdBits.clear();
             mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
             mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
             mPointerGesture.currentGestureCoords[0].clear();
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                    mPointerGesture.referenceGestureX);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                    mPointerGesture.referenceGestureY);
             mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+            mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_PRESS;
+            }
+        } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+            // SWIPE mode.
+#if DEBUG_GESTURES
+            LOGD("Gestures: SWIPE activeTouchId=%d,"
+                    "activeGestureId=%d, currentTouchPointerCount=%d",
+                    activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
+#endif
+            LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+            mPointerGesture.currentGestureIdBits.clear();
+            mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+            mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+            mPointerGesture.currentGestureCoords[0].clear();
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                    mPointerGesture.referenceGestureX);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                    mPointerGesture.referenceGestureY);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+            mPointerController->setButtonState(0); // touch is not actually following the pointer
+
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_SWIPE;
+            }
         } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
             // FREEFORM mode.
 #if DEBUG_GESTURES
             LOGD("Gestures: FREEFORM activeTouchId=%d,"
                     "activeGestureId=%d, currentTouchPointerCount=%d",
-                    activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount);
+                    activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
 #endif
             LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
 
-            mPointerGesture.currentGesturePointerCount = currentTouchPointerCount;
             mPointerGesture.currentGestureIdBits.clear();
 
             BitSet32 mappedTouchIdBits;
@@ -3782,7 +4002,7 @@
                     mPointerGesture.activeGestureId);
 #endif
 
-            for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
+            for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
                 uint32_t touchId = mCurrentTouch.pointers[i].id;
                 uint32_t gestureId;
                 if (!mappedTouchIdBits.hasBit(touchId)) {
@@ -3805,10 +4025,10 @@
                 mPointerGesture.currentGestureIdBits.markBit(gestureId);
                 mPointerGesture.currentGestureIdToIndex[gestureId] = i;
 
-                float x = (mCurrentTouch.pointers[i].x - mPointerGesture.initialCentroidX)
-                        * mLocked.pointerGestureXZoomScale + mPointerGesture.initialPointerX;
-                float y = (mCurrentTouch.pointers[i].y - mPointerGesture.initialCentroidY)
-                        * mLocked.pointerGestureYZoomScale + mPointerGesture.initialPointerY;
+                float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX)
+                        * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX;
+                float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY)
+                        * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY;
 
                 mPointerGesture.currentGestureCoords[i].clear();
                 mPointerGesture.currentGestureCoords[i].setAxisValue(
@@ -3827,30 +4047,45 @@
                         "activeGestureId=%d", mPointerGesture.activeGestureId);
 #endif
             }
-        } else {
-            // INDETERMINATE_MULTITOUCH mode.
-            // Do nothing.
-#if DEBUG_GESTURES
-            LOGD("Gestures: INDETERMINATE_MULTITOUCH");
-#endif
-        }
-    }
 
-    // Unfade the pointer if the user is doing anything with the touch pad.
-    mPointerController->setButtonState(mCurrentTouch.buttonState);
-    if (mCurrentTouch.buttonState || mCurrentTouch.pointerCount != 0) {
-        mPointerController->unfade();
+            mPointerController->setButtonState(0); // touch is not actually following the pointer
+
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_FREEFORM;
+            }
+        }
+
+        // Update spot locations for PRESS, SWIPE and FREEFORM.
+        // We use the same calculation as we do to calculate the gesture pointers
+        // for FREEFORM so that the spots smoothly track gestures.
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerGesture.spotIdBits.clear();
+            for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
+                uint32_t id = mCurrentTouch.pointers[i].id;
+                mPointerGesture.spotIdBits.markBit(id);
+                mPointerGesture.spotIdToIndex[id] = i;
+
+                float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX)
+                        * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX;
+                float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY)
+                        * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY;
+
+                mPointerGesture.spotCoords[i].clear();
+                mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+                mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+                mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+            }
+            moveSpotsLocked();
+        }
     }
 
 #if DEBUG_GESTURES
     LOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
-            "currentGestureMode=%d, currentGesturePointerCount=%d, currentGestureIdBits=0x%08x, "
-            "lastGestureMode=%d, lastGesturePointerCount=%d, lastGestureIdBits=0x%08x",
+            "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+            "lastGestureMode=%d, lastGestureIdBits=0x%08x",
             toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
-            mPointerGesture.currentGestureMode, mPointerGesture.currentGesturePointerCount,
-            mPointerGesture.currentGestureIdBits.value,
-            mPointerGesture.lastGestureMode, mPointerGesture.lastGesturePointerCount,
-            mPointerGesture.lastGestureIdBits.value);
+            mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+            mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
     for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) {
         uint32_t id = idBits.firstMarkedBit();
         idBits.clearBit(id);
@@ -3874,6 +4109,11 @@
 #endif
 }
 
+void TouchInputMapper::moveSpotsLocked() {
+    mPointerController->setSpots(mPointerGesture.spotGesture,
+            mPointerGesture.spotCoords, mPointerGesture.spotIdToIndex, mPointerGesture.spotIdBits);
+}
+
 void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
         int32_t action, int32_t flags, uint32_t metaState, int32_t edgeFlags,
         const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits,
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 9ed1391..9b2f4d2 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -20,7 +20,6 @@
 #include "EventHub.h"
 #include "InputDispatcher.h"
 #include "PointerController.h"
-#include "SpotController.h"
 
 #include <ui/Input.h>
 #include <ui/DisplayInfo.h>
@@ -90,9 +89,6 @@
 
     /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
-
-    /* Gets a spot controller associated with the specified touch pad device. */
-    virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId) = 0;
 };
 
 
@@ -648,6 +644,20 @@
             idBits.clear();
             buttonState = 0;
         }
+
+        void getCentroid(float* outX, float* outY) {
+            float x = 0, y = 0;
+            if (pointerCount != 0) {
+                for (uint32_t i = 0; i < pointerCount; i++) {
+                    x += pointers[i].x;
+                    y += pointers[i].y;
+                }
+                x /= pointerCount;
+                y /= pointerCount;
+            }
+            *outX = x;
+            *outY = y;
+        }
     };
 
     // Input sources supported by the device.
@@ -670,6 +680,12 @@
         bool useJumpyTouchFilter;
         bool useAveragingTouchFilter;
         nsecs_t virtualKeyQuietTime;
+
+        enum GestureMode {
+            GESTURE_MODE_POINTER,
+            GESTURE_MODE_SPOTS,
+        };
+        GestureMode gestureMode;
     } mParameters;
 
     // Immutable calibration parameters in parsed form.
@@ -841,8 +857,8 @@
         float pointerGestureXZoomScale;
         float pointerGestureYZoomScale;
 
-        // The maximum swipe width squared.
-        int32_t pointerGestureMaxSwipeWidthSquared;
+        // The maximum swipe width.
+        float pointerGestureMaxSwipeWidth;
     } mLocked;
 
     virtual void configureParameters();
@@ -929,28 +945,32 @@
             // Emits HOVER_MOVE events at the pointer location.
             HOVER,
 
-            // More than two fingers involved but they haven't moved enough for us
-            // to figure out what is intended.
-            INDETERMINATE_MULTITOUCH,
+            // Exactly two fingers but neither have moved enough to clearly indicate
+            // whether a swipe or freeform gesture was intended.  We consider the
+            // pointer to be pressed so this enables clicking or long-pressing on buttons.
+            // Pointer does not move.
+            // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate.
+            PRESS,
 
             // Exactly two fingers moving in the same direction, button is not pressed.
             // Pointer does not move.
             // Emits DOWN, MOVE and UP events with a single pointer coordinate that
             // follows the midpoint between both fingers.
-            // The centroid is fixed when entering this state.
             SWIPE,
 
             // Two or more fingers moving in arbitrary directions, button is not pressed.
             // Pointer does not move.
             // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
             // each finger individually relative to the initial centroid of the finger.
-            // The centroid is fixed when entering this state.
             FREEFORM,
 
             // Waiting for quiet time to end before starting the next gesture.
             QUIET,
         };
 
+        // Time the first finger went down.
+        nsecs_t firstTouchTime;
+
         // The active pointer id from the raw touch data.
         int32_t activeTouchId; // -1 if none
 
@@ -959,32 +979,20 @@
 
         // Pointer coords and ids for the current and previous pointer gesture.
         Mode currentGestureMode;
-        uint32_t currentGesturePointerCount;
         BitSet32 currentGestureIdBits;
         uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
         PointerCoords currentGestureCoords[MAX_POINTERS];
 
         Mode lastGestureMode;
-        uint32_t lastGesturePointerCount;
         BitSet32 lastGestureIdBits;
         uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
         PointerCoords lastGestureCoords[MAX_POINTERS];
 
-        // Tracks for all pointers originally went down.
-        TouchData touchOrigin;
-
-        // Describes how touch ids are mapped to gesture ids for freeform gestures.
-        uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
-
-        // Initial centroid of the movement.
-        // Used to calculate how far the touch pointers have moved since the gesture started.
-        int32_t initialCentroidX;
-        int32_t initialCentroidY;
-
-        // Initial pointer location.
-        // Used to track where the pointer was when the gesture started.
-        float initialPointerX;
-        float initialPointerY;
+        // Pointer coords and ids for the current spots.
+        PointerControllerInterface::SpotGesture spotGesture;
+        BitSet32 spotIdBits; // same set of ids as touch ids
+        uint32_t spotIdToIndex[MAX_POINTER_ID + 1];
+        PointerCoords spotCoords[MAX_POINTERS];
 
         // Time the pointer gesture last went down.
         nsecs_t downTime;
@@ -992,26 +1000,34 @@
         // Time we started waiting for a tap gesture.
         nsecs_t tapTime;
 
+        // Location of initial tap.
+        float tapX, tapY;
+
         // Time we started waiting for quiescence.
         nsecs_t quietTime;
 
+        // Reference points for multitouch gestures.
+        float referenceTouchX;    // reference touch X/Y coordinates in surface units
+        float referenceTouchY;
+        float referenceGestureX;  // reference gesture X/Y coordinates in pixels
+        float referenceGestureY;
+
+        // Describes how touch ids are mapped to gesture ids for freeform gestures.
+        uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
+
         // A velocity tracker for determining whether to switch active pointers during drags.
         VelocityTracker velocityTracker;
 
         void reset() {
+            firstTouchTime = LLONG_MIN;
             activeTouchId = -1;
             activeGestureId = -1;
             currentGestureMode = NEUTRAL;
-            currentGesturePointerCount = 0;
             currentGestureIdBits.clear();
             lastGestureMode = NEUTRAL;
-            lastGesturePointerCount = 0;
             lastGestureIdBits.clear();
-            touchOrigin.clear();
-            initialCentroidX = 0;
-            initialCentroidY = 0;
-            initialPointerX = 0;
-            initialPointerY = 0;
+            spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+            spotIdBits.clear();
             downTime = 0;
             velocityTracker.clear();
             resetTapTime();
@@ -1035,6 +1051,7 @@
     void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags);
     void preparePointerGestures(nsecs_t when,
             bool* outCancelPreviousGesture, bool* outFinishPreviousGesture);
+    void moveSpotsLocked();
 
     // Dispatches a motion event.
     // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
index 15effb7..ffef720 100644
--- a/services/input/PointerController.cpp
+++ b/services/input/PointerController.cpp
@@ -36,40 +36,49 @@
 // --- PointerController ---
 
 // Time to wait before starting the fade when the pointer is inactive.
-static const nsecs_t INACTIVITY_FADE_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
-static const nsecs_t INACTIVITY_FADE_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+
+// Time to wait between animation frames.
+static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
+
+// Time to spend fading out the spot completely.
+static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
 
 // Time to spend fading out the pointer completely.
-static const nsecs_t FADE_DURATION = 500 * 1000000LL; // 500 ms
-
-// Time to wait between frames.
-static const nsecs_t FADE_FRAME_INTERVAL = 1000000000LL / 60;
-
-// Amount to subtract from alpha per frame.
-static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION;
+static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
 
 
-PointerController::PointerController(const sp<Looper>& looper,
-        const sp<SpriteController>& spriteController) :
-        mLooper(looper), mSpriteController(spriteController) {
+// --- PointerController ---
+
+PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
+        const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
+        mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
     mHandler = new WeakMessageHandler(this);
 
     AutoMutex _l(mLock);
 
+    mLocked.animationPending = false;
+
     mLocked.displayWidth = -1;
     mLocked.displayHeight = -1;
     mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
 
+    mLocked.presentation = PRESENTATION_POINTER;
+    mLocked.presentationChanged = false;
+
+    mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
+
+    mLocked.pointerIsFading = true; // keep the pointer initially faded
     mLocked.pointerX = 0;
     mLocked.pointerY = 0;
+    mLocked.pointerAlpha = 0.0f;
+    mLocked.pointerSprite = mSpriteController->createSprite();
+    mLocked.pointerIconChanged = false;
+
     mLocked.buttonState = 0;
 
-    mLocked.fadeAlpha = 1;
-    mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL;
-
-    mLocked.visible = false;
-
-    mLocked.sprite = mSpriteController->createSprite();
+    loadResources();
 }
 
 PointerController::~PointerController() {
@@ -77,7 +86,13 @@
 
     AutoMutex _l(mLock);
 
-    mLocked.sprite.clear();
+    mLocked.pointerSprite.clear();
+
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        delete mLocked.spots.itemAt(i);
+    }
+    mLocked.spots.clear();
+    mLocked.recycledSprites.clear();
 }
 
 bool PointerController::getBounds(float* outMinX, float* outMinY,
@@ -130,8 +145,6 @@
 
     if (mLocked.buttonState != buttonState) {
         mLocked.buttonState = buttonState;
-        unfadeBeforeUpdateLocked();
-        updateLocked();
     }
 }
 
@@ -167,8 +180,7 @@
         } else {
             mLocked.pointerY = y;
         }
-        unfadeBeforeUpdateLocked();
-        updateLocked();
+        updatePointerLocked();
     }
 }
 
@@ -182,32 +194,105 @@
 void PointerController::fade() {
     AutoMutex _l(mLock);
 
-    startFadeLocked();
+    sendImmediateInactivityTimeoutLocked();
 }
 
 void PointerController::unfade() {
     AutoMutex _l(mLock);
 
-    if (unfadeBeforeUpdateLocked()) {
-        updateLocked();
+    // Always reset the inactivity timer.
+    resetInactivityTimeoutLocked();
+
+    // Unfade immediately if needed.
+    if (mLocked.pointerIsFading) {
+        mLocked.pointerIsFading = false;
+        mLocked.pointerAlpha = 1.0f;
+        updatePointerLocked();
     }
 }
 
-void PointerController::setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay) {
+void PointerController::setPresentation(Presentation presentation) {
     AutoMutex _l(mLock);
 
-    if (mLocked.inactivityFadeDelay != inactivityFadeDelay) {
-        mLocked.inactivityFadeDelay = inactivityFadeDelay;
-        startInactivityFadeDelayLocked();
+    if (mLocked.presentation != presentation) {
+        mLocked.presentation = presentation;
+        mLocked.presentationChanged = true;
+
+        if (presentation != PRESENTATION_SPOT) {
+            fadeOutAndReleaseAllSpotsLocked();
+        }
+
+        updatePointerLocked();
     }
 }
 
-void PointerController::updateLocked() {
-    mLocked.sprite->openTransaction();
-    mLocked.sprite->setPosition(mLocked.pointerX, mLocked.pointerY);
-    mLocked.sprite->setAlpha(mLocked.fadeAlpha);
-    mLocked.sprite->setVisible(mLocked.visible);
-    mLocked.sprite->closeTransaction();
+void PointerController::setSpots(SpotGesture spotGesture,
+        const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+#if DEBUG_POINTER_UPDATES
+    LOGD("setSpots: spotGesture=%d", spotGesture);
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+        uint32_t id = idBits.firstMarkedBit();
+        idBits.clearBit(id);
+        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+        LOGD("  spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id,
+                c.getAxisValue(AMOTION_EVENT_AXIS_X),
+                c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+                c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+    }
+#endif
+
+    AutoMutex _l(mLock);
+
+    mSpriteController->openTransaction();
+
+    // Add or move spots for fingers that are down.
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+        uint32_t id = idBits.firstMarkedBit();
+        idBits.clearBit(id);
+
+        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+        const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+                ? mResources.spotTouch : mResources.spotHover;
+        float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+        float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+        Spot* spot = getSpotLocked(id);
+        if (!spot) {
+            spot = createAndAddSpotLocked(id);
+        }
+
+        spot->updateSprite(&icon, x, y);
+    }
+
+    // Remove spots for fingers that went up.
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        if (spot->id != Spot::INVALID_ID
+                && !spotIdBits.hasBit(spot->id)) {
+            fadeOutAndReleaseSpotLocked(spot);
+        }
+    }
+
+    mSpriteController->closeTransaction();
+}
+
+void PointerController::clearSpots() {
+#if DEBUG_POINTER_UPDATES
+    LOGD("clearSpots");
+#endif
+
+    AutoMutex _l(mLock);
+
+    fadeOutAndReleaseAllSpotsLocked();
+}
+
+void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+    AutoMutex _l(mLock);
+
+    if (mLocked.inactivityTimeout != inactivityTimeout) {
+        mLocked.inactivityTimeout = inactivityTimeout;
+        resetInactivityTimeoutLocked();
+    }
 }
 
 void PointerController::setDisplaySize(int32_t width, int32_t height) {
@@ -226,7 +311,8 @@
             mLocked.pointerY = 0;
         }
 
-        updateLocked();
+        fadeOutAndReleaseAllSpotsLocked();
+        updatePointerLocked();
     }
 }
 
@@ -283,74 +369,217 @@
         mLocked.pointerY = y - 0.5f;
         mLocked.displayOrientation = orientation;
 
-        updateLocked();
+        updatePointerLocked();
     }
 }
 
-void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) {
+void PointerController::setPointerIcon(const SpriteIcon& icon) {
     AutoMutex _l(mLock);
 
-    mLocked.sprite->setBitmap(bitmap, hotSpotX, hotSpotY);
+    mLocked.pointerIcon = icon.copy();
+    mLocked.pointerIconChanged = true;
+
+    updatePointerLocked();
 }
 
 void PointerController::handleMessage(const Message& message) {
     switch (message.what) {
-    case MSG_FADE_STEP: {
-        AutoMutex _l(mLock);
-        fadeStepLocked();
+    case MSG_ANIMATE:
+        doAnimate();
+        break;
+    case MSG_INACTIVITY_TIMEOUT:
+        doInactivityTimeout();
         break;
     }
-    }
 }
 
-bool PointerController::unfadeBeforeUpdateLocked() {
-    sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
+void PointerController::doAnimate() {
+    AutoMutex _l(mLock);
 
-    if (isFadingLocked()) {
-        mLocked.visible = true;
-        mLocked.fadeAlpha = 1;
-        return true; // update required to effect the unfade
-    }
-    return false; // update not required
-}
+    bool keepAnimating = false;
+    mLocked.animationPending = false;
+    nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
 
-void PointerController::startFadeLocked() {
-    if (!isFadingLocked()) {
-        sendFadeStepMessageDelayedLocked(0);
-    }
-}
-
-void PointerController::startInactivityFadeDelayLocked() {
-    if (!isFadingLocked()) {
-        sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
-    }
-}
-
-void PointerController::fadeStepLocked() {
-    if (mLocked.visible) {
-        mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME;
-        if (mLocked.fadeAlpha < 0) {
-            mLocked.fadeAlpha = 0;
-            mLocked.visible = false;
+    // Animate pointer fade.
+    if (mLocked.pointerIsFading) {
+        mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+        if (mLocked.pointerAlpha <= 0) {
+            mLocked.pointerAlpha = 0;
         } else {
-            sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL);
+            keepAnimating = true;
         }
-        updateLocked();
+        updatePointerLocked();
+    }
+
+    // Animate spots that are fading out and being removed.
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        if (spot->id == Spot::INVALID_ID) {
+            spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+            if (spot->alpha <= 0) {
+                mLocked.spots.removeAt(i--);
+                releaseSpotLocked(spot);
+            } else {
+                spot->sprite->setAlpha(spot->alpha);
+                keepAnimating = true;
+            }
+        }
+    }
+
+    if (keepAnimating) {
+        startAnimationLocked();
     }
 }
 
-bool PointerController::isFadingLocked() {
-    return !mLocked.visible || mLocked.fadeAlpha != 1;
+void PointerController::doInactivityTimeout() {
+    AutoMutex _l(mLock);
+
+    if (!mLocked.pointerIsFading) {
+        mLocked.pointerIsFading = true;
+        startAnimationLocked();
+    }
 }
 
-nsecs_t PointerController::getInactivityFadeDelayTimeLocked() {
-    return mLocked.inactivityFadeDelay == INACTIVITY_FADE_DELAY_SHORT
-            ? INACTIVITY_FADE_DELAY_TIME_SHORT : INACTIVITY_FADE_DELAY_TIME_NORMAL;
+void PointerController::startAnimationLocked() {
+    if (!mLocked.animationPending) {
+        mLocked.animationPending = true;
+        mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+    }
 }
 
-void PointerController::sendFadeStepMessageDelayedLocked(nsecs_t delayTime) {
-    mLooper->removeMessages(mHandler, MSG_FADE_STEP);
-    mLooper->sendMessageDelayed(delayTime, mHandler, Message(MSG_FADE_STEP));
+void PointerController::resetInactivityTimeoutLocked() {
+    mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+
+    nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT
+            ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+    mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::sendImmediateInactivityTimeoutLocked() {
+    mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+    mLooper->sendMessage(mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::updatePointerLocked() {
+    mSpriteController->openTransaction();
+
+    mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+    mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+
+    if (mLocked.pointerAlpha > 0) {
+        mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+        mLocked.pointerSprite->setVisible(true);
+    } else {
+        mLocked.pointerSprite->setVisible(false);
+    }
+
+    if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
+        mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
+                ? mLocked.pointerIcon : mResources.spotAnchor);
+        mLocked.pointerIconChanged = false;
+        mLocked.presentationChanged = false;
+    }
+
+    mSpriteController->closeTransaction();
+}
+
+PointerController::Spot* PointerController::getSpotLocked(uint32_t id) {
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        if (spot->id == id) {
+            return spot;
+        }
+    }
+    return NULL;
+}
+
+PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) {
+    // Remove spots until we have fewer than MAX_SPOTS remaining.
+    while (mLocked.spots.size() >= MAX_SPOTS) {
+        Spot* spot = removeFirstFadingSpotLocked();
+        if (!spot) {
+            spot = mLocked.spots.itemAt(0);
+            mLocked.spots.removeAt(0);
+        }
+        releaseSpotLocked(spot);
+    }
+
+    // Obtain a sprite from the recycled pool.
+    sp<Sprite> sprite;
+    if (! mLocked.recycledSprites.isEmpty()) {
+        sprite = mLocked.recycledSprites.top();
+        mLocked.recycledSprites.pop();
+    } else {
+        sprite = mSpriteController->createSprite();
+    }
+
+    // Return the new spot.
+    Spot* spot = new Spot(id, sprite);
+    mLocked.spots.push(spot);
+    return spot;
+}
+
+PointerController::Spot* PointerController::removeFirstFadingSpotLocked() {
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        if (spot->id == Spot::INVALID_ID) {
+            mLocked.spots.removeAt(i);
+            return spot;
+        }
+    }
+    return NULL;
+}
+
+void PointerController::releaseSpotLocked(Spot* spot) {
+    spot->sprite->clearIcon();
+
+    if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+        mLocked.recycledSprites.push(spot->sprite);
+    }
+
+    delete spot;
+}
+
+void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
+    if (spot->id != Spot::INVALID_ID) {
+        spot->id = Spot::INVALID_ID;
+        startAnimationLocked();
+    }
+}
+
+void PointerController::fadeOutAndReleaseAllSpotsLocked() {
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        fadeOutAndReleaseSpotLocked(spot);
+    }
+}
+
+void PointerController::loadResources() {
+    mPolicy->loadPointerResources(&mResources);
+}
+
+
+// --- PointerController::Spot ---
+
+void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) {
+    sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+    sprite->setAlpha(alpha);
+    sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+    sprite->setPosition(x, y);
+
+    this->x = x;
+    this->y = y;
+
+    if (icon != lastIcon) {
+        lastIcon = icon;
+        if (icon) {
+            sprite->setIcon(*icon);
+            sprite->setVisible(true);
+        } else {
+            sprite->setVisible(false);
+        }
+    }
 }
 
 } // namespace android
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
index d467a5a..afd6371 100644
--- a/services/input/PointerController.h
+++ b/services/input/PointerController.h
@@ -30,7 +30,10 @@
 namespace android {
 
 /**
- * Interface for tracking a single (mouse) pointer.
+ * Interface for tracking a mouse / touch pad pointer and touch pad spots.
+ *
+ * The spots are sprites on screen that visually represent the positions of
+ * fingers
  *
  * The pointer controller is responsible for providing synchronization and for tracking
  * display orientation changes if needed.
@@ -64,8 +67,95 @@
     /* Fades the pointer out now. */
     virtual void fade() = 0;
 
-    /* Makes the pointer visible if it has faded out. */
+    /* Makes the pointer visible if it has faded out.
+     * The pointer never unfades itself automatically.  This method must be called
+     * by the client whenever the pointer is moved or a button is pressed and it
+     * wants to ensure that the pointer becomes visible again. */
     virtual void unfade() = 0;
+
+    enum Presentation {
+        // Show the mouse pointer.
+        PRESENTATION_POINTER,
+        // Show spots and a spot anchor in place of the mouse pointer.
+        PRESENTATION_SPOT,
+    };
+
+    /* Sets the mode of the pointer controller. */
+    virtual void setPresentation(Presentation presentation) = 0;
+
+    // Describes the current gesture.
+    enum SpotGesture {
+        // No gesture.
+        // Do not display any spots.
+        SPOT_GESTURE_NEUTRAL,
+        // Tap at current location.
+        // Briefly display one spot at the tapped location.
+        SPOT_GESTURE_TAP,
+        // Button pressed but no finger is down.
+        // Display spot at pressed location.
+        SPOT_GESTURE_BUTTON_CLICK,
+        // Button pressed and a finger is down.
+        // Display spot at pressed location.
+        SPOT_GESTURE_BUTTON_DRAG,
+        // One finger down and hovering.
+        // Display spot at the hovered location.
+        SPOT_GESTURE_HOVER,
+        // Two fingers down but not sure in which direction they are moving so we consider
+        // it a press at the pointer location.
+        // Display two spots near the pointer location.
+        SPOT_GESTURE_PRESS,
+        // Two fingers down and moving in same direction.
+        // Display two spots near the pointer location.
+        SPOT_GESTURE_SWIPE,
+        // Two or more fingers down and moving in arbitrary directions.
+        // Display two or more spots near the pointer location, one for each finger.
+        SPOT_GESTURE_FREEFORM,
+    };
+
+    /* Sets the spots for the current gesture.
+     * The spots are not subject to the inactivity timeout like the pointer
+     * itself it since they are expected to remain visible for so long as
+     * the fingers are on the touch pad.
+     *
+     * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant.
+     * For spotCoords, pressure != 0 indicates that the spot's location is being
+     * pressed (not hovering).
+     */
+    virtual void setSpots(SpotGesture spotGesture,
+            const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+            BitSet32 spotIdBits) = 0;
+
+    /* Removes all spots. */
+    virtual void clearSpots() = 0;
+};
+
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+    SpriteIcon spotHover;
+    SpriteIcon spotTouch;
+    SpriteIcon spotAnchor;
+};
+
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI.  This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+    PointerControllerPolicyInterface() { }
+    virtual ~PointerControllerPolicyInterface() { }
+
+public:
+    virtual void loadPointerResources(PointerResources* outResources) = 0;
 };
 
 
@@ -79,12 +169,13 @@
     virtual ~PointerController();
 
 public:
-    enum InactivityFadeDelay {
-        INACTIVITY_FADE_DELAY_NORMAL = 0,
-        INACTIVITY_FADE_DELAY_SHORT = 1,
+    enum InactivityTimeout {
+        INACTIVITY_TIMEOUT_NORMAL = 0,
+        INACTIVITY_TIMEOUT_SHORT = 1,
     };
 
-    PointerController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+    PointerController(const sp<PointerControllerPolicyInterface>& policy,
+            const sp<Looper>& looper, const sp<SpriteController>& spriteController);
 
     virtual bool getBounds(float* outMinX, float* outMinY,
             float* outMaxX, float* outMaxY) const;
@@ -96,51 +187,101 @@
     virtual void fade();
     virtual void unfade();
 
+    virtual void setPresentation(Presentation presentation);
+    virtual void setSpots(SpotGesture spotGesture,
+            const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
+    virtual void clearSpots();
+
     void setDisplaySize(int32_t width, int32_t height);
     void setDisplayOrientation(int32_t orientation);
-    void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
-    void setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay);
+    void setPointerIcon(const SpriteIcon& icon);
+    void setInactivityTimeout(InactivityTimeout inactivityTimeout);
 
 private:
+    static const size_t MAX_RECYCLED_SPRITES = 12;
+    static const size_t MAX_SPOTS = 12;
+
     enum {
-        MSG_FADE_STEP = 0,
+        MSG_ANIMATE,
+        MSG_INACTIVITY_TIMEOUT,
+    };
+
+    struct Spot {
+        static const uint32_t INVALID_ID = 0xffffffff;
+
+        uint32_t id;
+        sp<Sprite> sprite;
+        float alpha;
+        float scale;
+        float x, y;
+
+        inline Spot(uint32_t id, const sp<Sprite>& sprite)
+                : id(id), sprite(sprite), alpha(1.0f), scale(1.0f),
+                  x(0.0f), y(0.0f), lastIcon(NULL) { }
+
+        void updateSprite(const SpriteIcon* icon, float x, float y);
+
+    private:
+        const SpriteIcon* lastIcon;
     };
 
     mutable Mutex mLock;
 
+    sp<PointerControllerPolicyInterface> mPolicy;
     sp<Looper> mLooper;
     sp<SpriteController> mSpriteController;
     sp<WeakMessageHandler> mHandler;
 
+    PointerResources mResources;
+
     struct Locked {
+        bool animationPending;
+        nsecs_t animationTime;
+
         int32_t displayWidth;
         int32_t displayHeight;
         int32_t displayOrientation;
 
+        InactivityTimeout inactivityTimeout;
+
+        Presentation presentation;
+        bool presentationChanged;
+
+        bool pointerIsFading;
         float pointerX;
         float pointerY;
+        float pointerAlpha;
+        sp<Sprite> pointerSprite;
+        SpriteIcon pointerIcon;
+        bool pointerIconChanged;
+
         uint32_t buttonState;
 
-        float fadeAlpha;
-        InactivityFadeDelay inactivityFadeDelay;
-
-        bool visible;
-
-        sp<Sprite> sprite;
+        Vector<Spot*> spots;
+        Vector<sp<Sprite> > recycledSprites;
     } mLocked;
 
     bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
     void setPositionLocked(float x, float y);
-    void updateLocked();
 
     void handleMessage(const Message& message);
-    bool unfadeBeforeUpdateLocked();
-    void startFadeLocked();
-    void startInactivityFadeDelayLocked();
-    void fadeStepLocked();
-    bool isFadingLocked();
-    nsecs_t getInactivityFadeDelayTimeLocked();
-    void sendFadeStepMessageDelayedLocked(nsecs_t delayTime);
+    void doAnimate();
+    void doInactivityTimeout();
+
+    void startAnimationLocked();
+
+    void resetInactivityTimeoutLocked();
+    void sendImmediateInactivityTimeoutLocked();
+    void updatePointerLocked();
+
+    Spot* getSpotLocked(uint32_t id);
+    Spot* createAndAddSpotLocked(uint32_t id);
+    Spot* removeFirstFadingSpotLocked();
+    void releaseSpotLocked(Spot* spot);
+    void fadeOutAndReleaseSpotLocked(Spot* spot);
+    void fadeOutAndReleaseAllSpotsLocked();
+
+    void loadResources();
 };
 
 } // namespace android
diff --git a/services/input/SpotController.cpp b/services/input/SpotController.cpp
deleted file mode 100644
index dffad81..0000000
--- a/services/input/SpotController.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "SpotController"
-
-//#define LOG_NDEBUG 0
-
-// Log debug messages about spot updates
-#define DEBUG_SPOT_UPDATES 0
-
-#include "SpotController.h"
-
-#include <cutils/log.h>
-
-namespace android {
-
-// --- SpotController ---
-
-SpotController::SpotController(const sp<Looper>& looper,
-        const sp<SpriteController>& spriteController) :
-        mLooper(looper), mSpriteController(spriteController) {
-    mHandler = new WeakMessageHandler(this);
-}
-
-SpotController::~SpotController() {
-    mLooper->removeMessages(mHandler);
-}
-
-void SpotController:: handleMessage(const Message& message) {
-}
-
-} // namespace android
diff --git a/services/input/SpotController.h b/services/input/SpotController.h
deleted file mode 100644
index 1d091d7..0000000
--- a/services/input/SpotController.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 _UI_SPOT_CONTROLLER_H
-#define _UI_SPOT_CONTROLLER_H
-
-#include "SpriteController.h"
-
-#include <utils/RefBase.h>
-#include <utils/Looper.h>
-
-#include <SkBitmap.h>
-
-namespace android {
-
-/*
- * Interface for displaying spots on screen that visually represent the positions
- * of fingers on a touch pad.
- *
- * The spot controller is responsible for providing synchronization and for tracking
- * display orientation changes if needed.
- */
-class SpotControllerInterface : public virtual RefBase {
-protected:
-    SpotControllerInterface() { }
-    virtual ~SpotControllerInterface() { }
-
-public:
-
-};
-
-
-/*
- * Sprite-based spot controller implementation.
- */
-class SpotController : public SpotControllerInterface, public MessageHandler {
-protected:
-    virtual ~SpotController();
-
-public:
-    SpotController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
-
-private:
-    mutable Mutex mLock;
-
-    sp<Looper> mLooper;
-    sp<SpriteController> mSpriteController;
-    sp<WeakMessageHandler> mHandler;
-
-    struct Locked {
-    } mLocked;
-
-    void handleMessage(const Message& message);
-};
-
-} // namespace android
-
-#endif // _UI_SPOT_CONTROLLER_H
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
index c6d4390f..2fd1f0a 100644
--- a/services/input/SpriteController.cpp
+++ b/services/input/SpriteController.cpp
@@ -36,6 +36,9 @@
 SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
         mLooper(looper), mOverlayLayer(overlayLayer) {
     mHandler = new WeakMessageHandler(this);
+
+    mLocked.transactionNestingCount = 0;
+    mLocked.deferredSpriteUpdate = false;
 }
 
 SpriteController::~SpriteController() {
@@ -51,17 +54,40 @@
     return new SpriteImpl(this);
 }
 
-void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
-    bool wasEmpty = mInvalidatedSprites.isEmpty();
-    mInvalidatedSprites.push(sprite);
-    if (wasEmpty) {
+void SpriteController::openTransaction() {
+    AutoMutex _l(mLock);
+
+    mLocked.transactionNestingCount += 1;
+}
+
+void SpriteController::closeTransaction() {
+    AutoMutex _l(mLock);
+
+    LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0,
+            "Sprite closeTransaction() called but there is no open sprite transaction");
+
+    mLocked.transactionNestingCount -= 1;
+    if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
+        mLocked.deferredSpriteUpdate = false;
         mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
     }
 }
 
+void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
+    bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
+    mLocked.invalidatedSprites.push(sprite);
+    if (wasEmpty) {
+        if (mLocked.transactionNestingCount != 0) {
+            mLocked.deferredSpriteUpdate = true;
+        } else {
+            mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+        }
+    }
+}
+
 void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
-    bool wasEmpty = mDisposedSurfaces.isEmpty();
-    mDisposedSurfaces.push(surfaceControl);
+    bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
+    mLocked.disposedSurfaces.push(surfaceControl);
     if (wasEmpty) {
         mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
     }
@@ -89,14 +115,14 @@
     { // acquire lock
         AutoMutex _l(mLock);
 
-        numSprites = mInvalidatedSprites.size();
+        numSprites = mLocked.invalidatedSprites.size();
         for (size_t i = 0; i < numSprites; i++) {
-            const sp<SpriteImpl>& sprite = mInvalidatedSprites.itemAt(i);
+            const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
 
             updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
             sprite->resetDirtyLocked();
         }
-        mInvalidatedSprites.clear();
+        mLocked.invalidatedSprites.clear();
     } // release lock
 
     // Create missing surfaces.
@@ -105,8 +131,8 @@
         SpriteUpdate& update = updates.editItemAt(i);
 
         if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
-            update.state.surfaceWidth = update.state.bitmap.width();
-            update.state.surfaceHeight = update.state.bitmap.height();
+            update.state.surfaceWidth = update.state.icon.bitmap.width();
+            update.state.surfaceHeight = update.state.icon.bitmap.height();
             update.state.surfaceDrawn = false;
             update.state.surfaceVisible = false;
             update.state.surfaceControl = obtainSurface(
@@ -123,8 +149,8 @@
         SpriteUpdate& update = updates.editItemAt(i);
 
         if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
-            int32_t desiredWidth = update.state.bitmap.width();
-            int32_t desiredHeight = update.state.bitmap.height();
+            int32_t desiredWidth = update.state.icon.bitmap.width();
+            int32_t desiredHeight = update.state.icon.bitmap.height();
             if (update.state.surfaceWidth < desiredWidth
                     || update.state.surfaceHeight < desiredHeight) {
                 if (!haveGlobalTransaction) {
@@ -187,16 +213,16 @@
 
                 SkPaint paint;
                 paint.setXfermodeMode(SkXfermode::kSrc_Mode);
-                surfaceCanvas.drawBitmap(update.state.bitmap, 0, 0, &paint);
+                surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
 
-                if (surfaceInfo.w > uint32_t(update.state.bitmap.width())) {
+                if (surfaceInfo.w > uint32_t(update.state.icon.bitmap.width())) {
                     paint.setColor(0); // transparent fill color
-                    surfaceCanvas.drawRectCoords(update.state.bitmap.width(), 0,
-                            surfaceInfo.w, update.state.bitmap.height(), paint);
+                    surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
+                            surfaceInfo.w, update.state.icon.bitmap.height(), paint);
                 }
-                if (surfaceInfo.h > uint32_t(update.state.bitmap.height())) {
+                if (surfaceInfo.h > uint32_t(update.state.icon.bitmap.height())) {
                     paint.setColor(0); // transparent fill color
-                    surfaceCanvas.drawRectCoords(0, update.state.bitmap.height(),
+                    surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
                             surfaceInfo.w, surfaceInfo.h, paint);
                 }
 
@@ -246,8 +272,8 @@
                     && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
                             | DIRTY_HOTSPOT)))) {
                 status = update.state.surfaceControl->setPosition(
-                        update.state.positionX - update.state.hotSpotX,
-                        update.state.positionY - update.state.hotSpotY);
+                        update.state.positionX - update.state.icon.hotSpotX,
+                        update.state.positionY - update.state.icon.hotSpotY);
                 if (status) {
                     LOGE("Error %d setting sprite surface position.", status);
                 }
@@ -329,8 +355,10 @@
     // Collect disposed surfaces.
     Vector<sp<SurfaceControl> > disposedSurfaces;
     { // acquire lock
-        disposedSurfaces = mDisposedSurfaces;
-        mDisposedSurfaces.clear();
+        AutoMutex _l(mLock);
+
+        disposedSurfaces = mLocked.disposedSurfaces;
+        mLocked.disposedSurfaces.clear();
     } // release lock
 
     // Release the last reference to each surface outside of the lock.
@@ -349,7 +377,8 @@
 
     sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
             getpid(), String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888);
-    if (surfaceControl == NULL) {
+    if (surfaceControl == NULL || !surfaceControl->isValid()
+            || !surfaceControl->getSurface()->isValid()) {
         LOGE("Error creating sprite surface.");
         return NULL;
     }
@@ -360,7 +389,7 @@
 // --- SpriteController::SpriteImpl ---
 
 SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
-        mController(controller), mTransactionNestingCount(0) {
+        mController(controller) {
 }
 
 SpriteController::SpriteImpl::~SpriteImpl() {
@@ -368,27 +397,33 @@
 
     // Let the controller take care of deleting the last reference to sprite
     // surfaces so that we do not block the caller on an IPC here.
-    if (mState.surfaceControl != NULL) {
-        mController->disposeSurfaceLocked(mState.surfaceControl);
-        mState.surfaceControl.clear();
+    if (mLocked.state.surfaceControl != NULL) {
+        mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+        mLocked.state.surfaceControl.clear();
     }
 }
 
-void SpriteController::SpriteImpl::setBitmap(const SkBitmap* bitmap,
-        float hotSpotX, float hotSpotY) {
+void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
     AutoMutex _l(mController->mLock);
 
-    if (bitmap) {
-        bitmap->copyTo(&mState.bitmap, SkBitmap::kARGB_8888_Config);
-    } else {
-        mState.bitmap.reset();
-    }
+    uint32_t dirty;
+    if (icon.isValid()) {
+        icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config);
 
-    uint32_t dirty = DIRTY_BITMAP;
-    if (mState.hotSpotX != hotSpotX || mState.hotSpotY != hotSpotY) {
-        mState.hotSpotX = hotSpotX;
-        mState.hotSpotY = hotSpotY;
-        dirty |= DIRTY_HOTSPOT;
+        if (!mLocked.state.icon.isValid()
+                || mLocked.state.icon.hotSpotX != icon.hotSpotX
+                || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+            mLocked.state.icon.hotSpotX = icon.hotSpotX;
+            mLocked.state.icon.hotSpotY = icon.hotSpotY;
+            dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+        } else {
+            dirty = DIRTY_BITMAP;
+        }
+    } else if (mLocked.state.icon.isValid()) {
+        mLocked.state.icon.bitmap.reset();
+        dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+    } else {
+        return; // setting to invalid icon and already invalid so nothing to do
     }
 
     invalidateLocked(dirty);
@@ -397,8 +432,8 @@
 void SpriteController::SpriteImpl::setVisible(bool visible) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.visible != visible) {
-        mState.visible = visible;
+    if (mLocked.state.visible != visible) {
+        mLocked.state.visible = visible;
         invalidateLocked(DIRTY_VISIBILITY);
     }
 }
@@ -406,9 +441,9 @@
 void SpriteController::SpriteImpl::setPosition(float x, float y) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.positionX != x || mState.positionY != y) {
-        mState.positionX = x;
-        mState.positionY = y;
+    if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
+        mLocked.state.positionX = x;
+        mLocked.state.positionY = y;
         invalidateLocked(DIRTY_POSITION);
     }
 }
@@ -416,8 +451,8 @@
 void SpriteController::SpriteImpl::setLayer(int32_t layer) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.layer != layer) {
-        mState.layer = layer;
+    if (mLocked.state.layer != layer) {
+        mLocked.state.layer = layer;
         invalidateLocked(DIRTY_LAYER);
     }
 }
@@ -425,8 +460,8 @@
 void SpriteController::SpriteImpl::setAlpha(float alpha) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.alpha != alpha) {
-        mState.alpha = alpha;
+    if (mLocked.state.alpha != alpha) {
+        mLocked.state.alpha = alpha;
         invalidateLocked(DIRTY_ALPHA);
     }
 }
@@ -435,37 +470,18 @@
         const SpriteTransformationMatrix& matrix) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.transformationMatrix != matrix) {
-        mState.transformationMatrix = matrix;
+    if (mLocked.state.transformationMatrix != matrix) {
+        mLocked.state.transformationMatrix = matrix;
         invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
     }
 }
 
-void SpriteController::SpriteImpl::openTransaction() {
-    AutoMutex _l(mController->mLock);
-
-    mTransactionNestingCount += 1;
-}
-
-void SpriteController::SpriteImpl::closeTransaction() {
-    AutoMutex _l(mController->mLock);
-
-    LOG_ALWAYS_FATAL_IF(mTransactionNestingCount == 0,
-            "Sprite closeTransaction() called but there is no open sprite transaction");
-
-    mTransactionNestingCount -= 1;
-    if (mTransactionNestingCount == 0 && mState.dirty) {
-        mController->invalidateSpriteLocked(this);
-    }
-}
-
 void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
-    if (mTransactionNestingCount > 0) {
-        bool wasDirty = mState.dirty;
-        mState.dirty |= dirty;
-        if (!wasDirty) {
-            mController->invalidateSpriteLocked(this);
-        }
+    bool wasDirty = mLocked.state.dirty;
+    mLocked.state.dirty |= dirty;
+
+    if (!wasDirty) {
+        mController->invalidateSpriteLocked(this);
     }
 }
 
diff --git a/services/input/SpriteController.h b/services/input/SpriteController.h
index 27afb5e..50ae8a5 100644
--- a/services/input/SpriteController.h
+++ b/services/input/SpriteController.h
@@ -33,6 +33,8 @@
  */
 struct SpriteTransformationMatrix {
     inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+    inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) :
+            dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { }
 
     float dsdx;
     float dtdx;
@@ -52,6 +54,35 @@
 };
 
 /*
+ * Icon that a sprite displays, including its hotspot.
+ */
+struct SpriteIcon {
+    inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { }
+    inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) :
+            bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
+
+    SkBitmap bitmap;
+    float hotSpotX;
+    float hotSpotY;
+
+    inline SpriteIcon copy() const {
+        SkBitmap bitmapCopy;
+        bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config);
+        return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
+    }
+
+    inline void reset() {
+        bitmap.reset();
+        hotSpotX = 0;
+        hotSpotY = 0;
+    }
+
+    inline bool isValid() const {
+        return !bitmap.isNull() && !bitmap.empty();
+    }
+};
+
+/*
  * A sprite is a simple graphical object that is displayed on-screen above other layers.
  * The basic sprite class is an interface.
  * The implementation is provided by the sprite controller.
@@ -62,9 +93,21 @@
     virtual ~Sprite() { }
 
 public:
+    enum {
+        // The base layer for pointer sprites.
+        BASE_LAYER_POINTER = 0, // reserve space for 1 pointer
+
+        // The base layer for spot sprites.
+        BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots
+    };
+
     /* Sets the bitmap that is drawn by the sprite.
      * The sprite retains a copy of the bitmap for subsequent rendering. */
-    virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) = 0;
+    virtual void setIcon(const SpriteIcon& icon) = 0;
+
+    inline void clearIcon() {
+        setIcon(SpriteIcon());
+    }
 
     /* Sets whether the sprite is visible. */
     virtual void setVisible(bool visible) = 0;
@@ -81,14 +124,6 @@
 
     /* Sets the sprite transformation matrix. */
     virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
-
-    /* Opens or closes a transaction to perform a batch of sprite updates as part of
-     * a single operation such as setPosition and setAlpha.  It is not necessary to
-     * open a transaction when updating a single property.
-     * Calls to openTransaction() nest and must be matched by an equal number
-     * of calls to closeTransaction(). */
-    virtual void openTransaction() = 0;
-    virtual void closeTransaction() = 0;
 };
 
 /*
@@ -112,6 +147,14 @@
     /* Creates a new sprite, initially invisible. */
     sp<Sprite> createSprite();
 
+    /* Opens or closes a transaction to perform a batch of sprite updates as part of
+     * a single operation such as setPosition and setAlpha.  It is not necessary to
+     * open a transaction when updating a single property.
+     * Calls to openTransaction() nest and must be matched by an equal number
+     * of calls to closeTransaction(). */
+    void openTransaction();
+    void closeTransaction();
+
 private:
     enum {
         MSG_UPDATE_SPRITES,
@@ -135,16 +178,14 @@
      * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
     struct SpriteState {
         inline SpriteState() :
-                dirty(0), hotSpotX(0), hotSpotY(0), visible(false),
+                dirty(0), visible(false),
                 positionX(0), positionY(0), layer(0), alpha(1.0f),
                 surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
         }
 
         uint32_t dirty;
 
-        SkBitmap bitmap;
-        float hotSpotX;
-        float hotSpotY;
+        SpriteIcon icon;
         bool visible;
         float positionX;
         float positionY;
@@ -159,7 +200,7 @@
         bool surfaceVisible;
 
         inline bool wantSurfaceVisible() const {
-            return visible && alpha > 0.0f && !bitmap.isNull() && !bitmap.empty();
+            return visible && alpha > 0.0f && icon.isValid();
         }
     };
 
@@ -177,37 +218,36 @@
     public:
         SpriteImpl(const sp<SpriteController> controller);
 
-        virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
+        virtual void setIcon(const SpriteIcon& icon);
         virtual void setVisible(bool visible);
         virtual void setPosition(float x, float y);
         virtual void setLayer(int32_t layer);
         virtual void setAlpha(float alpha);
         virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
-        virtual void openTransaction();
-        virtual void closeTransaction();
 
         inline const SpriteState& getStateLocked() const {
-            return mState;
+            return mLocked.state;
         }
 
         inline void resetDirtyLocked() {
-            mState.dirty = 0;
+            mLocked.state.dirty = 0;
         }
 
         inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
                 int32_t width, int32_t height, bool drawn, bool visible) {
-            mState.surfaceControl = surfaceControl;
-            mState.surfaceWidth = width;
-            mState.surfaceHeight = height;
-            mState.surfaceDrawn = drawn;
-            mState.surfaceVisible = visible;
+            mLocked.state.surfaceControl = surfaceControl;
+            mLocked.state.surfaceWidth = width;
+            mLocked.state.surfaceHeight = height;
+            mLocked.state.surfaceDrawn = drawn;
+            mLocked.state.surfaceVisible = visible;
         }
 
     private:
         sp<SpriteController> mController;
 
-        SpriteState mState; // guarded by mController->mLock
-        uint32_t mTransactionNestingCount; // guarded by mController->mLock
+        struct Locked {
+            SpriteState state;
+        } mLocked; // guarded by mController->mLock
 
         void invalidateLocked(uint32_t dirty);
     };
@@ -232,8 +272,12 @@
 
     sp<SurfaceComposerClient> mSurfaceComposerClient;
 
-    Vector<sp<SpriteImpl> > mInvalidatedSprites; // guarded by mLock
-    Vector<sp<SurfaceControl> > mDisposedSurfaces; // guarded by mLock
+    struct Locked {
+        Vector<sp<SpriteImpl> > invalidatedSprites;
+        Vector<sp<SurfaceControl> > disposedSurfaces;
+        uint32_t transactionNestingCount;
+        bool deferredSpriteUpdate;
+    } mLocked; // guarded by mLock
 
     void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
     void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index ba8ca9c..54bb9d7 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -97,6 +97,16 @@
 
     virtual void unfade() {
     }
+
+    virtual void setPresentation(Presentation presentation) {
+    }
+
+    virtual void setSpots(SpotGesture spotGesture,
+            const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+    }
+
+    virtual void clearSpots() {
+    }
 };
 
 
@@ -192,10 +202,6 @@
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
         return mPointerControllers.valueFor(deviceId);
     }
-
-    virtual sp<SpotControllerInterface> obtainSpotController(int32_t device) {
-        return NULL;
-    }
 };
 
 
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 5853696..9ff5233 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -49,8 +49,9 @@
 import android.util.Log;
 
 import com.android.internal.telephony.Phone;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -122,7 +123,7 @@
     // resampled each time we turn on tethering - used as cache for settings/config-val
     private boolean mDunRequired;  // configuration info - must use DUN apn on 3g
 
-    private HierarchicalStateMachine mTetherMasterSM;
+    private StateMachine mTetherMasterSM;
 
     private Notification mTetheredNotification;
 
@@ -668,7 +669,7 @@
     }
 
 
-    class TetherInterfaceSM extends HierarchicalStateMachine {
+    class TetherInterfaceSM extends StateMachine {
         // notification from the master SM that it's not in tether mode
         static final int CMD_TETHER_MODE_DEAD            =  1;
         // request from the user that it wants to tether
@@ -694,13 +695,13 @@
         // the upstream connection has changed
         static final int CMD_TETHER_CONNECTION_CHANGED   = 12;
 
-        private HierarchicalState mDefaultState;
+        private State mDefaultState;
 
-        private HierarchicalState mInitialState;
-        private HierarchicalState mStartingState;
-        private HierarchicalState mTetheredState;
+        private State mInitialState;
+        private State mStartingState;
+        private State mTetheredState;
 
-        private HierarchicalState mUnavailableState;
+        private State mUnavailableState;
 
         private boolean mAvailable;
         private boolean mTethered;
@@ -732,7 +733,7 @@
         public String toString() {
             String res = new String();
             res += mIfaceName + " - ";
-            HierarchicalState current = getCurrentState();
+            IState current = getCurrentState();
             if (current == mInitialState) res += "InitialState";
             if (current == mStartingState) res += "StartingState";
             if (current == mTetheredState) res += "TetheredState";
@@ -782,7 +783,7 @@
             return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
         }
 
-        class InitialState extends HierarchicalState {
+        class InitialState extends State {
             @Override
             public void enter() {
                 setAvailable(true);
@@ -812,7 +813,7 @@
             }
         }
 
-        class StartingState extends HierarchicalState {
+        class StartingState extends State {
             @Override
             public void enter() {
                 setAvailable(false);
@@ -870,7 +871,7 @@
             }
         }
 
-        class TetheredState extends HierarchicalState {
+        class TetheredState extends State {
             @Override
             public void enter() {
                 IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
@@ -1034,7 +1035,7 @@
             }
         }
 
-        class UnavailableState extends HierarchicalState {
+        class UnavailableState extends State {
             @Override
             public void enter() {
                 setAvailable(false);
@@ -1064,7 +1065,7 @@
 
     }
 
-    class TetherMasterSM extends HierarchicalStateMachine {
+    class TetherMasterSM extends StateMachine {
         // an interface SM has requested Tethering
         static final int CMD_TETHER_MODE_REQUESTED   = 1;
         // an interface SM has unrequested Tethering
@@ -1082,14 +1083,14 @@
         // We do not flush the old ones.
         private int mSequenceNumber;
 
-        private HierarchicalState mInitialState;
-        private HierarchicalState mTetherModeAliveState;
+        private State mInitialState;
+        private State mTetherModeAliveState;
 
-        private HierarchicalState mSetIpForwardingEnabledErrorState;
-        private HierarchicalState mSetIpForwardingDisabledErrorState;
-        private HierarchicalState mStartTetheringErrorState;
-        private HierarchicalState mStopTetheringErrorState;
-        private HierarchicalState mSetDnsForwardersErrorState;
+        private State mSetIpForwardingEnabledErrorState;
+        private State mSetIpForwardingDisabledErrorState;
+        private State mStartTetheringErrorState;
+        private State mStopTetheringErrorState;
+        private State mSetDnsForwardersErrorState;
 
         private ArrayList mNotifyList;
 
@@ -1125,7 +1126,7 @@
             setInitialState(mInitialState);
         }
 
-        class TetherMasterUtilState extends HierarchicalState {
+        class TetherMasterUtilState extends State {
             protected final static boolean TRY_TO_SETUP_MOBILE_CONNECTION = true;
             protected final static boolean WAIT_FOR_NETWORK_TO_SETTLE     = false;
 
@@ -1440,7 +1441,7 @@
             }
         }
 
-        class ErrorState extends HierarchicalState {
+        class ErrorState extends State {
             int mErrorNotification;
             @Override
             public boolean processMessage(Message message) {
diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java
index da3ebaf..d10aa97 100644
--- a/services/java/com/android/server/pm/Installer.java
+++ b/services/java/com/android/server/pm/Installer.java
@@ -225,10 +225,12 @@
         return execute(builder.toString());
     }
 
-    public int remove(String name) {
+    public int remove(String name, int userId) {
         StringBuilder builder = new StringBuilder("remove");
         builder.append(' ');
         builder.append(name);
+        builder.append(' ');
+        builder.append(userId);
         return execute(builder.toString());
     }
 
@@ -248,10 +250,30 @@
         return execute(builder.toString());
     }
 
-    public int clearUserData(String name) {
+    public int createUserData(String name, int uid, int userId) {
+        StringBuilder builder = new StringBuilder("mkuserdata");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(uid);
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
+    public int removeUserDataDirs(int userId) {
+        StringBuilder builder = new StringBuilder("rmuser");
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
+    public int clearUserData(String name, int userId) {
         StringBuilder builder = new StringBuilder("rmuserdata");
         builder.append(' ');
         builder.append(name);
+        builder.append(' ');
+        builder.append(userId);
         return execute(builder.toString());
     }
 
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index a9d49b4..6e1093f 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -65,6 +65,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
+import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -208,6 +209,9 @@
     // This is where all application persistent data goes.
     final File mAppDataDir;
 
+    // This is where all application persistent data goes for secondary users.
+    final File mUserAppDataDir;
+
     // This is the object monitoring the framework dir.
     final FileObserver mFrameworkInstallObserver;
 
@@ -359,6 +363,8 @@
     // Delay time in millisecs
     static final int BROADCAST_DELAY = 10 * 1000;
 
+    final UserManager mUserManager;
+
     final private DefaultContainerConnection mDefContainerConn =
             new DefaultContainerConnection();
     class DefaultContainerConnection implements ServiceConnection {
@@ -797,8 +803,11 @@
 
             File dataDir = Environment.getDataDirectory();
             mAppDataDir = new File(dataDir, "data");
+            mUserAppDataDir = new File(dataDir, "user");
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
 
+            mUserManager = new UserManager(mInstaller, mUserAppDataDir);
+
             if (mInstaller == null) {
                 // Make sure these dirs exist, when we are running in
                 // the simulator.
@@ -806,6 +815,7 @@
                 File miscDir = new File(dataDir, "misc");
                 miscDir.mkdirs();
                 mAppDataDir.mkdirs();
+                mUserAppDataDir.mkdirs();
                 mDrmAppPrivateInstallDir.mkdirs();
             }
 
@@ -974,7 +984,8 @@
                             + " no longer exists; wiping its data";
                     reportSettingsProblem(Log.WARN, msg);
                     if (mInstaller != null) {
-                        mInstaller.remove(ps.name);
+                        mInstaller.remove(ps.name, 0);
+                        mUserManager.removePackageForAllUsers(ps.name);
                     }
                 }
             }
@@ -1059,10 +1070,12 @@
     void cleanupInstallFailedPackage(PackageSetting ps) {
         Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name);
         if (mInstaller != null) {
-            int retCode = mInstaller.remove(ps.name);
+            int retCode = mInstaller.remove(ps.name, 0);
             if (retCode < 0) {
                 Slog.w(TAG, "Couldn't remove app data directory for package: "
                            + ps.name + ", retcode=" + retCode);
+            } else {
+                mUserManager.removePackageForAllUsers(ps.name);
             }
         } else {
             //for emulator
@@ -1510,7 +1523,8 @@
                 ps.pkg.applicationInfo.flags = ps.pkgFlags;
                 ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
                 ps.pkg.applicationInfo.sourceDir = ps.codePathString;
-                ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath();
+                ps.pkg.applicationInfo.dataDir =
+                        getDataPathForPackage(ps.pkg.packageName, 0).getPath();
                 ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
                 ps.pkg.mSetEnabled = ps.enabled;
                 ps.pkg.mSetStopped = ps.stopped;
@@ -2836,11 +2850,15 @@
         return true;
     }
 
-    private File getDataPathForPackage(PackageParser.Package pkg) {
-        final File dataPath = new File(mAppDataDir, pkg.packageName);
-        return dataPath;
+    File getDataPathForUser(int userId) {
+        return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId);
     }
-    
+
+    private File getDataPathForPackage(String packageName, int userId) {
+        return new File(mUserAppDataDir.getAbsolutePath() + File.separator
+                    + userId + File.separator + packageName);
+    }
+
     private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
             int parseFlags, int scanMode, long currentTime) {
         File scanFile = new File(pkg.mScanPath);
@@ -3162,7 +3180,7 @@
             pkg.applicationInfo.dataDir = dataPath.getPath();
         } else {
             // This is a normal package, need to make its data directory.
-            dataPath = getDataPathForPackage(pkg);
+            dataPath = getDataPathForPackage(pkg.packageName, 0);
             
             boolean uidError = false;
             
@@ -3178,8 +3196,11 @@
                         // If this is a system app, we can at least delete its
                         // current data so the application will still work.
                         if (mInstaller != null) {
-                            int ret = mInstaller.remove(pkgName);
+                            int ret = mInstaller.remove(pkgName, 0);
                             if (ret >= 0) {
+                                // TODO: Kill the processes first
+                                // Remove the data directories for all users
+                                mUserManager.removePackageForAllUsers(pkgName);
                                 // Old data gone!
                                 String msg = "System package " + pkg.packageName
                                         + " has changed from uid: "
@@ -3199,6 +3220,9 @@
                                     mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                                     return null;
                                 }
+                                // Create data directories for all users
+                                mUserManager.installPackageForAllUsers(pkgName,
+                                        pkg.applicationInfo.uid);
                             }
                         }
                         if (!recovered) {
@@ -3235,11 +3259,13 @@
                 if (mInstaller != null) {
                     int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid,
                             pkg.applicationInfo.uid);
-                    if(ret < 0) {
+                    if (ret < 0) {
                         // Error from installer
                         mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                         return null;
                     }
+                    // Create data directories for all users
+                    mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid);
                 } else {
                     dataPath.mkdirs();
                     if (dataPath.exists()) {
@@ -5703,7 +5729,7 @@
         // Remember this for later, in case we need to rollback this install
         String pkgName = pkg.packageName;
 
-        boolean dataDirExists = getDataPathForPackage(pkg).exists();
+        boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
         res.name = pkgName;
         synchronized(mPackages) {
             if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -6390,11 +6416,14 @@
         }
         if ((flags&PackageManager.DONT_DELETE_DATA) == 0) {
             if (mInstaller != null) {
-                int retCode = mInstaller.remove(packageName);
+                int retCode = mInstaller.remove(packageName, 0);
                 if (retCode < 0) {
                     Slog.w(TAG, "Couldn't remove app data or cache directory for package: "
                                + packageName + ", retcode=" + retCode);
                     // we don't consider this to be a failure of the core package deletion
+                } else {
+                    // TODO: Kill the processes first
+                    mUserManager.removePackageForAllUsers(packageName);
                 }
             } else {
                 // for simulator
@@ -6654,7 +6683,7 @@
             }
         }
         if (mInstaller != null) {
-            int retCode = mInstaller.clearUserData(packageName);
+            int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId
             if (retCode < 0) {
                 Slog.w(TAG, "Couldn't remove cache files for package: "
                         + packageName);
@@ -8015,4 +8044,17 @@
                 android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION,
                 PackageHelper.APP_INSTALL_AUTO);
     }
+
+    public UserInfo createUser(String name, int flags) {
+        UserInfo userInfo = mUserManager.createUser(name, flags, getInstalledApplications(0));
+        return userInfo;
+    }
+
+    public boolean removeUser(int userId) {
+        if (userId == 0) {
+            return false;
+        }
+        mUserManager.removeUser(userId);
+        return true;
+    }
 }
diff --git a/services/java/com/android/server/pm/UserDetails.java b/services/java/com/android/server/pm/UserManager.java
similarity index 65%
rename from services/java/com/android/server/pm/UserDetails.java
rename to services/java/com/android/server/pm/UserManager.java
index 2aeed7c..76fa5ab 100644
--- a/services/java/com/android/server/pm/UserDetails.java
+++ b/services/java/com/android/server/pm/UserManager.java
@@ -18,9 +18,13 @@
 
 import com.android.internal.util.FastXmlSerializer;
 
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.SystemClock;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -37,7 +41,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
-public class UserDetails {
+public class UserManager {
     private static final String TAG_NAME = "name";
 
     private static final String ATTR_FLAGS = "flags";
@@ -48,22 +52,27 @@
 
     private static final String TAG_USER = "user";
 
-    private static final String TAG = "UserDetails";
+    private static final String LOG_TAG = "UserManager";
 
-    private static final String USER_INFO_DIR = "system/users";
+    private static final String USER_INFO_DIR = "system" + File.separator + "users";
     private static final String USER_LIST_FILENAME = "userlist.xml";
 
     private SparseArray<UserInfo> mUsers;
 
     private final File mUsersDir;
     private final File mUserListFile;
+    private int[] mUserIds;
+
+    private Installer mInstaller;
+    private File mBaseUserPath;
 
     /**
      * Available for testing purposes.
      */
-    UserDetails(File dataDir) {
+    UserManager(File dataDir, File baseUserPath) {
         mUsersDir = new File(dataDir, USER_INFO_DIR);
         mUsersDir.mkdirs();
+        mBaseUserPath = baseUserPath;
         FileUtils.setPermissions(mUsersDir.toString(),
                 FileUtils.S_IRWXU|FileUtils.S_IRWXG
                 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
@@ -72,8 +81,9 @@
         readUserList();
     }
 
-    public UserDetails() {
-        this(Environment.getDataDirectory());
+    public UserManager(Installer installer, File baseUserPath) {
+        this(Environment.getDataDirectory(), baseUserPath);
+        mInstaller = installer;
     }
 
     public List<UserInfo> getUsers() {
@@ -84,6 +94,15 @@
         return users;
     }
 
+    /**
+     * Returns an array of user ids. This array is cached here for quick access, so do not modify or
+     * cache it elsewhere.
+     * @return the array of user ids.
+     */
+    int[] getUserIds() {
+        return mUserIds;
+    }
+
     private void readUserList() {
         mUsers = new SparseArray<UserInfo>();
         if (!mUserListFile.exists()) {
@@ -102,7 +121,7 @@
             }
 
             if (type != XmlPullParser.START_TAG) {
-                Slog.e(TAG, "Unable to read user list");
+                Slog.e(LOG_TAG, "Unable to read user list");
                 fallbackToSingleUser();
                 return;
             }
@@ -116,6 +135,7 @@
                     }
                 }
             }
+            updateUserIds();
         } catch (IOException ioe) {
             fallbackToSingleUser();
         } catch (XmlPullParserException pe) {
@@ -128,6 +148,7 @@
         UserInfo primary = new UserInfo(0, "Primary",
                 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
         mUsers.put(0, primary);
+        updateUserIds();
 
         writeUserList();
         writeUser(primary);
@@ -164,7 +185,7 @@
 
             serializer.endDocument();
         } catch (IOException ioe) {
-            Slog.e(TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
+            Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
         }
     }
 
@@ -194,14 +215,13 @@
                 serializer.startTag(null, TAG_USER);
                 serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
                 serializer.endTag(null, TAG_USER);
-                Slog.e(TAG, "Wrote user " + user.id + " to userlist.xml");
             }
 
             serializer.endTag(null, TAG_USERS);
 
             serializer.endDocument();
         } catch (IOException ioe) {
-            Slog.e(TAG, "Error writing user list");
+            Slog.e(LOG_TAG, "Error writing user list");
         }
     }
 
@@ -222,14 +242,14 @@
             }
 
             if (type != XmlPullParser.START_TAG) {
-                Slog.e(TAG, "Unable to read user " + id);
+                Slog.e(LOG_TAG, "Unable to read user " + id);
                 return null;
             }
 
             if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
                 String storedId = parser.getAttributeValue(null, ATTR_ID);
                 if (Integer.parseInt(storedId) != id) {
-                    Slog.e(TAG, "User id does not match the file name");
+                    Slog.e(LOG_TAG, "User id does not match the file name");
                     return null;
                 }
                 String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
@@ -256,18 +276,25 @@
         return null;
     }
 
-    public UserInfo createUser(String name, int flags) {
-        int id = getNextAvailableId();
-        UserInfo userInfo = new UserInfo(id, name, flags);
-        if (!createPackageFolders(id)) {
+    public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) {
+        int userId = getNextAvailableId();
+        UserInfo userInfo = new UserInfo(userId, name, flags);
+        File userPath = new File(mBaseUserPath, Integer.toString(userId));
+        if (!createPackageFolders(userId, userPath, apps)) {
             return null;
         }
-        mUsers.put(id, userInfo);
+        mUsers.put(userId, userInfo);
         writeUserList();
         writeUser(userInfo);
+        updateUserIds();
         return userInfo;
     }
 
+    /**
+     * Removes a user and all data directories created for that user. This method should be called
+     * after the user's processes have been terminated.
+     * @param id the user's id
+     */
     public void removeUser(int id) {
         // Remove from the list
         UserInfo userInfo = mUsers.get(id);
@@ -277,11 +304,58 @@
             // Remove user file
             File userFile = new File(mUsersDir, id + ".xml");
             userFile.delete();
+            // Update the user list
             writeUserList();
+            // Remove the data directories for all packages for this user
             removePackageFolders(id);
+            updateUserIds();
         }
     }
 
+    public void installPackageForAllUsers(String packageName, int uid) {
+        for (int userId : mUserIds) {
+            // Don't do it for the primary user, it will become recursive.
+            if (userId == 0)
+                continue;
+            mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid),
+                    userId);
+        }
+    }
+
+    public void clearUserDataForAllUsers(String packageName) {
+        for (int userId : mUserIds) {
+            // Don't do it for the primary user, it will become recursive.
+            if (userId == 0)
+                continue;
+            mInstaller.clearUserData(packageName, userId);
+        }
+    }
+
+    public void removePackageForAllUsers(String packageName) {
+        for (int userId : mUserIds) {
+            // Don't do it for the primary user, it will become recursive.
+            if (userId == 0)
+                continue;
+            mInstaller.remove(packageName, userId);
+        }
+    }
+
+    /**
+     * Caches the list of user ids in an array, adjusting the array size when necessary.
+     */
+    private void updateUserIds() {
+        if (mUserIds == null || mUserIds.length != mUsers.size()) {
+            mUserIds = new int[mUsers.size()];
+        }
+        for (int i = 0; i < mUsers.size(); i++) {
+            mUserIds[i] = mUsers.keyAt(i);
+        }
+    }
+
+    /**
+     * Returns the next available user id, filling in any holes in the ids.
+     * @return
+     */
     private int getNextAvailableId() {
         int i = 0;
         while (i < Integer.MAX_VALUE) {
@@ -293,13 +367,35 @@
         return i;
     }
 
-    private boolean createPackageFolders(int id) {
-        // TODO: Create data directories for all the packages for a new user, w/ specified user id.
+    private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) {
+        // mInstaller may not be available for unit-tests.
+        if (mInstaller == null || apps == null) return true;
+
+        final long startTime = SystemClock.elapsedRealtime();
+        // Create the user path
+        userPath.mkdir();
+        FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+                | FileUtils.S_IXOTH, -1, -1);
+
+        // Create the individual data directories
+        for (ApplicationInfo app : apps) {
+            if (app.uid > android.os.Process.FIRST_APPLICATION_UID
+                    && app.uid < PackageManager.PER_USER_RANGE) {
+                mInstaller.createUserData(app.packageName,
+                        PackageManager.getUid(id, app.uid), id);
+            }
+        }
+        final long stopTime = SystemClock.elapsedRealtime();
+        Log.i(LOG_TAG,
+                "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms");
         return true;
     }
 
     private boolean removePackageFolders(int id) {
-        // TODO: Remove all the data directories for the specified user.
+        // mInstaller may not be available for unit-tests.
+        if (mInstaller == null) return true;
+
+        mInstaller.removeUserDataDirs(id);
         return true;
     }
 }
diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java
index b0978a3..69bde41 100644
--- a/services/java/com/android/server/wm/InputManager.java
+++ b/services/java/com/android/server/wm/InputManager.java
@@ -23,12 +23,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 import android.os.Environment;
 import android.os.Looper;
 import android.os.MessageQueue;
@@ -39,6 +33,7 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.KeyEvent;
+import android.view.PointerIcon;
 import android.view.Surface;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
@@ -63,7 +58,8 @@
     private final Context mContext;
     private final WindowManagerService mWindowManagerService;
     
-    private static native void nativeInit(Callbacks callbacks, MessageQueue messageQueue);
+    private static native void nativeInit(Context context,
+            Callbacks callbacks, MessageQueue messageQueue);
     private static native void nativeStart();
     private static native void nativeSetDisplaySize(int displayId, int width, int height);
     private static native void nativeSetDisplayOrientation(int displayId, int rotation);
@@ -133,7 +129,7 @@
         Looper looper = windowManagerService.mH.getLooper();
 
         Slog.i(TAG, "Initializing input manager");
-        nativeInit(mCallbacks, looper.getQueue());
+        nativeInit(mContext, mCallbacks, looper.getQueue());
     }
     
     public void start() {
@@ -435,48 +431,6 @@
         }
     }
 
-    private static final class PointerIcon {
-        public Bitmap bitmap;
-        public float hotSpotX;
-        public float hotSpotY;
-
-        public static PointerIcon load(Resources resources, int resourceId) {
-            PointerIcon icon = new PointerIcon();
-
-            XmlResourceParser parser = resources.getXml(resourceId);
-            final int bitmapRes;
-            try {
-                XmlUtils.beginDocument(parser, "pointer-icon");
-
-                TypedArray a = resources.obtainAttributes(
-                        parser, com.android.internal.R.styleable.PointerIcon);
-                bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
-                icon.hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
-                icon.hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
-                a.recycle();
-            } catch (Exception ex) {
-                Slog.e(TAG, "Exception parsing pointer icon resource.", ex);
-                return null;
-            } finally {
-                parser.close();
-            }
-
-            if (bitmapRes == 0) {
-                Slog.e(TAG, "<pointer-icon> is missing bitmap attribute");
-                return null;
-            }
-
-            Drawable drawable = resources.getDrawable(bitmapRes);
-            if (!(drawable instanceof BitmapDrawable)) {
-                Slog.e(TAG, "<pointer-icon> bitmap attribute must refer to a bitmap drawable");
-                return null;
-            }
-
-            icon.bitmap = ((BitmapDrawable)drawable).getBitmap();
-            return icon;
-        }
-    }
-
     /*
      * Callbacks from native.
      */
@@ -641,8 +595,7 @@
 
         @SuppressWarnings("unused")
         public PointerIcon getPointerIcon() {
-            return PointerIcon.load(mContext.getResources(),
-                    com.android.internal.R.drawable.pointer_arrow_icon);
+            return PointerIcon.getDefaultIcon(mContext);
         }
     }
 }
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index aaa305e..1f10d9c 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -36,13 +36,13 @@
 
 #include <input/InputManager.h>
 #include <input/PointerController.h>
-#include <input/SpotController.h>
 #include <input/SpriteController.h>
 
 #include <android_os_MessageQueue.h>
 #include <android_view_KeyEvent.h>
 #include <android_view_MotionEvent.h>
 #include <android_view_InputChannel.h>
+#include <android_view_PointerIcon.h>
 #include <android/graphics/GraphicsJNI.h>
 
 #include "com_android_server_PowerManagerService.h"
@@ -101,12 +101,6 @@
     jfieldID navigation;
 } gConfigurationClassInfo;
 
-static struct {
-    jfieldID bitmap;
-    jfieldID hotSpotX;
-    jfieldID hotSpotY;
-} gPointerIconClassInfo;
-
 
 // --- Global functions ---
 
@@ -128,17 +122,30 @@
             getInputWindowHandleObjLocalRef(env);
 }
 
+static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t style,
+        SpriteIcon* outSpriteIcon) {
+    PointerIcon pointerIcon;
+    status_t status = android_view_PointerIcon_loadSystemIcon(env,
+            contextObj, style, &pointerIcon);
+    if (!status) {
+        pointerIcon.bitmap.copyTo(&outSpriteIcon->bitmap, SkBitmap::kARGB_8888_Config);
+        outSpriteIcon->hotSpotX = pointerIcon.hotSpotX;
+        outSpriteIcon->hotSpotY = pointerIcon.hotSpotY;
+    }
+}
+
 
 // --- NativeInputManager ---
 
 class NativeInputManager : public virtual RefBase,
     public virtual InputReaderPolicyInterface,
-    public virtual InputDispatcherPolicyInterface {
+    public virtual InputDispatcherPolicyInterface,
+    public virtual PointerControllerPolicyInterface {
 protected:
     virtual ~NativeInputManager();
 
 public:
-    NativeInputManager(jobject callbacksObj, const sp<Looper>& looper);
+    NativeInputManager(jobject contextObj, jobject callbacksObj, const sp<Looper>& looper);
 
     inline sp<InputManager> getInputManager() const { return mInputManager; }
 
@@ -165,7 +172,6 @@
     virtual nsecs_t getVirtualKeyQuietTime();
     virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
-    virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId);
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -189,9 +195,14 @@
     virtual bool checkInjectEventsPermissionNonReentrant(
             int32_t injectorPid, int32_t injectorUid);
 
+    /* --- PointerControllerPolicyInterface implementation --- */
+
+    virtual void loadPointerResources(PointerResources* outResources);
+
 private:
     sp<InputManager> mInputManager;
 
+    jobject mContextObj;
     jobject mCallbacksObj;
     sp<Looper> mLooper;
 
@@ -223,7 +234,7 @@
         wp<PointerController> pointerController;
     } mLocked;
 
-    void updateInactivityFadeDelayLocked(const sp<PointerController>& controller);
+    void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
     void ensureSpriteControllerLocked();
 
@@ -240,13 +251,15 @@
 
 
 
-NativeInputManager::NativeInputManager(jobject callbacksObj, const sp<Looper>& looper) :
+NativeInputManager::NativeInputManager(jobject contextObj,
+        jobject callbacksObj, const sp<Looper>& looper) :
         mLooper(looper),
         mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
         mKeyRepeatTimeout(-1), mKeyRepeatDelay(-1),
         mMaxEventsPerSecond(-1) {
     JNIEnv* env = jniEnv();
 
+    mContextObj = env->NewGlobalRef(contextObj);
     mCallbacksObj = env->NewGlobalRef(callbacksObj);
 
     {
@@ -265,6 +278,7 @@
 NativeInputManager::~NativeInputManager() {
     JNIEnv* env = jniEnv();
 
+    env->DeleteGlobalRef(mContextObj);
     env->DeleteGlobalRef(mCallbacksObj);
 }
 
@@ -288,9 +302,13 @@
 
 void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height) {
     if (displayId == 0) {
-        AutoMutex _l(mLock);
+        { // acquire lock
+            AutoMutex _l(mLock);
 
-        if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+            if (mLocked.displayWidth == width && mLocked.displayHeight == height) {
+                return;
+            }
+
             mLocked.displayWidth = width;
             mLocked.displayHeight = height;
 
@@ -298,7 +316,7 @@
             if (controller != NULL) {
                 controller->setDisplaySize(width, height);
             }
-        }
+        } // release lock
     }
 }
 
@@ -428,40 +446,33 @@
     if (controller == NULL) {
         ensureSpriteControllerLocked();
 
-        controller = new PointerController(mLooper, mLocked.spriteController);
+        controller = new PointerController(this, mLooper, mLocked.spriteController);
         mLocked.pointerController = controller;
 
         controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
         controller->setDisplayOrientation(mLocked.displayOrientation);
 
         JNIEnv* env = jniEnv();
-        jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon);
-        if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) {
-            jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX);
-            jfloat iconHotSpotY = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotY);
-            jobject iconBitmapObj = env->GetObjectField(iconObj, gPointerIconClassInfo.bitmap);
-            if (iconBitmapObj) {
-                SkBitmap* iconBitmap = GraphicsJNI::getNativeBitmap(env, iconBitmapObj);
-                if (iconBitmap) {
-                    controller->setPointerIcon(iconBitmap, iconHotSpotX, iconHotSpotY);
-                }
-                env->DeleteLocalRef(iconBitmapObj);
+        jobject pointerIconObj = env->CallObjectMethod(mCallbacksObj,
+                gCallbacksClassInfo.getPointerIcon);
+        if (!checkAndClearExceptionFromCallback(env, "getPointerIcon")) {
+            PointerIcon pointerIcon;
+            status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+                    mContextObj, &pointerIcon);
+            if (!status && !pointerIcon.isNullIcon()) {
+                controller->setPointerIcon(SpriteIcon(pointerIcon.bitmap,
+                        pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+            } else {
+                controller->setPointerIcon(SpriteIcon());
             }
-            env->DeleteLocalRef(iconObj);
+            env->DeleteLocalRef(pointerIconObj);
         }
 
-        updateInactivityFadeDelayLocked(controller);
+        updateInactivityTimeoutLocked(controller);
     }
     return controller;
 }
 
-sp<SpotControllerInterface> NativeInputManager::obtainSpotController(int32_t deviceId) {
-    AutoMutex _l(mLock);
-
-    ensureSpriteControllerLocked();
-    return new SpotController(mLooper, mLocked.spriteController);
-}
-
 void NativeInputManager::ensureSpriteControllerLocked() {
     if (mLocked.spriteController == NULL) {
         JNIEnv* env = jniEnv();
@@ -642,16 +653,16 @@
 
         sp<PointerController> controller = mLocked.pointerController.promote();
         if (controller != NULL) {
-            updateInactivityFadeDelayLocked(controller);
+            updateInactivityTimeoutLocked(controller);
         }
     }
 }
 
-void NativeInputManager::updateInactivityFadeDelayLocked(const sp<PointerController>& controller) {
+void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) {
     bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
-    controller->setInactivityFadeDelay(lightsOut
-            ? PointerController::INACTIVITY_FADE_DELAY_SHORT
-            : PointerController::INACTIVITY_FADE_DELAY_NORMAL);
+    controller->setInactivityTimeout(lightsOut
+            ? PointerController::INACTIVITY_TIMEOUT_SHORT
+            : PointerController::INACTIVITY_TIMEOUT_NORMAL);
 }
 
 bool NativeInputManager::isScreenOn() {
@@ -884,6 +895,17 @@
     return result;
 }
 
+void NativeInputManager::loadPointerResources(PointerResources* outResources) {
+    JNIEnv* env = jniEnv();
+
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_HOVER,
+            &outResources->spotHover);
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_TOUCH,
+            &outResources->spotTouch);
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_ANCHOR,
+            &outResources->spotAnchor);
+}
+
 
 // ----------------------------------------------------------------------------
 
@@ -899,10 +921,10 @@
 }
 
 static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
-        jobject callbacks, jobject messageQueueObj) {
+        jobject contextObj, jobject callbacksObj, jobject messageQueueObj) {
     if (gNativeInputManager == NULL) {
         sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
-        gNativeInputManager = new NativeInputManager(callbacks, looper);
+        gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper);
     } else {
         LOGE("Input manager already initialized.");
         jniThrowRuntimeException(env, "Input manager already initialized.");
@@ -1246,7 +1268,8 @@
 
 static JNINativeMethod gInputManagerMethods[] = {
     /* name, signature, funcPtr */
-    { "nativeInit", "(Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
+    { "nativeInit", "(Landroid/content/Context;"
+            "Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
             (void*) android_server_InputManager_nativeInit },
     { "nativeStart", "()V",
             (void*) android_server_InputManager_nativeStart },
@@ -1372,7 +1395,7 @@
             "getPointerLayer", "()I");
 
     GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, clazz,
-            "getPointerIcon", "()Lcom/android/server/wm/InputManager$PointerIcon;");
+            "getPointerIcon", "()Landroid/view/PointerIcon;");
 
     // KeyEvent
 
@@ -1421,19 +1444,6 @@
     GET_FIELD_ID(gConfigurationClassInfo.navigation, clazz,
             "navigation", "I");
 
-    // PointerIcon
-
-    FIND_CLASS(clazz, "com/android/server/wm/InputManager$PointerIcon");
-
-    GET_FIELD_ID(gPointerIconClassInfo.bitmap, clazz,
-            "bitmap", "Landroid/graphics/Bitmap;");
-
-    GET_FIELD_ID(gPointerIconClassInfo.hotSpotX, clazz,
-            "hotSpotX", "F");
-
-    GET_FIELD_ID(gPointerIconClassInfo.hotSpotY, clazz,
-            "hotSpotY", "F");
-
     return 0;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 64cff96..a774841 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -93,7 +93,11 @@
 int DisplayHardware::getHeight() const          { return mHeight; }
 PixelFormat DisplayHardware::getFormat() const  { return mFormat; }
 uint32_t DisplayHardware::getMaxTextureSize() const { return mMaxTextureSize; }
-uint32_t DisplayHardware::getMaxViewportDims() const { return mMaxViewportDims; }
+
+uint32_t DisplayHardware::getMaxViewportDims() const {
+    return mMaxViewportDims[0] < mMaxViewportDims[1] ?
+            mMaxViewportDims[0] : mMaxViewportDims[1];
+}
 
 void DisplayHardware::init(uint32_t dpy)
 {
@@ -228,7 +232,7 @@
             eglQueryString(display, EGL_EXTENSIONS));
 
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
-    glGetIntegerv(GL_MAX_VIEWPORT_DIMS, &mMaxViewportDims);
+    glGetIntegerv(GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
 
 
 #ifdef EGL_ANDROID_swap_rectangle
@@ -260,7 +264,7 @@
     LOGI("version   : %s", extensions.getVersion());
     LOGI("extensions: %s", extensions.getExtension());
     LOGI("GL_MAX_TEXTURE_SIZE = %d", mMaxTextureSize);
-    LOGI("GL_MAX_VIEWPORT_DIMS = %d", mMaxViewportDims);
+    LOGI("GL_MAX_VIEWPORT_DIMS = %d x %d", mMaxViewportDims[0], mMaxViewportDims[1]);
     LOGI("flags = %08x", mFlags);
 
     // Unbind the context from this thread
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
index ee7a2af..cdf89fd 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
@@ -108,7 +108,7 @@
     PixelFormat     mFormat;
     uint32_t        mFlags;
     mutable uint32_t mPageFlipCount;
-    GLint           mMaxViewportDims;
+    GLint           mMaxViewportDims[2];
     GLint           mMaxTextureSize;
     
     HWComposer*     mHwc;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
similarity index 77%
rename from services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java
rename to services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 7b77aac..e8188e7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.pm;
 
-import com.android.server.pm.UserDetails;
+import com.android.server.pm.UserManager;
 
 import android.content.pm.UserInfo;
 import android.os.Debug;
@@ -25,23 +25,24 @@
 
 import java.util.List;
 
-/** Test {@link UserDetails} functionality. */
-public class UserDetailsTest extends AndroidTestCase {
+/** Test {@link UserManager} functionality. */
+public class UserManagerTest extends AndroidTestCase {
 
-    UserDetails mDetails = null;
+    UserManager mUserManager = null;
 
     @Override
     public void setUp() throws Exception {
-        mDetails = new UserDetails(Environment.getExternalStorageDirectory());
+        mUserManager = new UserManager(Environment.getExternalStorageDirectory(),
+                Environment.getExternalStorageDirectory());
     }
 
     @Override
     public void tearDown() throws Exception {
-        List<UserInfo> users = mDetails.getUsers();
+        List<UserInfo> users = mUserManager.getUsers();
         // Remove all except the primary user
         for (UserInfo user : users) {
             if (!user.isPrimary()) {
-                mDetails.removeUser(user.id);
+                mUserManager.removeUser(user.id);
             }
         }
     }
@@ -51,9 +52,9 @@
     }
 
     public void testAddUser() throws Exception {
-        final UserDetails details = mDetails;
+        final UserManager details = mUserManager;
 
-        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
         assertTrue(userInfo != null);
 
         List<UserInfo> list = details.getUsers();
@@ -70,10 +71,10 @@
     }
 
     public void testAdd2Users() throws Exception {
-        final UserDetails details = mDetails;
+        final UserManager details = mUserManager;
 
-        UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
-        UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN);
+        UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
+        UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN, null);
 
         assertTrue(user1 != null);
         assertTrue(user2 != null);
@@ -84,9 +85,9 @@
     }
 
     public void testRemoveUser() throws Exception {
-        final UserDetails details = mDetails;
+        final UserManager details = mUserManager;
 
-        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
 
         details.removeUser(userInfo.id);
 
@@ -94,7 +95,7 @@
     }
 
     private boolean findUser(int id) {
-        List<UserInfo> list = mDetails.getUsers();
+        List<UserInfo> list = mUserManager.getUsers();
 
         for (UserInfo user : list) {
             if (user.id == id) {
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 0c47e86..791fbfd 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -17,8 +17,8 @@
 package com.android.internal.telephony;
 
 
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
 
 import android.net.LinkAddress;
 import android.net.LinkCapabilities;
@@ -37,7 +37,7 @@
 /**
  * {@hide}
  *
- * DataConnection HierarchicalStateMachine.
+ * DataConnection StateMachine.
  *
  * This is an abstract base class for representing a single data connection.
  * Instances of this class such as <code>CdmaDataConnection</code> and
@@ -55,7 +55,7 @@
  *
  * The other public methods are provided for debugging.
  */
-public abstract class DataConnection extends HierarchicalStateMachine {
+public abstract class DataConnection extends StateMachine {
     protected static final boolean DBG = true;
 
     protected static Object mCountLock = new Object();
@@ -484,17 +484,17 @@
     /**
      * The parent state for all other states.
      */
-    private class DcDefaultState extends HierarchicalState {
+    private class DcDefaultState extends State {
         @Override
-        protected void enter() {
+        public void enter() {
             phone.mCM.registerForRilConnected(getHandler(), EVENT_RIL_CONNECTED, null);
         }
         @Override
-        protected void exit() {
+        public void exit() {
             phone.mCM.unregisterForRilConnected(getHandler());
         }
         @Override
-        protected boolean processMessage(Message msg) {
+        public boolean processMessage(Message msg) {
             AsyncResult ar;
 
             switch (msg.what) {
@@ -547,7 +547,7 @@
     /**
      * The state machine is inactive and expects a EVENT_CONNECT.
      */
-    private class DcInactiveState extends HierarchicalState {
+    private class DcInactiveState extends State {
         private ConnectionParams mConnectionParams = null;
         private FailCause mFailCause = null;
         private DisconnectParams mDisconnectParams = null;
@@ -563,7 +563,8 @@
             mDisconnectParams = dp;
         }
 
-        @Override protected void enter() {
+        @Override
+        public void enter() {
             mTag += 1;
 
             /**
@@ -583,14 +584,16 @@
             }
         }
 
-        @Override protected void exit() {
+        @Override
+        public void exit() {
             // clear notifications
             mConnectionParams = null;
             mFailCause = null;
             mDisconnectParams = null;
         }
 
-        @Override protected boolean processMessage(Message msg) {
+        @Override
+        public boolean processMessage(Message msg) {
             boolean retVal;
 
             switch (msg.what) {
@@ -626,8 +629,9 @@
     /**
      * The state machine is activating a connection.
      */
-    private class DcActivatingState extends HierarchicalState {
-        @Override protected boolean processMessage(Message msg) {
+    private class DcActivatingState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
             boolean retVal;
             AsyncResult ar;
             ConnectionParams cp;
@@ -722,7 +726,7 @@
     /**
      * The state machine is connected, expecting an EVENT_DISCONNECT.
      */
-    private class DcActiveState extends HierarchicalState {
+    private class DcActiveState extends State {
         private ConnectionParams mConnectionParams = null;
         private FailCause mFailCause = null;
 
@@ -746,13 +750,15 @@
             }
         }
 
-        @Override protected void exit() {
+        @Override
+        public void exit() {
             // clear notifications
             mConnectionParams = null;
             mFailCause = null;
         }
 
-        @Override protected boolean processMessage(Message msg) {
+        @Override
+        public boolean processMessage(Message msg) {
             boolean retVal;
 
             switch (msg.what) {
@@ -778,8 +784,9 @@
     /**
      * The state machine is disconnecting.
      */
-    private class DcDisconnectingState extends HierarchicalState {
-        @Override protected boolean processMessage(Message msg) {
+    private class DcDisconnectingState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
             boolean retVal;
 
             switch (msg.what) {
@@ -812,8 +819,9 @@
     /**
      * The state machine is disconnecting after an creating a connection.
      */
-    private class DcDisconnectionErrorCreatingConnection extends HierarchicalState {
-        @Override protected boolean processMessage(Message msg) {
+    private class DcDisconnectionErrorCreatingConnection extends State {
+        @Override
+        public boolean processMessage(Message msg) {
             boolean retVal;
 
             switch (msg.what) {
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 488794f..56be570 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -159,8 +159,8 @@
     static final String REASON_ROAMING_OFF = "roamingOff";
     static final String REASON_DATA_DISABLED = "dataDisabled";
     static final String REASON_DATA_ENABLED = "dataEnabled";
-    static final String REASON_GPRS_ATTACHED = "gprsAttached";
-    static final String REASON_GPRS_DETACHED = "gprsDetached";
+    static final String REASON_DATA_ATTACHED = "dataAttached";
+    static final String REASON_DATA_DETACHED = "dataDetached";
     static final String REASON_CDMA_DATA_ATTACHED = "cdmaDataAttached";
     static final String REASON_CDMA_DATA_DETACHED = "cdmaDataDetached";
     static final String REASON_APN_CHANGED = "apnChanged";
diff --git a/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java
index a197c9a..2a1f508 100644
--- a/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java
+++ b/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java
@@ -20,15 +20,15 @@
 import com.android.internal.telephony.IccUtils;
 
 import android.os.Handler;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
 import android.os.Message;
 
 /**
  * Class used for queuing raw ril messages, decoding them into CommanParams
  * objects and sending the result back to the CAT Service.
  */
-class RilMessageDecoder extends HierarchicalStateMachine {
+class RilMessageDecoder extends StateMachine {
 
     // constants
     private static final int CMD_START = 1;
@@ -101,8 +101,9 @@
         mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh);
     }
 
-    private class StateStart extends HierarchicalState {
-        @Override protected boolean processMessage(Message msg) {
+    private class StateStart extends State {
+        @Override
+        public boolean processMessage(Message msg) {
             if (msg.what == CMD_START) {
                 if (decodeMessageParams((RilMessage)msg.obj)) {
                     transitionTo(mStateCmdParamsReady);
@@ -115,8 +116,9 @@
         }
     }
 
-    private class StateCmdParamsReady extends HierarchicalState {
-        @Override protected boolean processMessage(Message msg) {
+    private class StateCmdParamsReady extends State {
+        @Override
+        public boolean processMessage(Message msg) {
             if (msg.what == CMD_PARAMS_READY) {
                 mCurrentRilMessage.mResCode = ResultCode.fromInt(msg.arg1);
                 mCurrentRilMessage.mData = msg.obj;
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 9a91ee4..fc1d536 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -505,9 +505,8 @@
         ApnContext apnContext = mApnContexts.get(type);
 
         if (apnContext != null) {
-            apnContext.setPendingAction(ApnContext.PENDING_ACTION_APN_DISABLE);
-
             if (apnContext.getState() != State.IDLE && apnContext.getState() != State.FAILED) {
+                apnContext.setPendingAction(ApnContext.PENDING_ACTION_APN_DISABLE);
                 Message msg = obtainMessage(EVENT_CLEAN_UP_CONNECTION);
                 msg.arg1 = 1; // tearDown is true;
                 // TODO - don't set things on apnContext from public functions.
@@ -519,6 +518,7 @@
                 return Phone.APN_REQUEST_STARTED;
             } else {
                 if (DBG) log("return APN_ALREADY_INACTIVE");
+                apnContext.setEnabled(false);
                 return Phone.APN_ALREADY_INACTIVE;
             }
 
@@ -582,26 +582,16 @@
          * when GPRS detaches, but we should stop the network polling.
          */
         stopNetStatPoll();
-        notifyDataConnection(Phone.REASON_GPRS_DETACHED);
+        notifyDataConnection(Phone.REASON_DATA_DETACHED);
     }
 
     private void onDataConnectionAttached() {
         if (getOverallState() == State.CONNECTED) {
             startNetStatPoll();
-            notifyDataConnection(Phone.REASON_GPRS_ATTACHED);
-        } else {
-            // Only check for default APN state
-            ApnContext defaultApnContext = mApnContexts.get(Phone.APN_TYPE_DEFAULT);
-            if (defaultApnContext != null) {
-                if (defaultApnContext.getState() == State.FAILED) {
-                    cleanUpConnection(false, defaultApnContext);
-                    if (defaultApnContext.getDataConnection() != null) {
-                        defaultApnContext.getDataConnection().resetRetryCount();
-                    }
-                }
-                trySetupData(Phone.REASON_GPRS_ATTACHED, Phone.APN_TYPE_DEFAULT);
-            }
+            notifyDataConnection(Phone.REASON_DATA_ATTACHED);
         }
+
+        setupDataOnReadyApns(Phone.REASON_DATA_ATTACHED);
     }
 
     @Override
@@ -610,7 +600,7 @@
         boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
 
         boolean allowed =
-                    (gprsState == ServiceState.STATE_IN_SERVICE || mAutoAttachOnCreation) &&
+                    gprsState == ServiceState.STATE_IN_SERVICE &&
                     mPhone.mSIMRecords.getRecordsLoaded() &&
                     mPhone.getState() == Phone.State.IDLE &&
                     mInternalDataEnabled &&
@@ -619,7 +609,7 @@
                     desiredPowerState;
         if (!allowed && DBG) {
             String reason = "";
-            if (!((gprsState == ServiceState.STATE_IN_SERVICE) || mAutoAttachOnCreation)) {
+            if (!(gprsState == ServiceState.STATE_IN_SERVICE)) {
                 reason += " - gprs= " + gprsState;
             }
             if (!mPhone.mSIMRecords.getRecordsLoaded()) reason += " - SIM not loaded";
@@ -637,6 +627,26 @@
         return allowed;
     }
 
+    private void setupDataOnReadyApns(String reason) {
+        // Only check for default APN state
+        for (ApnContext apnContext : mApnContexts.values()) {
+            if (apnContext.isReady()) {
+                if (apnContext.getState() == State.FAILED) {
+                    cleanUpConnection(false, apnContext);
+                    if (apnContext.getDataConnection() != null) {
+                        apnContext.getDataConnection().resetRetryCount();
+                    }
+                }
+                // Do not start ApnContext in SCANNING state
+                // FAILED state must be reset to IDLE by now
+                if (apnContext.getState() == State.IDLE) {
+                    apnContext.setReason(reason);
+                    trySetupData(apnContext);
+                }
+            }
+        }
+    }
+
     private boolean trySetupData(String reason, String type) {
         if (DBG) {
             log("***trySetupData for type:" + type +
@@ -973,10 +983,7 @@
         createAllApnList();
         cleanUpAllConnections(isConnected, Phone.REASON_APN_CHANGED);
         if (!isConnected) {
-            // TODO: Won't work for multiple connections!!!!
-            defaultApnContext.getDataConnection().resetRetryCount();
-            defaultApnContext.setReason(Phone.REASON_APN_CHANGED);
-            trySetupData(defaultApnContext);
+            setupDataOnReadyApns(Phone.REASON_APN_CHANGED);
         }
     }
 
@@ -1316,18 +1323,7 @@
     private void onRecordsLoaded() {
         if (DBG) log("onRecordsLoaded: createAllApnList");
         createAllApnList();
-        for (ApnContext apnContext : mApnContexts.values()) {
-            if (apnContext.isReady()) {
-                apnContext.setReason(Phone.REASON_SIM_LOADED);
-                if (apnContext.getState() == State.FAILED) {
-                    if (DBG) {
-                        log("onRecordsLoaded clean connection for " + apnContext.getApnType());
-                    }
-                    cleanUpConnection(false, apnContext);
-                }
-                sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, apnContext));
-            }
-        }
+        setupDataOnReadyApns(Phone.REASON_SIM_LOADED);
     }
 
     @Override
@@ -1413,7 +1409,8 @@
     @Override
     // TODO: We shouldnt need this.
     protected boolean onTrySetupData(String reason) {
-        return trySetupData(reason, Phone.APN_TYPE_DEFAULT);
+        setupDataOnReadyApns(reason);
+        return true;
     }
 
     protected boolean onTrySetupData(ApnContext apnContext) {
@@ -1421,16 +1418,14 @@
     }
 
     @Override
-    // TODO: Need to understand if more than DEFAULT is impacted?
     protected void onRoamingOff() {
-        trySetupData(Phone.REASON_ROAMING_OFF, Phone.APN_TYPE_DEFAULT);
+        setupDataOnReadyApns(Phone.REASON_ROAMING_OFF);
     }
 
     @Override
-    // TODO: Need to understand if more than DEFAULT is impacted?
     protected void onRoamingOn() {
         if (getDataOnRoamingEnabled()) {
-            trySetupData(Phone.REASON_ROAMING_ON, Phone.APN_TYPE_DEFAULT);
+            setupDataOnReadyApns(Phone.REASON_ROAMING_ON);
         } else {
             if (DBG) log("Tear down data connection on roaming.");
             cleanUpAllConnections(true, Phone.REASON_ROAMING_ON);
@@ -1618,14 +1613,12 @@
             }
         }
 
-        if (TextUtils.equals(apnContext.getApnType(), Phone.APN_TYPE_DEFAULT)
-            && retryAfterDisconnected(apnContext.getReason())) {
+        // If APN is still enabled, try to bring it back up automatically
+        if (apnContext.isReady() && retryAfterDisconnected(apnContext.getReason())) {
             SystemProperties.set("gsm.defaultpdpcontext.active", "false");
-            trySetupData(apnContext);
-        }
-        else if (apnContext.getPendingAction() == ApnContext.PENDING_ACTION_RECONNECT)
-        {
-            apnContext.setPendingAction(ApnContext.PENDING_ACTION_NONE);
+            if (apnContext.getPendingAction() == ApnContext.PENDING_ACTION_RECONNECT) {
+                apnContext.setPendingAction(ApnContext.PENDING_ACTION_NONE);
+            }
             trySetupData(apnContext);
         }
     }
@@ -1658,13 +1651,7 @@
             }
         } else {
             // reset reconnect timer
-            ApnContext defaultApnContext = mApnContexts.get(Phone.APN_TYPE_DEFAULT);
-            if (defaultApnContext != null) {
-                defaultApnContext.getDataConnection().resetRetryCount();
-                mReregisterOnReconnectFailure = false;
-                // in case data setup was attempted when we were on a voice call
-                trySetupData(Phone.REASON_VOICE_CALL_ENDED, Phone.APN_TYPE_DEFAULT);
-            }
+            setupDataOnReadyApns(Phone.REASON_VOICE_CALL_ENDED);
         }
     }
 
diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java
index 3cde949..9ae26da 100644
--- a/wifi/java/android/net/wifi/SupplicantStateTracker.java
+++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java
@@ -16,8 +16,8 @@
 
 package android.net.wifi;
 
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
 
 import android.net.wifi.WifiStateMachine.StateChangeResult;
 import android.content.Context;
@@ -33,7 +33,7 @@
  * - detect a failed WPA handshake that loops indefinitely
  * - authentication failure handling
  */
-class SupplicantStateTracker extends HierarchicalStateMachine {
+class SupplicantStateTracker extends StateMachine {
 
     private static final String TAG = "SupplicantStateTracker";
     private static final boolean DBG = false;
@@ -53,14 +53,14 @@
 
     private Context mContext;
 
-    private HierarchicalState mUninitializedState = new UninitializedState();
-    private HierarchicalState mDefaultState = new DefaultState();
-    private HierarchicalState mInactiveState = new InactiveState();
-    private HierarchicalState mDisconnectState = new DisconnectedState();
-    private HierarchicalState mScanState = new ScanState();
-    private HierarchicalState mHandshakeState = new HandshakeState();
-    private HierarchicalState mCompletedState = new CompletedState();
-    private HierarchicalState mDormantState = new DormantState();
+    private State mUninitializedState = new UninitializedState();
+    private State mDefaultState = new DefaultState();
+    private State mInactiveState = new InactiveState();
+    private State mDisconnectState = new DisconnectedState();
+    private State mScanState = new ScanState();
+    private State mHandshakeState = new HandshakeState();
+    private State mCompletedState = new CompletedState();
+    private State mDormantState = new DormantState();
 
     public SupplicantStateTracker(Context context, WifiStateMachine wsm, Handler target) {
         super(TAG, target.getLooper());
@@ -146,7 +146,7 @@
      * HSM states
      *******************************************************/
 
-    class DefaultState extends HierarchicalState {
+    class DefaultState extends State {
         @Override
          public void enter() {
              if (DBG) Log.d(TAG, getName() + "\n");
@@ -188,21 +188,21 @@
      * or after we have lost the control channel
      * connection to the supplicant
      */
-    class UninitializedState extends HierarchicalState {
+    class UninitializedState extends State {
         @Override
          public void enter() {
              if (DBG) Log.d(TAG, getName() + "\n");
          }
     }
 
-    class InactiveState extends HierarchicalState {
+    class InactiveState extends State {
         @Override
          public void enter() {
              if (DBG) Log.d(TAG, getName() + "\n");
          }
     }
 
-    class DisconnectedState extends HierarchicalState {
+    class DisconnectedState extends State {
         @Override
          public void enter() {
              if (DBG) Log.d(TAG, getName() + "\n");
@@ -221,14 +221,14 @@
          }
     }
 
-    class ScanState extends HierarchicalState {
+    class ScanState extends State {
         @Override
          public void enter() {
              if (DBG) Log.d(TAG, getName() + "\n");
          }
     }
 
-    class HandshakeState extends HierarchicalState {
+    class HandshakeState extends State {
         /**
          * The max number of the WPA supplicant loop iterations before we
          * decide that the loop should be terminated:
@@ -277,7 +277,7 @@
         }
     }
 
-    class CompletedState extends HierarchicalState {
+    class CompletedState extends State {
         @Override
          public void enter() {
              if (DBG) Log.d(TAG, getName() + "\n");
@@ -318,7 +318,7 @@
     }
 
     //TODO: remove after getting rid of the state in supplicant
-    class DormantState extends HierarchicalState {
+    class DormantState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 46c07a3..16611d8 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -73,8 +73,8 @@
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
@@ -88,7 +88,7 @@
  *
  * @hide
  */
-public class WifiStateMachine extends HierarchicalStateMachine {
+public class WifiStateMachine extends StateMachine {
 
     private static final String TAG = "WifiStateMachine";
     private static final String NETWORKTYPE = "WIFI";
@@ -358,50 +358,50 @@
     private static final int MAX_RSSI = 256;
 
     /* Default parent state */
-    private HierarchicalState mDefaultState = new DefaultState();
+    private State mDefaultState = new DefaultState();
     /* Temporary initial state */
-    private HierarchicalState mInitialState = new InitialState();
+    private State mInitialState = new InitialState();
     /* Unloading the driver */
-    private HierarchicalState mDriverUnloadingState = new DriverUnloadingState();
+    private State mDriverUnloadingState = new DriverUnloadingState();
     /* Loading the driver */
-    private HierarchicalState mDriverUnloadedState = new DriverUnloadedState();
+    private State mDriverUnloadedState = new DriverUnloadedState();
     /* Driver load/unload failed */
-    private HierarchicalState mDriverFailedState = new DriverFailedState();
+    private State mDriverFailedState = new DriverFailedState();
     /* Driver loading */
-    private HierarchicalState mDriverLoadingState = new DriverLoadingState();
+    private State mDriverLoadingState = new DriverLoadingState();
     /* Driver loaded */
-    private HierarchicalState mDriverLoadedState = new DriverLoadedState();
+    private State mDriverLoadedState = new DriverLoadedState();
     /* Driver loaded, waiting for supplicant to start */
-    private HierarchicalState mSupplicantStartingState = new SupplicantStartingState();
+    private State mSupplicantStartingState = new SupplicantStartingState();
     /* Driver loaded and supplicant ready */
-    private HierarchicalState mSupplicantStartedState = new SupplicantStartedState();
+    private State mSupplicantStartedState = new SupplicantStartedState();
     /* Waiting for supplicant to stop and monitor to exit */
-    private HierarchicalState mSupplicantStoppingState = new SupplicantStoppingState();
+    private State mSupplicantStoppingState = new SupplicantStoppingState();
     /* Driver start issued, waiting for completed event */
-    private HierarchicalState mDriverStartingState = new DriverStartingState();
+    private State mDriverStartingState = new DriverStartingState();
     /* Driver started */
-    private HierarchicalState mDriverStartedState = new DriverStartedState();
+    private State mDriverStartedState = new DriverStartedState();
     /* Driver stopping */
-    private HierarchicalState mDriverStoppingState = new DriverStoppingState();
+    private State mDriverStoppingState = new DriverStoppingState();
     /* Driver stopped */
-    private HierarchicalState mDriverStoppedState = new DriverStoppedState();
+    private State mDriverStoppedState = new DriverStoppedState();
     /* Scan for networks, no connection will be established */
-    private HierarchicalState mScanModeState = new ScanModeState();
+    private State mScanModeState = new ScanModeState();
     /* Connecting to an access point */
-    private HierarchicalState mConnectModeState = new ConnectModeState();
+    private State mConnectModeState = new ConnectModeState();
     /* Fetching IP after network connection (assoc+auth complete) */
-    private HierarchicalState mConnectingState = new ConnectingState();
+    private State mConnectingState = new ConnectingState();
     /* Connected with IP addr */
-    private HierarchicalState mConnectedState = new ConnectedState();
+    private State mConnectedState = new ConnectedState();
     /* disconnect issued, waiting for network disconnect confirmation */
-    private HierarchicalState mDisconnectingState = new DisconnectingState();
+    private State mDisconnectingState = new DisconnectingState();
     /* Network is not connected, supplicant assoc+auth is not complete */
-    private HierarchicalState mDisconnectedState = new DisconnectedState();
+    private State mDisconnectedState = new DisconnectedState();
     /* Waiting for WPS to be completed*/
-    private HierarchicalState mWaitForWpsCompletionState = new WaitForWpsCompletionState();
+    private State mWaitForWpsCompletionState = new WaitForWpsCompletionState();
 
     /* Soft Ap is running */
-    private HierarchicalState mSoftApStartedState = new SoftApStartedState();
+    private State mSoftApStartedState = new SoftApStartedState();
 
 
     /**
@@ -1543,7 +1543,7 @@
      * HSM states
      *******************************************************/
 
-    class DefaultState extends HierarchicalState {
+    class DefaultState extends State {
         @Override
         public boolean processMessage(Message message) {
             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
@@ -1617,7 +1617,7 @@
         }
     }
 
-    class InitialState extends HierarchicalState {
+    class InitialState extends State {
         @Override
         //TODO: could move logging into a common class
         public void enter() {
@@ -1636,7 +1636,7 @@
         }
     }
 
-    class DriverLoadingState extends HierarchicalState {
+    class DriverLoadingState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -1715,7 +1715,7 @@
         }
     }
 
-    class DriverLoadedState extends HierarchicalState {
+    class DriverLoadedState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -1768,7 +1768,7 @@
         }
     }
 
-    class DriverUnloadingState extends HierarchicalState {
+    class DriverUnloadingState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -1849,7 +1849,7 @@
         }
     }
 
-    class DriverUnloadedState extends HierarchicalState {
+    class DriverUnloadedState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -1870,7 +1870,7 @@
         }
     }
 
-    class DriverFailedState extends HierarchicalState {
+    class DriverFailedState extends State {
         @Override
         public void enter() {
             Log.e(TAG, getName() + "\n");
@@ -1884,7 +1884,7 @@
     }
 
 
-    class SupplicantStartingState extends HierarchicalState {
+    class SupplicantStartingState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -1956,7 +1956,7 @@
         }
     }
 
-    class SupplicantStartedState extends HierarchicalState {
+    class SupplicantStartedState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -1978,17 +1978,6 @@
             boolean eventLoggingEnabled = true;
             switch(message.what) {
                 case CMD_STOP_SUPPLICANT:   /* Supplicant stopped by user */
-                    Log.d(TAG, "stopping supplicant");
-                    if (!WifiNative.stopSupplicant()) {
-                        Log.e(TAG, "Failed to stop supplicant, issue kill");
-                        WifiNative.killSupplicant();
-                    }
-                    mNetworkInfo.setIsAvailable(false);
-                    handleNetworkDisconnect();
-                    setWifiState(WIFI_STATE_DISABLING);
-                    sendSupplicantConnectionChangedBroadcast(false);
-                    mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
-                    mWpsStateMachine.sendMessage(CMD_RESET_WPS_STATE);
                     transitionTo(mSupplicantStoppingState);
                     break;
                 case SUP_DISCONNECTION_EVENT:  /* Supplicant connection lost */
@@ -2084,11 +2073,22 @@
         }
     }
 
-    class SupplicantStoppingState extends HierarchicalState {
+    class SupplicantStoppingState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
             EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+            Log.d(TAG, "stopping supplicant");
+            if (!WifiNative.stopSupplicant()) {
+                Log.e(TAG, "Failed to stop supplicant, issue kill");
+                WifiNative.killSupplicant();
+            }
+            mNetworkInfo.setIsAvailable(false);
+            handleNetworkDisconnect();
+            setWifiState(WIFI_STATE_DISABLING);
+            sendSupplicantConnectionChangedBroadcast(false);
+            mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+            mWpsStateMachine.sendMessage(CMD_RESET_WPS_STATE);
         }
         @Override
         public boolean processMessage(Message message) {
@@ -2127,7 +2127,7 @@
         }
     }
 
-    class DriverStartingState extends HierarchicalState {
+    class DriverStartingState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2168,7 +2168,7 @@
         }
     }
 
-    class DriverStartedState extends HierarchicalState {
+    class DriverStartedState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2272,7 +2272,7 @@
         }
     }
 
-    class DriverStoppingState extends HierarchicalState {
+    class DriverStoppingState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2308,7 +2308,7 @@
         }
     }
 
-    class DriverStoppedState extends HierarchicalState {
+    class DriverStoppedState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2332,7 +2332,7 @@
         }
     }
 
-    class ScanModeState extends HierarchicalState {
+    class ScanModeState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2369,7 +2369,7 @@
         }
     }
 
-    class ConnectModeState extends HierarchicalState {
+    class ConnectModeState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2479,7 +2479,7 @@
         }
     }
 
-    class ConnectingState extends HierarchicalState {
+    class ConnectingState extends State {
         boolean mModifiedBluetoothCoexistenceMode;
         int mPowerMode;
         boolean mUseStaticIp;
@@ -2677,7 +2677,7 @@
       }
     }
 
-    class ConnectedState extends HierarchicalState {
+    class ConnectedState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2789,7 +2789,7 @@
         }
     }
 
-    class DisconnectingState extends HierarchicalState {
+    class DisconnectingState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2819,7 +2819,7 @@
         }
     }
 
-    class DisconnectedState extends HierarchicalState {
+    class DisconnectedState extends State {
         private boolean mAlarmEnabled = false;
         /* This is set from the overlay config file or from a secure setting.
          * A value of 0 disables scanning in the framework.
@@ -2931,7 +2931,7 @@
         }
     }
 
-    class WaitForWpsCompletionState extends HierarchicalState {
+    class WaitForWpsCompletionState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
@@ -2970,7 +2970,7 @@
         }
     }
 
-    class SoftApStartedState extends HierarchicalState {
+    class SoftApStartedState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
diff --git a/wifi/java/android/net/wifi/WpsStateMachine.java b/wifi/java/android/net/wifi/WpsStateMachine.java
index 32d77a1..120b228 100644
--- a/wifi/java/android/net/wifi/WpsStateMachine.java
+++ b/wifi/java/android/net/wifi/WpsStateMachine.java
@@ -17,8 +17,8 @@
 package android.net.wifi;
 
 import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
 
 import android.content.Context;
 import android.content.Intent;
@@ -46,7 +46,7 @@
  * reloads the configuration and updates the IP and proxy
  * settings, if any.
  */
-class WpsStateMachine extends HierarchicalStateMachine {
+class WpsStateMachine extends StateMachine {
 
     private static final String TAG = "WpsStateMachine";
     private static final boolean DBG = false;
@@ -58,9 +58,9 @@
     private Context mContext;
     AsyncChannel mReplyChannel = new AsyncChannel();
 
-    private HierarchicalState mDefaultState = new DefaultState();
-    private HierarchicalState mInactiveState = new InactiveState();
-    private HierarchicalState mActiveState = new ActiveState();
+    private State mDefaultState = new DefaultState();
+    private State mInactiveState = new InactiveState();
+    private State mActiveState = new ActiveState();
 
     public WpsStateMachine(Context context, WifiStateMachine wsm, Handler target) {
         super(TAG, target.getLooper());
@@ -82,7 +82,7 @@
      * HSM states
      *******************************************************/
 
-    class DefaultState extends HierarchicalState {
+    class DefaultState extends State {
         @Override
          public void enter() {
              if (DBG) Log.d(TAG, getName() + "\n");
@@ -128,7 +128,7 @@
         }
     }
 
-    class ActiveState extends HierarchicalState {
+    class ActiveState extends State {
         @Override
          public void enter() {
              if (DBG) Log.d(TAG, getName() + "\n");
@@ -182,7 +182,7 @@
         }
     }
 
-    class InactiveState extends HierarchicalState {
+    class InactiveState extends State {
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");