Merge "Fix bugs in ListView"
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 9c9c5c4..9aa70a4 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -78,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)
@@ -106,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/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/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
index 227b042..dc5ff70 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisRequest.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
@@ -73,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;
         }
@@ -110,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;
             }
@@ -223,16 +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;
@@ -244,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;
         }
 
@@ -260,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;
@@ -271,4 +319,4 @@
         setupVolume(audioTrack, mVolume, mPan);
         return audioTrack;
     }
-}
\ No newline at end of file
+}
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/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/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/opengl/libs/GLES2_dbg/Android.mk b/opengl/libs/GLES2_dbg/Android.mk
index 853cce6..9f6e68c 100644
--- a/opengl/libs/GLES2_dbg/Android.mk
+++ b/opengl/libs/GLES2_dbg/Android.mk
@@ -45,3 +45,5 @@
 LOCAL_MODULE_TAGS := optional
 
 include $(BUILD_SHARED_LIBRARY)
+
+include $(LOCAL_PATH)/test/Android.mk
diff --git a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
index fe93874..71892d3 100644
--- a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
+++ b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
@@ -25,11 +25,11 @@
 namespace android
 {
 
-static pthread_key_t sEGLThreadLocalStorageKey = -1;
+pthread_key_t dbgEGLThreadLocalStorageKey = -1;
 
 DbgContext * getDbgContextThreadSpecific()
 {
-    tls_t* tls = (tls_t*)pthread_getspecific(sEGLThreadLocalStorageKey);
+    tls_t* tls = (tls_t*)pthread_getspecific(dbgEGLThreadLocalStorageKey);
     return tls->dbg;
 }
 
@@ -63,7 +63,7 @@
 DbgContext * CreateDbgContext(const pthread_key_t EGLThreadLocalStorageKey,
                               const unsigned version, const gl_hooks_t * const hooks)
 {
-    sEGLThreadLocalStorageKey = EGLThreadLocalStorageKey;
+    dbgEGLThreadLocalStorageKey = EGLThreadLocalStorageKey;
     assert(version < 2);
     assert(GL_NO_ERROR == hooks->gl.glGetError());
     GLint MAX_VERTEX_ATTRIBS = 0;
@@ -149,6 +149,37 @@
     }
 }
 
+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) {
diff --git a/opengl/libs/GLES2_dbg/src/header.h b/opengl/libs/GLES2_dbg/src/header.h
index c9e6c41..f2b1fa6 100644
--- a/opengl/libs/GLES2_dbg/src/header.h
+++ b/opengl/libs/GLES2_dbg/src/header.h
@@ -73,8 +73,9 @@
 };
 
 struct DbgContext {
-private:
     static const unsigned int LZF_CHUNK_SIZE = 256 * 1024;
+
+private:
     char * lzf_buf; // malloc / free; for lzf chunk compression and other uses
 
     // used as buffer and reference frame for ReadPixels; malloc/free
@@ -129,6 +130,8 @@
 
     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];
diff --git a/opengl/libs/GLES2_dbg/src/server.cpp b/opengl/libs/GLES2_dbg/src/server.cpp
index f13d6cc..ba4960d 100644
--- a/opengl/libs/GLES2_dbg/src/server.cpp
+++ b/opengl/libs/GLES2_dbg/src/server.cpp
@@ -186,7 +186,7 @@
         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);
@@ -246,8 +246,9 @@
     msg.set_function(function);
 
     // 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(false);
+    cmd.set_expect_response(expectResponse);
     glesv2debugger::Message_Function oldCmd = cmd.function();
     Send(msg, cmd);
     expectResponse = cmd.expect_response();
diff --git a/opengl/libs/GLES2_dbg/src/vertex.cpp b/opengl/libs/GLES2_dbg/src/vertex.cpp
index 7edc050..029ee3b 100644
--- a/opengl/libs/GLES2_dbg/src/vertex.cpp
+++ b/opengl/libs/GLES2_dbg/src/vertex.cpp
@@ -43,10 +43,8 @@
 
     void * pixels = NULL;
     int viewport[4] = {};
-    if (!expectResponse) {
-        cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
-        cmd.set_expect_response(false);
-    }
+    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();
@@ -61,8 +59,6 @@
             msg.set_function(glesv2debugger::Message_Function_glDrawArrays);
             msg.set_type(glesv2debugger::Message_Type_AfterCall);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
-                cmd.set_function(glesv2debugger::Message_Function_SKIP);
             if (!expectResponse) {
                 cmd.set_function(glesv2debugger::Message_Function_SKIP);
                 cmd.set_expect_response(false);
@@ -154,10 +150,8 @@
 
     void * pixels = NULL;
     int viewport[4] = {};
-    if (!expectResponse) {
-        cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
-        cmd.set_expect_response(false);
-    }
+    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();
@@ -172,8 +166,6 @@
             msg.set_function(glesv2debugger::Message_Function_glDrawElements);
             msg.set_type(glesv2debugger::Message_Type_AfterCall);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
-                cmd.set_function(glesv2debugger::Message_Function_SKIP);
             if (!expectResponse) {
                 cmd.set_function(glesv2debugger::Message_Function_SKIP);
                 cmd.set_expect_response(false);
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..7fb87ea
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_server.cpp
@@ -0,0 +1,197 @@
+/*
+ ** 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;
+    }
+};
+
+
+
+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());
+}
+
+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..1c31a94
--- /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_NE((void *)NULL, tls.dbg);
+        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_NE((char *)NULL, buffer);
+        }
+        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_NE((void *)NULL, pixels);
+            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/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/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) {